/**
* KONFIGURASI UTAMA
* Masukkan ID Folder Drive (untuk simpan foto) dan URL Spreadsheet Anda.
*/
const FOLDER_ID = "1gWEJ-d3PGhkngLIgqDYzLUSFEnCYcZRC";
const SS_URL = "https://docs.google.com/spreadsheets/d/11XMXDbfjOn2gUCS8x74hKnodAe7aEXBNlES5L81ozTQ/edit?usp=sharing";
const SHEET_NAME = "MenuCoffee";
/**
* Melayani halaman web utama.
*/
function doGet() {
return HtmlService.createHtmlOutputFromFile('index')
.setTitle('OatBrew Admin & Landing Page')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
/**
* Fungsi untuk inisialisasi awal Spreadsheet.
*/
function setupDatabase() {
try {
const ss = SpreadsheetApp.openByUrl(SS_URL);
let sheet = ss.getSheetByName(SHEET_NAME);
if (!sheet) {
sheet = ss.insertSheet(SHEET_NAME);
}
const headers = ["Timestamp", "Nama Produk", "Harga", "Deskripsi", "URL Foto"];
sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
sheet.getRange(1, 1, 1, headers.length).setFontWeight("bold").setBackground("#f3f3f3");
return "Database berjaya disiapkan!";
} catch (e) {
return "Ralat: " + e.toString();
}
}
/**
* Fungsi Server-side untuk menyimpan data menu.
* Dipanggil dari frontend menggunakan google.script.run.
*/
function saveMenuData(data) {
try {
// 1. Simpan Foto ke Google Drive
const folder = DriveApp.getFolderById(FOLDER_ID);
const contentType = data.mimeType;
const bytes = Utilities.base64Decode(data.image);
const blob = Utilities.newBlob(bytes, contentType, data.fileName);
const file = folder.createFile(blob);
// Atur izin lihat bagi sesiapa yang mempunyai pautan
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
const fileUrl = file.getUrl();
// 2. Simpan Data ke Spreadsheet
const ss = SpreadsheetApp.openByUrl(SS_URL);
const sheet = ss.getSheetByName(SHEET_NAME);
sheet.appendRow([
new Date(),
data.productName,
data.price,
data.description,
fileUrl
]);
return { status: "success", message: "Menu berjaya disimpan ke Spreadsheet!" };
} catch (error) {
return { status: "error", message: error.toString() };
}
}
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OatBrew - Segarnya Instan</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
body { font-family: 'Plus+Jakarta+Sans', sans-serif; }
.coffee-gradient { background: linear-gradient(135deg, #4b3832 0%, #2b1d1a 100%); }
.oat-bg { background-color: #f7f3f0; }
.view-content { transition: opacity 0.3s ease-in-out; }
.hidden-view { display: none; opacity: 0; }
</style>
</head>
<body class="oat-bg text-stone-800">
<!-- Navigation -->
<nav class="fixed w-full z-50 bg-white/80 backdrop-blur-md border-b border-stone-200">
<div class="max-w-7xl mx-auto px-6 py-4 flex justify-between items-center">
<div class="text-2xl font-800 tracking-tighter text-stone-900 cursor-pointer" onclick="switchView('landing')">
OAT<span class="text-amber-700">BREW.</span>
</div>
<div class="hidden md:flex space-x-8 font-semibold text-sm uppercase tracking-widest text-stone-600">
<a href="javascript:void(0)" onclick="switchView('landing')" class="hover:text-amber-700 transition">Laman Utama</a>
<a href="javascript:void(0)" onclick="switchView('admin')" class="hover:text-amber-700 transition">Upload Menu</a>
</div>
<button onclick="switchView('admin')" class="bg-amber-800 text-white px-6 py-2 rounded-full text-sm font-bold hover:bg-amber-900 transition">
ADMIN PANEL
</button>
</div>
</nav>
<!-- LANDING VIEW -->
<main id="landing-view" class="view-content pt-32 pb-20 px-6">
<div class="max-w-7xl mx-auto grid md:grid-cols-2 gap-12 items-center">
<div class="space-y-6 text-center md:text-left">
<h1 class="text-5xl md:text-7xl font-800 text-stone-900">Segarnya Instan, <span class="text-amber-700">'Dosa'-nya Nol.</span></h1>
<p class="text-lg text-stone-600">Booster kafein berkualiti untuk pekerja sibuk yang mementingkan kesihatan.</p>
<button onclick="switchView('admin')" class="bg-stone-900 text-white px-8 py-4 rounded-2xl font-bold">Urus Menu Sekarang</button>
</div>
<div class="relative flex justify-center">
<img src="https://images.unsplash.com/photo-1559496417-e7f25cb247f3?q=80&w=600" class="rounded-3xl shadow-2xl w-full max-w-sm" alt="Kopi">
</div>
</div>
</main>
<!-- ADMIN VIEW (Form Upload) -->
<main id="admin-view" class="view-content hidden-view pt-32 pb-20 px-6">
<div class="max-w-2xl mx-auto bg-white rounded-[2rem] shadow-xl p-8 border border-stone-100">
<h2 class="text-2xl font-800 text-stone-900 mb-8">Tambah Menu Kopi Baru</h2>
<form id="menuForm" class="space-y-6">
<div class="space-y-2">
<label class="block text-xs font-bold text-stone-500 uppercase">Foto Produk</label>
<div class="border-2 border-dashed border-stone-200 rounded-xl p-6 text-center bg-stone-50 relative">
<input type="file" id="coffeePhoto" accept="image/*" class="absolute inset-0 opacity-0 cursor-pointer" onchange="handlePreview(this)" required>
<div id="placeholder">
<p class="text-stone-400">Klik untuk pilih gambar</p>
</div>
<img id="preview" class="hidden h-32 mx-auto rounded-lg">
</div>
</div>
<div class="grid md:grid-cols-2 gap-4">
<input type="text" id="productName" placeholder="Nama Produk" class="w-full p-4 bg-stone-50 border border-stone-200 rounded-xl" required>
<input type="number" id="productPrice" placeholder="Harga (RM/IDR)" class="w-full p-4 bg-stone-50 border border-stone-200 rounded-xl" required>
</div>
<textarea id="productDesc" placeholder="Deskripsi ringkas..." class="w-full p-4 bg-stone-50 border border-stone-200 rounded-xl" rows="3" required></textarea>
<button type="submit" id="submitBtn" class="w-full bg-amber-800 text-white py-4 rounded-xl font-bold hover:bg-amber-900 transition flex justify-center items-center gap-2">
<span id="btnText">SIMPAN MENU</span>
<div id="loader" class="hidden animate-spin h-5 w-5 border-2 border-white border-t-transparent rounded-full"></div>
</button>
</form>
</div>
</main>
<script>
function switchView(view) {
document.getElementById('landing-view').classList.toggle('hidden-view', view !== 'landing');
document.getElementById('admin-view').classList.toggle('hidden-view', view !== 'admin');
}
function handlePreview(input) {
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = (e) => {
document.getElementById('preview').src = e.target.result;
document.getElementById('preview').classList.remove('hidden');
document.getElementById('placeholder').classList.add('hidden');
};
reader.readAsDataURL(input.files[0]);
}
}
document.getElementById('menuForm').onsubmit = function(e) {
e.preventDefault();
const btn = document.getElementById('submitBtn');
const loader = document.getElementById('loader');
const btnText = document.getElementById('btnText');
btn.disabled = true;
loader.classList.remove('hidden');
btnText.innerText = "SEDANG DISIMPAN...";
const file = document.getElementById('coffeePhoto').files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {
const payload = {
image: reader.result.split(',')[1],
fileName: file.name,
mimeType: file.type,
productName: document.getElementById('productName').value,
price: document.getElementById('productPrice').value,
description: document.getElementById('productDesc').value
};
// Memanggil fungsi server di backend.gs
google.script.run
.withSuccessHandler((res) => {
alert(res.message);
document.getElementById('menuForm').reset();
document.getElementById('preview').classList.add('hidden');
document.getElementById('placeholder').classList.remove('hidden');
resetButton();
})
.withFailureHandler((err) => {
alert("Ralat: " + err);
resetButton();
})
.saveMenuData(payload);
};
};
function resetButton() {
const btn = document.getElementById('submitBtn');
const loader = document.getElementById('loader');
const btnText = document.getElementById('btnText');
btn.disabled = false;
loader.classList.add('hidden');
btnText.innerText = "SIMPAN MENU";
}
</script>
</body>
</html>
Tidak ada komentar:
Posting Komentar