Masuk dengan email dan password
+Gunakan akun yang sudah terdaftar untuk mengakses alur pemesanan dengan lebih cepat.
+ + +Belum punya akun? Buat akun sekarang.
+diff --git a/assets/css/custom.css b/assets/css/custom.css
index e0187d8..004c0c6 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -904,3 +904,427 @@ textarea:focus {
grid-template-columns: 44px minmax(72px, 1fr) 44px;
}
}
+
+.diagram-showcase,
+.actor-panel,
+.documentation-note,
+.code-panel {
+ background: var(--surface);
+ border: 1px solid var(--line);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow-sm);
+}
+
+.documentation-note {
+ padding: 1rem 1.15rem;
+ background: linear-gradient(180deg, #ffffff 0%, #f8f7f4 100%);
+}
+
+.documentation-note strong {
+ display: block;
+ margin-bottom: 0.35rem;
+}
+
+.documentation-note p {
+ margin: 0;
+ color: var(--muted);
+ line-height: 1.7;
+}
+
+.diagram-showcase {
+ padding: clamp(1rem, 2vw, 1.5rem);
+ background: linear-gradient(180deg, #fffcf7 0%, #f6f4ef 100%);
+}
+
+.diagram-scroll {
+ overflow-x: auto;
+}
+
+.diagram-scroll svg {
+ display: block;
+ width: 100%;
+ height: auto;
+ min-width: 960px;
+}
+
+.diagram-boundary {
+ fill: rgba(255, 255, 255, 0.92);
+ stroke: #2b2927;
+ stroke-width: 2;
+}
+
+.diagram-node {
+ fill: #ffffff;
+ stroke: #2b2927;
+ stroke-width: 2.25;
+}
+
+.diagram-connector {
+ fill: none;
+ stroke: #a8a29e;
+ stroke-width: 2;
+ stroke-linecap: round;
+}
+
+.diagram-actor {
+ fill: none;
+ stroke: #2b2927;
+ stroke-width: 2.75;
+ stroke-linecap: round;
+}
+
+.diagram-title {
+ font: 700 24px 'Inter', sans-serif;
+ fill: #171717;
+}
+
+.diagram-actor-label,
+.diagram-node-label {
+ font: 600 18px 'Inter', sans-serif;
+ fill: #171717;
+}
+
+.diagram-node-label--small,
+.diagram-boundary-label {
+ font: 500 16px 'Inter', sans-serif;
+ fill: #57534e;
+}
+
+.actor-panel {
+ padding: 1.4rem;
+ height: 100%;
+}
+
+.actor-panel__head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 1rem;
+}
+
+.actor-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 5rem;
+ padding: 0.45rem 0.8rem;
+ border-radius: 999px;
+ background: var(--accent-soft);
+ border: 1px solid var(--line);
+ font-size: 0.8rem;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.actor-panel h2,
+.actor-panel h3 {
+ font-size: 1.2rem;
+ margin: 0;
+}
+
+.actor-panel p {
+ color: var(--muted);
+ line-height: 1.7;
+}
+
+.usecase-list {
+ list-style: none;
+ padding: 0;
+ margin: 1rem 0 0;
+ display: grid;
+ gap: 0.75rem;
+}
+
+.usecase-list li {
+ display: flex;
+ gap: 0.8rem;
+ align-items: flex-start;
+ padding: 0.9rem 1rem;
+ border: 1px solid var(--line);
+ border-radius: var(--radius-md);
+ background: var(--surface-alt);
+}
+
+.usecase-list span {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.8rem;
+ height: 1.8rem;
+ flex-shrink: 0;
+ border-radius: 999px;
+ background: var(--surface);
+ border: 1px solid var(--line);
+ font-size: 0.8rem;
+ font-weight: 700;
+}
+
+.code-panel {
+ overflow: hidden;
+}
+
+.code-panel summary {
+ cursor: pointer;
+ padding: 1rem 1.15rem;
+ font-weight: 600;
+ list-style: none;
+}
+
+.code-panel summary::-webkit-details-marker {
+ display: none;
+}
+
+.code-panel pre {
+ margin: 0;
+ padding: 0 1.15rem 1.15rem;
+ white-space: pre-wrap;
+ word-break: break-word;
+ font-size: 0.92rem;
+ line-height: 1.65;
+ color: var(--text);
+ background: transparent;
+}
+
+.code-panel code {
+ font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', monospace;
+}
+
+
+.payment-info-card,
+.auth-side-card,
+.auth-panel,
+.account-stat {
+ background: var(--surface);
+ border: 1px solid var(--line);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow-sm);
+}
+
+.payment-info-card {
+ padding: 1.35rem;
+ background: linear-gradient(180deg, #ffffff 0%, #f8f7f4 100%);
+}
+
+.payment-info-card__label {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.4rem 0.7rem;
+ margin-bottom: 0.85rem;
+ border-radius: 999px;
+ background: var(--accent-soft);
+ color: var(--text);
+ font-size: 0.8rem;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.payment-info-card__copy,
+.payment-info-card__instruction {
+ color: var(--muted);
+ line-height: 1.7;
+}
+
+.payment-info-card__instruction {
+ padding-top: 0.85rem;
+ margin-top: 0.85rem;
+ border-top: 1px solid var(--line);
+}
+
+.contact-panel {
+ height: 100%;
+}
+
+.contact-checklist {
+ display: grid;
+ gap: 0.85rem;
+}
+
+.contact-checklist__item {
+ display: grid;
+ gap: 0.25rem;
+ padding: 0.95rem 1rem;
+ border: 1px solid var(--line);
+ border-radius: var(--radius-md);
+ background: var(--surface-alt);
+}
+
+.contact-checklist__item span {
+ color: var(--muted);
+ line-height: 1.65;
+}
+
+.auth-sidebar {
+ display: grid;
+ gap: 1rem;
+ position: sticky;
+ top: 6rem;
+}
+
+.auth-side-card {
+ padding: 1.25rem;
+}
+
+.sidebar-link-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: grid;
+ gap: 0.75rem;
+}
+
+.sidebar-link-list li a {
+ display: block;
+ padding: 0.95rem 1rem;
+ border: 1px solid var(--line);
+ border-radius: var(--radius-md);
+ background: var(--surface-alt);
+ transition: all 0.2s ease;
+}
+
+.sidebar-link-list li a:hover {
+ border-color: var(--text);
+ background: var(--surface);
+ transform: translateY(-1px);
+}
+
+.sidebar-link-list strong {
+ display: block;
+ margin-bottom: 0.2rem;
+}
+
+.sidebar-link-list span {
+ display: block;
+ color: var(--muted);
+ font-size: 0.9rem;
+ line-height: 1.6;
+}
+
+.sidebar-metric {
+ font-size: clamp(2.1rem, 4vw, 2.8rem);
+ line-height: 1;
+ font-weight: 800;
+ letter-spacing: -0.05em;
+ margin: 0.3rem 0 0.75rem;
+}
+
+.auth-shell {
+ padding: clamp(1.2rem, 2vw, 2rem);
+}
+
+.auth-toggle {
+ display: inline-flex;
+ gap: 0.4rem;
+ padding: 0.35rem;
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ background: var(--surface-alt);
+}
+
+.toggle-pill {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.65rem 1rem;
+ border-radius: 999px;
+ color: var(--muted);
+ font-weight: 600;
+ transition: all 0.2s ease;
+}
+
+.toggle-pill:hover,
+.toggle-pill.is-active {
+ color: var(--text);
+ background: var(--surface);
+ box-shadow: var(--shadow-sm);
+}
+
+.auth-panel {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ padding: 1.35rem;
+ height: 100%;
+}
+
+.auth-panel--active {
+ border-color: var(--line-strong);
+ box-shadow: inset 0 0 0 1px rgba(23, 23, 23, 0.08), var(--shadow-sm);
+}
+
+.auth-panel__head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+}
+
+.auth-note {
+ margin-top: 1rem;
+ color: var(--muted);
+}
+
+.auth-note a {
+ text-decoration: underline;
+ text-underline-offset: 0.15em;
+}
+
+.auth-state-chip {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: fit-content;
+ padding: 0.45rem 0.75rem;
+ margin-bottom: 0.8rem;
+ border-radius: 999px;
+ background: #ecfdf3;
+ border: 1px solid #bbf7d0;
+ color: #166534;
+ font-size: 0.82rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+.account-stat {
+ padding: 1rem 1.1rem;
+ height: 100%;
+ background: linear-gradient(180deg, #ffffff 0%, #f8f7f4 100%);
+}
+
+.account-stat__label {
+ display: block;
+ margin-bottom: 0.35rem;
+ color: var(--muted);
+ font-size: 0.78rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+@media (max-width: 991.98px) {
+ .auth-sidebar {
+ position: static;
+ }
+}
+
+@media (max-width: 767.98px) {
+ .auth-shell,
+ .auth-panel,
+ .auth-side-card,
+ .payment-info-card,
+ .account-stat {
+ padding: 1rem;
+ }
+
+ .auth-toggle {
+ display: flex;
+ width: 100%;
+ }
+
+ .toggle-pill {
+ flex: 1;
+ text-align: center;
+ }
+}
diff --git a/assets/pasted-20260526-080054-2f870e73.png b/assets/pasted-20260526-080054-2f870e73.png
new file mode 100644
index 0000000..cdee8da
Binary files /dev/null and b/assets/pasted-20260526-080054-2f870e73.png differ
diff --git a/assets/pasted-20260526-080630-6e52341a.png b/assets/pasted-20260526-080630-6e52341a.png
new file mode 100644
index 0000000..0db1c30
Binary files /dev/null and b/assets/pasted-20260526-080630-6e52341a.png differ
diff --git a/auth.php b/auth.php
new file mode 100644
index 0000000..c1b6085
--- /dev/null
+++ b/auth.php
@@ -0,0 +1,239 @@
+ ''];
+$registerForm = ['full_name' => '', 'email' => ''];
+$loginErrors = [];
+$registerErrors = [];
+$loginMessage = '';
+$registerMessage = '';
+
+if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
+ $action = (string)($_POST['action'] ?? '');
+
+ if ($action === 'login') {
+ $result = store_login_user($_POST);
+ if (!empty($result['success'])) {
+ store_flash('success', (string)($result['message'] ?? 'Login berhasil.'));
+ header('Location: ' . $redirectTo);
+ exit;
+ }
+
+ $mode = 'login';
+ $loginForm = $result['form'] ?? $loginForm;
+ $loginErrors = $result['errors'] ?? [];
+ $loginMessage = (string)($result['message'] ?? 'Login belum berhasil diproses.');
+ }
+
+ if ($action === 'register') {
+ $result = store_register_user($_POST);
+ if (!empty($result['success'])) {
+ store_flash('success', (string)($result['message'] ?? 'Akun berhasil dibuat.'));
+ header('Location: ' . $redirectTo);
+ exit;
+ }
+
+ $mode = 'register';
+ $registerForm = $result['form'] ?? $registerForm;
+ $registerErrors = $result['errors'] ?? [];
+ $registerMessage = (string)($result['message'] ?? 'Pendaftaran belum berhasil diproses.');
+ }
+}
+
+$currentUser = store_current_user();
+$summary = store_cart_summary();
+$categories = store_categories();
+$accountTitle = $currentUser ? 'Akun Saya' : 'Login / Register';
+$accountDescription = $currentUser
+ ? 'Ringkasan akun user untuk melanjutkan belanja, checkout, dan melacak pesanan.'
+ : 'Halaman login dan registrasi user untuk masuk ke sistem menggunakan email dan password.';
+
+store_page_start($accountTitle, $accountDescription, ['noindex' => true]);
+?>
+
+ = $currentUser
+ ? 'Anda sudah login. Gunakan halaman ini untuk kembali ke katalog, membuka keranjang, atau melacak status pesanan.'
+ : 'Masukkan email dan password pada form login, atau buat akun baru melalui form registrasi agar data pengguna tersimpan lebih rapi.' ?>
+ Akun Anda sudah tersimpan di sesi browser ini dan siap digunakan untuk melanjutkan aktivitas belanja. Gunakan akun yang sudah terdaftar untuk mengakses alur pemesanan dengan lebih cepat. Belum punya akun? Buat akun sekarang. Setelah registrasi berhasil, akun akan langsung aktif pada sesi browser ini.
+ = $currentUser ? 'Akun user aktif dan siap dipakai.' : 'Tampilan login digunakan oleh pengguna yang telah terdaftar untuk masuk ke dalam sistem.' ?>
+
+ Halo, = h(store_user_first_name((string)$currentUser['full_name'])) ?>.
+ Masuk dengan email dan password
+ Daftarkan akun pengguna
+
Status awal pesanan adalah Menunggu Pembayaran. Instruksi pembayaran akan tampil setelah order berhasil dibuat.
+ +- Katalog, keranjang, checkout, dan halaman status pesanan sekarang terhubung dalam satu alur yang rapi. - Cocok untuk validasi toko online berbasis PHP + MySQL sebelum lanjut ke admin panel penuh. + Tampilan user bakery kini dibuat lebih mudah dipahami: ada menu Home, + Daftar Kue, Info Pembayaran, Kontak Kami, + serta Login / Register agar alur pemesanan terasa lebih terstruktur.
Ada kategori, harga, deskripsi singkat, dan halaman detail produk untuk tiap item.
+Halaman login/register kini menjadi pintu masuk yang jelas bagi pengguna yang sudah atau belum memiliki akun.
Order number dibuat otomatis, item pesanan disimpan di MySQL, dan status awal langsung tercatat.
+Produk dibagi per kategori agar pengguna lebih cepat menemukan cake, bread, atau pastry yang dibutuhkan.
Pelanggan cukup masukkan kode pesanan dan email untuk melihat progress dan instruksi pembayaran.
+Setelah checkout, pelanggan tetap bisa melihat metode bayar dan memeriksa progress pesanan dari halaman status.
Gunakan katalog ini sebagai starting point sebelum menambahkan admin, stok, kupon, dan pembayaran real.
+ Daftar Kue +Gunakan filter kategori untuk melihat produk yang paling relevan sebelum ditambahkan ke keranjang belanja.
Bagian ini menjelaskan metode pembayaran sejak awal agar proses checkout terasa lebih meyakinkan.
+= h($method['description']) ?>
+= h($method['instruction']) ?>
+Bagian kontak diposisikan sebagai tempat pengguna memahami kapan harus menghubungi admin toko.
+Pelanggan lihat katalog, buka detail produk, lalu tambahkan item ke keranjang.
+Pengguna masuk dengan email dan password, atau membuat akun baru jika belum terdaftar.
Isi nama, email, telepon, alamat, dan metode pembayaran. Order number dibuat otomatis.
+Setelah memilih produk, pengguna melanjutkan ke checkout untuk menyimpan alamat dan metode pembayaran.
Gunakan order number dan email untuk melihat status pesanan dan instruksi pembayaran kapan saja.
+Gunakan kode pesanan dan email untuk melihat instruksi pembayaran dan status order secara mandiri.
Halaman use case diagram versi pertama masih bisa dibuka untuk kebutuhan laporan, revisi, atau presentasi sistem.
+ Buka use case diagram +
+ Halaman ini menampilkan blueprint fungsional level tinggi untuk Sistem Penjualan Kue Online Sekut Bakery.
+ Versi yang dipasang memakai asosiasi langsung antara aktor dan use case, tanpa relasi <<include>> atau <<extend>>.
+
+ Diagram di bawah mengikuti versi pertama yang paling sederhana: dua aktor utama, satu system boundary, + dan relasi asosiasi langsung ke setiap use case inti. +
+Pelanggan berfokus pada alur pembelian: masuk ke sistem, melihat produk, mengisi keranjang, checkout, lalu mengirim bukti pembayaran.
+Admin berfokus pada pengelolaan sistem: login ke dashboard lalu mengatur kategori, produk, pembelian, laporan, dan data pelanggan.
+Kode ini sama dengan versi pertama yang sebelumnya sudah dipilih, jadi bisa langsung disalin untuk laporan atau digenerate ulang di PlantUML.
+= h($plantumlSource) ?>
+