Sekut bakery
This commit is contained in:
parent
192f07588e
commit
cb3a15004c
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
assets/pasted-20260526-080054-2f870e73.png
Normal file
BIN
assets/pasted-20260526-080054-2f870e73.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
BIN
assets/pasted-20260526-080630-6e52341a.png
Normal file
BIN
assets/pasted-20260526-080630-6e52341a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
239
auth.php
Normal file
239
auth.php
Normal file
@ -0,0 +1,239 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/store.php';
|
||||
|
||||
$mode = (string)($_GET['mode'] ?? 'login');
|
||||
if ($mode !== 'register') {
|
||||
$mode = 'login';
|
||||
}
|
||||
|
||||
$redirectTo = store_safe_redirect((string)($_REQUEST['redirect_to'] ?? 'auth.php'), 'auth.php');
|
||||
$loginForm = ['email' => ''];
|
||||
$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]);
|
||||
?>
|
||||
<section class="section-block pt-0">
|
||||
<div class="section-heading mb-4">
|
||||
<span class="eyebrow">Login User</span>
|
||||
<h1 class="section-title">
|
||||
<?= $currentUser ? 'Akun user aktif dan siap dipakai.' : 'Tampilan login digunakan oleh pengguna yang telah terdaftar untuk masuk ke dalam sistem.' ?>
|
||||
</h1>
|
||||
<p class="section-copy mb-0">
|
||||
<?= $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.' ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-4">
|
||||
<aside class="auth-sidebar">
|
||||
<div class="auth-side-card">
|
||||
<div class="card-kicker">Kategori Menu</div>
|
||||
<ul class="sidebar-link-list mb-0">
|
||||
<?php foreach ($categories as $key => $category): ?>
|
||||
<?php if ($key === 'all') continue; ?>
|
||||
<li>
|
||||
<a href="index.php?category=<?= h($key) ?>#catalog">
|
||||
<strong><?= h($category['label']) ?></strong>
|
||||
<span><?= h($category['description']) ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="auth-side-card">
|
||||
<div class="card-kicker">Keranjang Belanja</div>
|
||||
<div class="sidebar-metric"><?= h((string)store_cart_count()) ?></div>
|
||||
<p class="mb-3 text-muted">Item yang sudah dipilih tetap tersimpan dan bisa dilanjutkan ke checkout kapan saja.</p>
|
||||
<a class="btn btn-outline-secondary w-100" href="cart.php">Buka keranjang</a>
|
||||
</div>
|
||||
|
||||
<div class="auth-side-card">
|
||||
<div class="card-kicker">Info Pembayaran</div>
|
||||
<ul class="list-clean compact-list mb-0">
|
||||
<li><span class="list-index">1</span><span>Pilih metode pembayaran saat checkout.</span></li>
|
||||
<li><span class="list-index">2</span><span>Simpan order number untuk pelacakan status.</span></li>
|
||||
<li><span class="list-index">3</span><span>Siapkan bukti pembayaran jika metode bayar memerlukannya.</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8">
|
||||
<?php if ($currentUser): ?>
|
||||
<section class="surface-panel auth-shell">
|
||||
<div class="auth-state-chip">Login aktif</div>
|
||||
<h2 class="summary-title mb-2">Halo, <?= h(store_user_first_name((string)$currentUser['full_name'])) ?>.</h2>
|
||||
<p class="section-copy mb-4">Akun Anda sudah tersimpan di sesi browser ini dan siap digunakan untuk melanjutkan aktivitas belanja.</p>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="account-stat">
|
||||
<span class="account-stat__label">Nama pengguna</span>
|
||||
<strong><?= h((string)$currentUser['full_name']) ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="account-stat">
|
||||
<span class="account-stat__label">Email login</span>
|
||||
<strong><?= h((string)$currentUser['email']) ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="account-stat">
|
||||
<span class="account-stat__label">Item di keranjang</span>
|
||||
<strong><?= h((string)store_cart_count()) ?> produk</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="account-stat">
|
||||
<span class="account-stat__label">Total sementara</span>
|
||||
<strong><?= h(store_money((float)$summary['grand_total'])) ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-dark" href="index.php#catalog">Lanjut belanja</a>
|
||||
<a class="btn btn-outline-secondary" href="cart.php">Buka keranjang</a>
|
||||
<a class="btn btn-outline-secondary" href="order_status.php">Lacak pesanan</a>
|
||||
<a class="btn btn-outline-secondary" href="logout.php">Logout</a>
|
||||
</div>
|
||||
</section>
|
||||
<?php else: ?>
|
||||
<section class="surface-panel auth-shell">
|
||||
<div class="auth-toggle mb-4">
|
||||
<a class="toggle-pill<?= $mode === 'login' ? ' is-active' : '' ?>" href="auth.php?mode=login<?= $redirectTo !== 'auth.php' ? '&redirect_to=' . urlencode($redirectTo) : '' ?>">Login</a>
|
||||
<a class="toggle-pill<?= $mode === 'register' ? ' is-active' : '' ?>" href="auth.php?mode=register<?= $redirectTo !== 'auth.php' ? '&redirect_to=' . urlencode($redirectTo) : '' ?>">Register</a>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-xl-6">
|
||||
<article class="auth-panel<?= $mode === 'login' ? ' auth-panel--active' : '' ?>">
|
||||
<div class="auth-panel__head">
|
||||
<div>
|
||||
<div class="card-kicker">Login User</div>
|
||||
<h2 class="h4 mb-1">Masuk dengan email dan password</h2>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted mb-4">Gunakan akun yang sudah terdaftar untuk mengakses alur pemesanan dengan lebih cepat.</p>
|
||||
|
||||
<?php if ($loginMessage !== ''): ?>
|
||||
<div class="alert alert-warning border-0 shadow-sm" role="alert"><?= h($loginMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="auth.php?mode=login" method="post" class="d-grid gap-3" data-auto-disable>
|
||||
<input type="hidden" name="action" value="login">
|
||||
<input type="hidden" name="redirect_to" value="<?= h($redirectTo) ?>">
|
||||
<div>
|
||||
<label class="form-label" for="login_email">Email</label>
|
||||
<input id="login_email" name="email" type="email" class="form-control<?= store_input_class($loginErrors, 'email') ?>" value="<?= h((string)($loginForm['email'] ?? '')) ?>" autocomplete="username" placeholder="nama@email.com" required>
|
||||
<?php if (!empty($loginErrors['email'])): ?><div class="invalid-feedback"><?= h((string)$loginErrors['email']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label" for="login_password">Password</label>
|
||||
<input id="login_password" name="password" type="password" class="form-control<?= store_input_class($loginErrors, 'password') ?>" autocomplete="current-password" placeholder="Minimal 8 karakter" required>
|
||||
<?php if (!empty($loginErrors['password'])): ?><div class="invalid-feedback"><?= h((string)$loginErrors['password']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<button class="btn btn-dark" type="submit">Login</button>
|
||||
</form>
|
||||
|
||||
<p class="auth-note mb-0">Belum punya akun? <a href="auth.php?mode=register<?= $redirectTo !== 'auth.php' ? '&redirect_to=' . urlencode($redirectTo) : '' ?>">Buat akun sekarang</a>.</p>
|
||||
</article>
|
||||
</div>
|
||||
<div class="col-xl-6">
|
||||
<article class="auth-panel<?= $mode === 'register' ? ' auth-panel--active' : '' ?>">
|
||||
<div class="auth-panel__head">
|
||||
<div>
|
||||
<div class="card-kicker">Register</div>
|
||||
<h2 class="h4 mb-1">Daftarkan akun pengguna</h2>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted mb-4">Setelah registrasi berhasil, akun akan langsung aktif pada sesi browser ini.</p>
|
||||
|
||||
<?php if ($registerMessage !== ''): ?>
|
||||
<div class="alert alert-warning border-0 shadow-sm" role="alert"><?= h($registerMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="auth.php?mode=register" method="post" class="d-grid gap-3" data-auto-disable>
|
||||
<input type="hidden" name="action" value="register">
|
||||
<input type="hidden" name="redirect_to" value="<?= h($redirectTo) ?>">
|
||||
<div>
|
||||
<label class="form-label" for="register_full_name">Nama lengkap</label>
|
||||
<input id="register_full_name" name="full_name" type="text" class="form-control<?= store_input_class($registerErrors, 'full_name') ?>" value="<?= h((string)($registerForm['full_name'] ?? '')) ?>" maxlength="120" autocomplete="name" placeholder="Nama lengkap pengguna" required>
|
||||
<?php if (!empty($registerErrors['full_name'])): ?><div class="invalid-feedback"><?= h((string)$registerErrors['full_name']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label" for="register_email">Email</label>
|
||||
<input id="register_email" name="email" type="email" class="form-control<?= store_input_class($registerErrors, 'email') ?>" value="<?= h((string)($registerForm['email'] ?? '')) ?>" maxlength="160" autocomplete="username" placeholder="nama@email.com" required>
|
||||
<?php if (!empty($registerErrors['email'])): ?><div class="invalid-feedback"><?= h((string)$registerErrors['email']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label" for="register_password">Password</label>
|
||||
<input id="register_password" name="password" type="password" class="form-control<?= store_input_class($registerErrors, 'password') ?>" minlength="8" autocomplete="new-password" placeholder="Minimal 8 karakter" required>
|
||||
<?php if (!empty($registerErrors['password'])): ?><div class="invalid-feedback"><?= h((string)$registerErrors['password']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label" for="register_confirm_password">Konfirmasi password</label>
|
||||
<input id="register_confirm_password" name="confirm_password" type="password" class="form-control<?= store_input_class($registerErrors, 'confirm_password') ?>" minlength="8" autocomplete="new-password" placeholder="Ulangi password" required>
|
||||
<?php if (!empty($registerErrors['confirm_password'])): ?><div class="invalid-feedback"><?= h((string)$registerErrors['confirm_password']) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<button class="btn btn-dark" type="submit">Buat akun</button>
|
||||
</form>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php store_page_end(); ?>
|
||||
@ -12,6 +12,7 @@ if (!$summary['lines']) {
|
||||
|
||||
$formData = store_checkout_defaults();
|
||||
$errors = [];
|
||||
$currentUser = store_current_user();
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
|
||||
$result = store_create_order($_POST);
|
||||
@ -34,6 +35,12 @@ store_page_start('Checkout', 'Lengkapi data pelanggan, alamat, dan metode pembay
|
||||
<p class="section-copy mb-0">Status awal pesanan adalah <strong>Menunggu Pembayaran</strong>. Instruksi pembayaran akan tampil setelah order berhasil dibuat.</p>
|
||||
</div>
|
||||
|
||||
<?php if (!$currentUser): ?>
|
||||
<div class="alert alert-light border shadow-sm mb-4" role="alert">
|
||||
Belum login? <a href="auth.php?redirect_to=checkout.php">Masuk atau daftar akun</a> agar nama dan email terisi otomatis saat checkout.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($errors['cart'])): ?>
|
||||
<div class="alert alert-warning border-0 shadow-sm mb-4" role="alert">
|
||||
<?= h($errors['cart']) ?>
|
||||
|
||||
144
index.php
144
index.php
@ -12,55 +12,59 @@ if (!isset($categories[$selectedCategory])) {
|
||||
$products = store_filtered_products($selectedCategory);
|
||||
$summary = store_cart_summary();
|
||||
$cartLines = array_slice($summary['lines'], 0, 3);
|
||||
$currentUser = store_current_user();
|
||||
$paymentMethods = store_payment_methods();
|
||||
|
||||
store_page_start(
|
||||
'Toko Online Bakery',
|
||||
'Pilih cake, roti, dan pastry favorit, simpan ke keranjang, checkout, lalu pantau status pesanan dari satu halaman.'
|
||||
'Home',
|
||||
'Website toko online bakery dengan halaman home, daftar kue, info pembayaran, kontak, dan login user yang lebih rapi.'
|
||||
);
|
||||
?>
|
||||
<section class="hero-panel mb-4 mb-lg-5">
|
||||
<div class="row g-4 align-items-center">
|
||||
<div class="col-lg-7">
|
||||
<span class="eyebrow">Initial MVP slice</span>
|
||||
<h1 class="display-title">Belanja bakery tanpa alur yang berantakan.</h1>
|
||||
<span class="eyebrow">Home</span>
|
||||
<h1 class="display-title">Pesan kue lebih jelas dengan menu user yang lengkap.</h1>
|
||||
<p class="lead-copy">
|
||||
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 <strong>Home</strong>,
|
||||
<strong>Daftar Kue</strong>, <strong>Info Pembayaran</strong>, <strong>Kontak Kami</strong>,
|
||||
serta <strong>Login / Register</strong> agar alur pemesanan terasa lebih terstruktur.
|
||||
</p>
|
||||
<div class="d-flex flex-wrap gap-2 mt-4">
|
||||
<a class="btn btn-dark btn-lg" href="#catalog">Belanja sekarang</a>
|
||||
<a class="btn btn-outline-secondary btn-lg" href="order_status.php">Cek status pesanan</a>
|
||||
<a class="btn btn-dark btn-lg" href="#catalog">Daftar kue</a>
|
||||
<a class="btn btn-outline-secondary btn-lg" href="auth.php"><?= $currentUser ? 'Akun saya' : 'Login / Register' ?></a>
|
||||
<a class="btn btn-outline-secondary btn-lg" href="#payment-info">Info pembayaran</a>
|
||||
</div>
|
||||
<div class="row g-3 mt-4">
|
||||
<div class="col-sm-4">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value"><?= h((string)count(store_products())) ?></div>
|
||||
<div class="metric-label">produk demo siap dijual</div>
|
||||
<div class="metric-label">produk bakery tampil di daftar kue</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value">1 tabel</div>
|
||||
<div class="metric-label">pesanan tersimpan di MySQL</div>
|
||||
<div class="metric-value"><?= h((string)count($paymentMethods)) ?></div>
|
||||
<div class="metric-label">metode pembayaran siap dipilih</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value">4 status</div>
|
||||
<div class="metric-label">progress order yang mudah dipahami</div>
|
||||
<div class="metric-value"><?= h((string)store_cart_count()) ?></div>
|
||||
<div class="metric-label">item tersimpan di keranjang</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<aside class="summary-card h-100">
|
||||
<div class="card-kicker">Workflow pelanggan</div>
|
||||
<h2 class="summary-title">Dari pilih produk sampai tracking status.</h2>
|
||||
<div class="card-kicker">Alur pengguna</div>
|
||||
<h2 class="summary-title">Dari login sampai cek status pesanan.</h2>
|
||||
<ul class="list-clean compact-list mb-4">
|
||||
<li><span class="list-index">1</span><span>Pelanggan pilih produk dari katalog atau halaman detail.</span></li>
|
||||
<li><span class="list-index">2</span><span>Keranjang menyimpan item, quantity, subtotal, dan ongkir.</span></li>
|
||||
<li><span class="list-index">3</span><span>Checkout membuat order number dan menyimpan data di sistem.</span></li>
|
||||
<li><span class="list-index">4</span><span>Halaman status menampilkan detail pesanan dan instruksi pembayaran.</span></li>
|
||||
<li><span class="list-index">1</span><span>Pengguna login dengan email dan password atau membuat akun baru.</span></li>
|
||||
<li><span class="list-index">2</span><span>Pengguna membuka daftar kue dan memilih produk bakery yang diinginkan.</span></li>
|
||||
<li><span class="list-index">3</span><span>Checkout menyimpan pesanan, alamat, dan metode pembayaran ke sistem.</span></li>
|
||||
<li><span class="list-index">4</span><span>Kode pesanan dipakai lagi untuk melacak status dan konfirmasi pembayaran.</span></li>
|
||||
</ul>
|
||||
|
||||
<div class="receipt-card">
|
||||
@ -81,8 +85,8 @@ store_page_start(
|
||||
</div>
|
||||
<a class="btn btn-dark w-100 mt-3" href="cart.php">Lanjutkan checkout</a>
|
||||
<?php else: ?>
|
||||
<p class="text-muted mb-3">Keranjang masih kosong. Tambahkan produk di bawah untuk mencoba alur checkout.</p>
|
||||
<a class="btn btn-outline-secondary w-100" href="#catalog">Mulai dari katalog</a>
|
||||
<p class="text-muted mb-3">Keranjang masih kosong. Mulai dari daftar kue untuk mencoba alur pemesanan user.</p>
|
||||
<a class="btn btn-outline-secondary w-100" href="#catalog">Buka daftar kue</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</aside>
|
||||
@ -94,20 +98,20 @@ store_page_start(
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-4">
|
||||
<div class="feature-card h-100">
|
||||
<div class="feature-card__title">Katalog yang langsung bisa dijual</div>
|
||||
<p class="feature-card__copy">Ada kategori, harga, deskripsi singkat, dan halaman detail produk untuk tiap item.</p>
|
||||
<div class="feature-card__title">Login user lebih jelas</div>
|
||||
<p class="feature-card__copy">Halaman login/register kini menjadi pintu masuk yang jelas bagi pengguna yang sudah atau belum memiliki akun.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="feature-card h-100">
|
||||
<div class="feature-card__title">Checkout tersimpan di sistem</div>
|
||||
<p class="feature-card__copy">Order number dibuat otomatis, item pesanan disimpan di MySQL, dan status awal langsung tercatat.</p>
|
||||
<div class="feature-card__title">Daftar kue lebih mudah dicari</div>
|
||||
<p class="feature-card__copy">Produk dibagi per kategori agar pengguna lebih cepat menemukan cake, bread, atau pastry yang dibutuhkan.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="feature-card h-100">
|
||||
<div class="feature-card__title">Status pesanan mandiri</div>
|
||||
<p class="feature-card__copy">Pelanggan cukup masukkan kode pesanan dan email untuk melihat progress dan instruksi pembayaran.</p>
|
||||
<div class="feature-card__title">Pembayaran & status tetap terhubung</div>
|
||||
<p class="feature-card__copy">Setelah checkout, pelanggan tetap bisa melihat metode bayar dan memeriksa progress pesanan dari halaman status.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -116,9 +120,9 @@ store_page_start(
|
||||
<section id="catalog" class="section-block">
|
||||
<div class="section-heading d-flex flex-column flex-lg-row align-items-lg-end justify-content-between gap-3 mb-4">
|
||||
<div>
|
||||
<span class="eyebrow">Katalog Produk</span>
|
||||
<h2 class="section-title">Pilihan cake, bread, dan pastry untuk toko demo.</h2>
|
||||
<p class="section-copy mb-0">Gunakan katalog ini sebagai starting point sebelum menambahkan admin, stok, kupon, dan pembayaran real.</p>
|
||||
<span class="eyebrow">Daftar Kue</span>
|
||||
<h2 class="section-title">Pilihan cake, bread, dan pastry untuk pengguna.</h2>
|
||||
<p class="section-copy mb-0">Gunakan filter kategori untuk melihat produk yang paling relevan sebelum ditambahkan ke keranjang belanja.</p>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 filter-pills" data-filter-bar>
|
||||
<?php foreach ($categories as $key => $category): ?>
|
||||
@ -170,33 +174,101 @@ store_page_start(
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="payment-info" class="section-block pt-0">
|
||||
<div class="section-heading mb-4">
|
||||
<span class="eyebrow">Info Pembayaran</span>
|
||||
<h2 class="section-title">Pilihan pembayaran yang bisa diakses pengguna.</h2>
|
||||
<p class="section-copy mb-0">Bagian ini menjelaskan metode pembayaran sejak awal agar proses checkout terasa lebih meyakinkan.</p>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<?php foreach ($paymentMethods as $method): ?>
|
||||
<div class="col-lg-4">
|
||||
<article class="payment-info-card h-100">
|
||||
<div class="payment-info-card__label"><?= h($method['label']) ?></div>
|
||||
<p class="payment-info-card__copy"><?= h($method['description']) ?></p>
|
||||
<p class="payment-info-card__instruction mb-0"><?= h($method['instruction']) ?></p>
|
||||
</article>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="contact" class="section-block pt-0">
|
||||
<div class="row g-4 align-items-stretch">
|
||||
<div class="col-lg-6">
|
||||
<div class="surface-panel h-100 contact-panel">
|
||||
<span class="eyebrow">Kontak Kami</span>
|
||||
<h2 class="section-title">Area bantuan untuk kebutuhan pengguna.</h2>
|
||||
<p class="section-copy">Bagian kontak diposisikan sebagai tempat pengguna memahami kapan harus menghubungi admin toko.</p>
|
||||
<ul class="list-clean compact-list mb-0">
|
||||
<li><span class="list-index">1</span><span>Konfirmasi ketersediaan produk dan estimasi pengerjaan pesanan.</span></li>
|
||||
<li><span class="list-index">2</span><span>Menanyakan jadwal pengiriman, perubahan alamat, atau detail pesanan.</span></li>
|
||||
<li><span class="list-index">3</span><span>Mengirim bukti pembayaran setelah checkout jika metode bayar memerlukannya.</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<aside class="summary-card h-100 contact-panel">
|
||||
<div class="card-kicker">Langkah cepat</div>
|
||||
<h2 class="summary-title">Yang sebaiknya dilakukan pengguna lebih dulu.</h2>
|
||||
<div class="contact-checklist">
|
||||
<div class="contact-checklist__item">
|
||||
<strong>Login / Register</strong>
|
||||
<span>Masuk dengan email dan password agar proses pemesanan lebih rapi.</span>
|
||||
</div>
|
||||
<div class="contact-checklist__item">
|
||||
<strong>Pilih produk</strong>
|
||||
<span>Tambahkan cake, bread, atau pastry ke keranjang sesuai kebutuhan.</span>
|
||||
</div>
|
||||
<div class="contact-checklist__item">
|
||||
<strong>Lihat status</strong>
|
||||
<span>Gunakan order number dan email untuk memeriksa perkembangan pesanan.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid gap-2 mt-4">
|
||||
<a class="btn btn-dark" href="auth.php"><?= $currentUser ? 'Buka akun saya' : 'Buka login user' ?></a>
|
||||
<a class="btn btn-outline-secondary" href="order_status.php">Lacak pesanan</a>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-block pt-0">
|
||||
<div class="section-heading mb-4">
|
||||
<span class="eyebrow">Cara Order</span>
|
||||
<h2 class="section-title">Alur sederhana yang siap dipakai sebagai base toko online.</h2>
|
||||
<h2 class="section-title">Alur sederhana yang siap dipakai pada tampilan user.</h2>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<div class="step-card h-100">
|
||||
<div class="step-card__number">01</div>
|
||||
<h3>Pilih item</h3>
|
||||
<p>Pelanggan lihat katalog, buka detail produk, lalu tambahkan item ke keranjang.</p>
|
||||
<h3>Login / Register</h3>
|
||||
<p>Pengguna masuk dengan email dan password, atau membuat akun baru jika belum terdaftar.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="step-card h-100">
|
||||
<div class="step-card__number">02</div>
|
||||
<h3>Checkout</h3>
|
||||
<p>Isi nama, email, telepon, alamat, dan metode pembayaran. Order number dibuat otomatis.</p>
|
||||
<h3>Pilih dan checkout</h3>
|
||||
<p>Setelah memilih produk, pengguna melanjutkan ke checkout untuk menyimpan alamat dan metode pembayaran.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="step-card h-100">
|
||||
<div class="step-card__number">03</div>
|
||||
<h3>Lacak status</h3>
|
||||
<p>Gunakan order number dan email untuk melihat status pesanan dan instruksi pembayaran kapan saja.</p>
|
||||
<p>Gunakan kode pesanan dan email untuk melihat instruksi pembayaran dan status order secara mandiri.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-block pt-0">
|
||||
<div class="documentation-note">
|
||||
<strong>Dokumentasi use case tetap tersedia.</strong>
|
||||
<p>Halaman use case diagram versi pertama masih bisa dibuka untuk kebutuhan laporan, revisi, atau presentasi sistem.</p>
|
||||
<a class="btn btn-outline-secondary mt-3" href="use_case.php">Buka use case diagram</a>
|
||||
</div>
|
||||
</section>
|
||||
<?php store_page_end(); ?>
|
||||
|
||||
9
logout.php
Normal file
9
logout.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/store.php';
|
||||
|
||||
store_logout_user();
|
||||
store_flash('success', 'Anda berhasil logout dari akun user.');
|
||||
header('Location: auth.php');
|
||||
exit;
|
||||
@ -7,11 +7,16 @@ $lookupOrder = store_sanitize_line((string)($_GET['order'] ?? ''), 30);
|
||||
$lookupEmail = trim(store_lower((string)($_GET['email'] ?? '')));
|
||||
$created = (string)($_GET['created'] ?? '') === '1';
|
||||
$lastLookup = store_last_order_lookup();
|
||||
$currentUser = store_current_user();
|
||||
|
||||
if ($lookupOrder !== '' && $lookupEmail === '' && ($lastLookup['order_number'] ?? '') === $lookupOrder) {
|
||||
$lookupEmail = (string)($lastLookup['email'] ?? '');
|
||||
}
|
||||
|
||||
if ($lookupEmail === '' && $currentUser) {
|
||||
$lookupEmail = (string)($currentUser['email'] ?? '');
|
||||
}
|
||||
|
||||
$order = null;
|
||||
$searchError = '';
|
||||
if ($lookupOrder !== '') {
|
||||
|
||||
253
store.php
253
store.php
@ -15,6 +15,7 @@ if (basename((string)($_SERVER['SCRIPT_FILENAME'] ?? '')) === basename(__FILE__)
|
||||
const STORE_CART_KEY = 'sekut_cart';
|
||||
const STORE_FLASH_KEY = 'sekut_flash';
|
||||
const STORE_LAST_ORDER_KEY = 'sekut_last_order';
|
||||
const STORE_USER_KEY = 'sekut_user';
|
||||
|
||||
function app_env(string $key, string $fallback = ''): string
|
||||
{
|
||||
@ -41,6 +42,212 @@ function store_brand(): string
|
||||
return 'SEKUT BAKERY';
|
||||
}
|
||||
|
||||
function store_current_user(): ?array
|
||||
{
|
||||
$user = $_SESSION[STORE_USER_KEY] ?? null;
|
||||
if (!is_array($user)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$id = (int)($user['id'] ?? 0);
|
||||
$fullName = store_sanitize_line((string)($user['full_name'] ?? ''), 120);
|
||||
$email = store_lower(trim((string)($user['email'] ?? '')));
|
||||
|
||||
if ($id <= 0 || $fullName === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $id,
|
||||
'full_name' => $fullName,
|
||||
'email' => $email,
|
||||
];
|
||||
}
|
||||
|
||||
function store_is_logged_in(): bool
|
||||
{
|
||||
return store_current_user() !== null;
|
||||
}
|
||||
|
||||
function store_user_first_name(string $fullName): string
|
||||
{
|
||||
$fullName = trim(preg_replace('/\s+/', ' ', $fullName) ?? '');
|
||||
if ($fullName === '') {
|
||||
return 'Pelanggan';
|
||||
}
|
||||
|
||||
$parts = explode(' ', $fullName);
|
||||
return store_substr((string)$parts[0], 0, 24);
|
||||
}
|
||||
|
||||
function store_set_user_session(array $user): void
|
||||
{
|
||||
$_SESSION[STORE_USER_KEY] = [
|
||||
'id' => (int)($user['id'] ?? 0),
|
||||
'full_name' => store_sanitize_line((string)($user['full_name'] ?? ''), 120),
|
||||
'email' => store_lower(trim((string)($user['email'] ?? ''))),
|
||||
];
|
||||
}
|
||||
|
||||
function store_logout_user(): void
|
||||
{
|
||||
unset($_SESSION[STORE_USER_KEY]);
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
}
|
||||
|
||||
function store_auth_password_error(string $password): string
|
||||
{
|
||||
if (store_strlen($password) < 8) {
|
||||
return 'Password minimal 8 karakter.';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function store_register_user(array $source): array
|
||||
{
|
||||
store_ensure_schema();
|
||||
|
||||
$form = [
|
||||
'full_name' => store_sanitize_line((string)($source['full_name'] ?? ''), 120),
|
||||
'email' => store_lower(trim((string)($source['email'] ?? ''))),
|
||||
];
|
||||
$password = (string)($source['password'] ?? '');
|
||||
$confirmPassword = (string)($source['confirm_password'] ?? '');
|
||||
$errors = [];
|
||||
|
||||
if (store_strlen($form['full_name']) < 3) {
|
||||
$errors['full_name'] = 'Nama lengkap minimal 3 karakter.';
|
||||
}
|
||||
|
||||
if (!filter_var($form['email'], FILTER_VALIDATE_EMAIL)) {
|
||||
$errors['email'] = 'Masukkan email yang valid.';
|
||||
}
|
||||
|
||||
$passwordError = store_auth_password_error($password);
|
||||
if ($passwordError !== '') {
|
||||
$errors['password'] = $passwordError;
|
||||
}
|
||||
|
||||
if ($confirmPassword === '') {
|
||||
$errors['confirm_password'] = 'Ulangi password untuk konfirmasi.';
|
||||
} elseif ($password !== $confirmPassword) {
|
||||
$errors['confirm_password'] = 'Konfirmasi password belum cocok.';
|
||||
}
|
||||
|
||||
if (!isset($errors['email'])) {
|
||||
$exists = db()->prepare('SELECT id FROM customer_users WHERE email = :email LIMIT 1');
|
||||
$exists->bindValue(':email', $form['email']);
|
||||
$exists->execute();
|
||||
if ($exists->fetch()) {
|
||||
$errors['email'] = 'Email sudah terdaftar. Silakan login.';
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Pendaftaran belum berhasil. Periksa kembali data Anda.',
|
||||
'errors' => $errors,
|
||||
'form' => $form,
|
||||
];
|
||||
}
|
||||
|
||||
$stmt = db()->prepare('INSERT INTO customer_users (full_name, email, password_hash, last_login_at) VALUES (:full_name, :email, :password_hash, NOW())');
|
||||
$stmt->bindValue(':full_name', $form['full_name']);
|
||||
$stmt->bindValue(':email', $form['email']);
|
||||
$stmt->bindValue(':password_hash', password_hash($password, PASSWORD_DEFAULT));
|
||||
$stmt->execute();
|
||||
|
||||
$user = [
|
||||
'id' => (int)db()->lastInsertId(),
|
||||
'full_name' => $form['full_name'],
|
||||
'email' => $form['email'],
|
||||
];
|
||||
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
store_set_user_session($user);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Akun berhasil dibuat. Anda sudah login.',
|
||||
'user' => $user,
|
||||
'form' => ['full_name' => '', 'email' => ''],
|
||||
];
|
||||
}
|
||||
|
||||
function store_login_user(array $source): array
|
||||
{
|
||||
store_ensure_schema();
|
||||
|
||||
$form = [
|
||||
'email' => store_lower(trim((string)($source['email'] ?? ''))),
|
||||
];
|
||||
$password = (string)($source['password'] ?? '');
|
||||
|
||||
if (!filter_var($form['email'], FILTER_VALIDATE_EMAIL) || $password === '') {
|
||||
$errors = [];
|
||||
if (!filter_var($form['email'], FILTER_VALIDATE_EMAIL)) {
|
||||
$errors['email'] = 'Masukkan email yang valid.';
|
||||
}
|
||||
if ($password === '') {
|
||||
$errors['password'] = 'Password wajib diisi.';
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Masukkan email dan password yang benar.',
|
||||
'errors' => $errors,
|
||||
'form' => $form,
|
||||
];
|
||||
}
|
||||
|
||||
$stmt = db()->prepare('SELECT id, full_name, email, password_hash FROM customer_users WHERE email = :email LIMIT 1');
|
||||
$stmt->bindValue(':email', $form['email']);
|
||||
$stmt->execute();
|
||||
|
||||
$user = $stmt->fetch();
|
||||
if (!$user || !password_verify($password, (string)($user['password_hash'] ?? ''))) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Email atau password tidak cocok.',
|
||||
'errors' => [],
|
||||
'form' => $form,
|
||||
];
|
||||
}
|
||||
|
||||
if (password_needs_rehash((string)($user['password_hash'] ?? ''), PASSWORD_DEFAULT)) {
|
||||
$rehash = db()->prepare('UPDATE customer_users SET password_hash = :password_hash WHERE id = :id LIMIT 1');
|
||||
$rehash->bindValue(':password_hash', password_hash($password, PASSWORD_DEFAULT));
|
||||
$rehash->bindValue(':id', (int)$user['id'], PDO::PARAM_INT);
|
||||
$rehash->execute();
|
||||
}
|
||||
|
||||
$touch = db()->prepare('UPDATE customer_users SET last_login_at = NOW() WHERE id = :id LIMIT 1');
|
||||
$touch->bindValue(':id', (int)$user['id'], PDO::PARAM_INT);
|
||||
$touch->execute();
|
||||
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
store_set_user_session($user);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Login berhasil. Selamat datang kembali.',
|
||||
'user' => [
|
||||
'id' => (int)$user['id'],
|
||||
'full_name' => (string)$user['full_name'],
|
||||
'email' => (string)$user['email'],
|
||||
],
|
||||
'form' => ['email' => ''],
|
||||
];
|
||||
}
|
||||
|
||||
function store_categories(): array
|
||||
{
|
||||
return [
|
||||
@ -417,9 +624,11 @@ function store_input_class(array $errors, string $field): string
|
||||
|
||||
function store_checkout_defaults(): array
|
||||
{
|
||||
$currentUser = store_current_user();
|
||||
|
||||
return [
|
||||
'customer_name' => '',
|
||||
'email' => '',
|
||||
'customer_name' => (string)($currentUser['full_name'] ?? ''),
|
||||
'email' => (string)($currentUser['email'] ?? ''),
|
||||
'phone' => '',
|
||||
'address' => '',
|
||||
'note' => '',
|
||||
@ -505,6 +714,19 @@ function store_ensure_schema(): void
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
|
||||
);
|
||||
|
||||
db()->exec(
|
||||
"CREATE TABLE IF NOT EXISTS customer_users (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
full_name VARCHAR(120) NOT NULL,
|
||||
email VARCHAR(160) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
last_login_at TIMESTAMP NULL DEFAULT NULL,
|
||||
INDEX idx_customer_users_email (email)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
|
||||
);
|
||||
|
||||
$ready = true;
|
||||
}
|
||||
|
||||
@ -735,6 +957,7 @@ function store_page_start(string $title, string $description = '', array $option
|
||||
}
|
||||
$robots = !empty($options['noindex']) ? '<meta name="robots" content="noindex, nofollow" />' : '';
|
||||
$cartCount = store_cart_count();
|
||||
$currentUser = store_current_user();
|
||||
|
||||
echo '<!doctype html>';
|
||||
echo '<html lang="id">';
|
||||
@ -771,16 +994,23 @@ function store_page_start(string $title, string $description = '', array $option
|
||||
echo ' <div class="container-xxl">';
|
||||
echo ' <a class="navbar-brand brand-mark" href="index.php">';
|
||||
echo ' <span class="brand-mark__title">' . h(store_brand()) . '</span>';
|
||||
echo ' <span class="brand-mark__subtitle">Online bakery store</span>';
|
||||
echo ' <span class="brand-mark__subtitle">Cake • Bread • Pastry</span>';
|
||||
echo ' </a>';
|
||||
echo ' <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#siteNav" aria-controls="siteNav" aria-expanded="false" aria-label="Toggle navigation">';
|
||||
echo ' <span class="navbar-toggler-icon"></span>';
|
||||
echo ' </button>';
|
||||
echo ' <div class="collapse navbar-collapse" id="siteNav">';
|
||||
echo ' <ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">';
|
||||
echo store_nav_link('index.php', 'Katalog', $currentPath);
|
||||
echo store_nav_link('cart.php', 'Keranjang', $currentPath);
|
||||
echo store_nav_link('order_status.php', 'Status Pesanan', $currentPath);
|
||||
echo store_nav_link('index.php', 'Home', $currentPath);
|
||||
echo store_nav_link('index.php#catalog', 'Daftar Kue', $currentPath);
|
||||
echo store_nav_link('index.php#payment-info', 'Info Pembayaran', $currentPath);
|
||||
echo store_nav_link('index.php#contact', 'Kontak Kami', $currentPath);
|
||||
if ($currentUser) {
|
||||
echo store_nav_link('auth.php', 'Akun', $currentPath);
|
||||
echo ' <li class="nav-item"><a class="btn btn-outline-secondary btn-sm" href="logout.php">Logout</a></li>';
|
||||
} else {
|
||||
echo store_nav_link('auth.php', 'Login / Register', $currentPath);
|
||||
}
|
||||
echo ' <li class="nav-item ms-lg-2">';
|
||||
echo ' <a class="btn btn-dark btn-sm btn-cart" href="cart.php">Keranjang <span class="badge rounded-pill text-bg-light ms-2">' . h((string)$cartCount) . '</span></a>';
|
||||
echo ' </li>';
|
||||
@ -815,6 +1045,7 @@ function store_page_start(string $title, string $description = '', array $option
|
||||
function store_page_end(): void
|
||||
{
|
||||
$jsVersion = file_exists(__DIR__ . '/assets/js/main.js') ? (string)filemtime(__DIR__ . '/assets/js/main.js') : (string)time();
|
||||
$currentUser = store_current_user();
|
||||
|
||||
echo ' </div>';
|
||||
echo '</main>';
|
||||
@ -822,11 +1053,15 @@ function store_page_end(): void
|
||||
echo ' <div class="container-xxl d-flex flex-column flex-lg-row justify-content-between gap-3 py-4">';
|
||||
echo ' <div>';
|
||||
echo ' <div class="footer-title">' . h(store_brand()) . '</div>';
|
||||
echo ' <p class="footer-copy">MVP toko online dengan katalog, keranjang, checkout tersimpan, dan pelacakan status pesanan.</p>';
|
||||
echo ' <p class="footer-copy">Tampilan user bakery dengan halaman home, daftar kue, info pembayaran, kontak, login user, dan tracking pesanan.</p>';
|
||||
echo ' </div>';
|
||||
echo ' <div class="footer-links d-flex flex-wrap gap-3">';
|
||||
echo ' <a href="index.php#catalog">Lihat katalog</a>';
|
||||
echo ' <a href="cart.php">Buka keranjang</a>';
|
||||
echo ' <a href="index.php">Home</a>';
|
||||
echo ' <a href="index.php#catalog">Daftar kue</a>';
|
||||
echo ' <a href="index.php#payment-info">Info pembayaran</a>';
|
||||
echo ' <a href="index.php#contact">Kontak kami</a>';
|
||||
echo ' <a href="' . h($currentUser ? 'auth.php' : 'auth.php') . '">' . h($currentUser ? 'Akun saya' : 'Login / Register') . '</a>';
|
||||
echo ' <a href="use_case.php">Use case diagram</a>';
|
||||
echo ' <a href="order_status.php">Lacak pesanan</a>';
|
||||
echo ' </div>';
|
||||
echo ' </div>';
|
||||
|
||||
226
use_case.php
Normal file
226
use_case.php
Normal file
@ -0,0 +1,226 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/store.php';
|
||||
|
||||
$plantumlSource = <<<'PLANTUML'
|
||||
@startuml
|
||||
left to right direction
|
||||
skinparam packageStyle rectangle
|
||||
skinparam shadowing false
|
||||
skinparam actorStyle awesome
|
||||
|
||||
actor "User" as User
|
||||
actor "Admin" as Admin
|
||||
|
||||
rectangle "Sistem Penjualan Kue Online\nSekut Bakery" {
|
||||
usecase "Login User" as UC1
|
||||
usecase "Melihat Produk" as UC2
|
||||
usecase "Memasukkan Produk\nke Dalam Keranjang" as UC3
|
||||
usecase "Checkout Keranjang\nPembelian" as UC4
|
||||
usecase "Mengirim Bukti\nPembayaran" as UC5
|
||||
usecase "Login Admin" as UC6
|
||||
usecase "Mengolah Data\nKategori" as UC7
|
||||
usecase "Mengolah Data\nProduk" as UC8
|
||||
usecase "Mengolah Data\nPembelian" as UC9
|
||||
usecase "Mencetak Laporan\nPembelian" as UC10
|
||||
usecase "Mengelola Data\nPelanggan" as UC11
|
||||
}
|
||||
|
||||
User --> UC1
|
||||
User --> UC2
|
||||
User --> UC3
|
||||
User --> UC4
|
||||
User --> UC5
|
||||
Admin --> UC6
|
||||
Admin --> UC7
|
||||
Admin --> UC8
|
||||
Admin --> UC9
|
||||
Admin --> UC10
|
||||
Admin --> UC11
|
||||
@enduml
|
||||
PLANTUML;
|
||||
|
||||
store_page_start(
|
||||
'Use Case Diagram',
|
||||
'Dokumentasi use case diagram versi pertama untuk Sistem Penjualan Kue Online Sekut Bakery, menampilkan aktor User dan Admin beserta fungsi utamanya.',
|
||||
['noindex' => true]
|
||||
);
|
||||
?>
|
||||
<section class="hero-panel mb-4 mb-lg-5">
|
||||
<div class="row g-4 align-items-center">
|
||||
<div class="col-lg-7">
|
||||
<span class="eyebrow">Dokumentasi Proyek</span>
|
||||
<h1 class="display-title">Use case diagram versi pertama sudah tayang di web.</h1>
|
||||
<p class="lead-copy">
|
||||
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 <code><<include>></code> atau <code><<extend>></code>.
|
||||
</p>
|
||||
<div class="d-flex flex-wrap gap-2 mt-4">
|
||||
<a class="btn btn-dark btn-lg" href="#diagram-use-case">Lihat diagram</a>
|
||||
<a class="btn btn-outline-secondary btn-lg" href="index.php">Kembali ke katalog</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<aside class="documentation-note h-100">
|
||||
<strong>Catatan implementasi</strong>
|
||||
<p>
|
||||
Ini adalah halaman dokumentasi analisis sistem untuk kebutuhan presentasi/laporan.
|
||||
Beberapa use case di diagram merepresentasikan arah pengembangan aplikasi, jadi tidak selalu identik dengan fitur MVP yang sudah live saat ini.
|
||||
</p>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="diagram-use-case" class="section-block pt-0">
|
||||
<div class="section-heading mb-4">
|
||||
<span class="eyebrow">Diagram Utama</span>
|
||||
<h2 class="section-title">Use case diagram Sistem Penjualan Kue Online Sekut Bakery.</h2>
|
||||
<p class="section-copy mb-0">
|
||||
Diagram di bawah mengikuti versi pertama yang paling sederhana: dua aktor utama, satu system boundary,
|
||||
dan relasi asosiasi langsung ke setiap use case inti.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<figure class="diagram-showcase mb-0">
|
||||
<div class="diagram-scroll" aria-label="Diagram use case Sistem Penjualan Kue Online Sekut Bakery">
|
||||
<svg viewBox="0 0 1200 760" role="img" aria-labelledby="useCaseTitle useCaseDesc" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="useCaseTitle">Use case diagram versi pertama Sekut Bakery</title>
|
||||
<desc id="useCaseDesc">Diagram dengan aktor User dan Admin yang terhubung langsung ke sebelas use case utama di dalam system boundary.</desc>
|
||||
|
||||
<rect class="diagram-boundary" x="250" y="48" width="700" height="664" rx="28" />
|
||||
<text class="diagram-title" x="600" y="94" text-anchor="middle">Sistem Penjualan Kue Online</text>
|
||||
<text class="diagram-boundary-label" x="600" y="120" text-anchor="middle">Sekut Bakery</text>
|
||||
|
||||
<circle class="diagram-actor" cx="116" cy="148" r="28" />
|
||||
<line class="diagram-actor" x1="116" y1="176" x2="116" y2="258" />
|
||||
<line class="diagram-actor" x1="72" y1="206" x2="160" y2="206" />
|
||||
<line class="diagram-actor" x1="116" y1="258" x2="76" y2="320" />
|
||||
<line class="diagram-actor" x1="116" y1="258" x2="156" y2="320" />
|
||||
<text class="diagram-actor-label" x="116" y="360" text-anchor="middle">User</text>
|
||||
|
||||
<circle class="diagram-actor" cx="1084" cy="148" r="28" />
|
||||
<line class="diagram-actor" x1="1084" y1="176" x2="1084" y2="258" />
|
||||
<line class="diagram-actor" x1="1040" y1="206" x2="1128" y2="206" />
|
||||
<line class="diagram-actor" x1="1084" y1="258" x2="1044" y2="320" />
|
||||
<line class="diagram-actor" x1="1084" y1="258" x2="1124" y2="320" />
|
||||
<text class="diagram-actor-label" x="1084" y="360" text-anchor="middle">Admin</text>
|
||||
|
||||
<ellipse class="diagram-node" cx="432" cy="170" rx="128" ry="36" />
|
||||
<text class="diagram-node-label" x="432" y="176" text-anchor="middle">Login User</text>
|
||||
|
||||
<ellipse class="diagram-node" cx="432" cy="260" rx="128" ry="36" />
|
||||
<text class="diagram-node-label" x="432" y="254" text-anchor="middle">Melihat Produk</text>
|
||||
<text class="diagram-node-label--small" x="432" y="276" text-anchor="middle">Produk bakery</text>
|
||||
|
||||
<ellipse class="diagram-node" cx="432" cy="350" rx="144" ry="42" />
|
||||
<text class="diagram-node-label" x="432" y="344" text-anchor="middle">Memasukkan Produk</text>
|
||||
<text class="diagram-node-label--small" x="432" y="366" text-anchor="middle">ke Dalam Keranjang</text>
|
||||
|
||||
<ellipse class="diagram-node" cx="432" cy="450" rx="144" ry="42" />
|
||||
<text class="diagram-node-label" x="432" y="444" text-anchor="middle">Checkout Keranjang</text>
|
||||
<text class="diagram-node-label--small" x="432" y="466" text-anchor="middle">Pembelian</text>
|
||||
|
||||
<ellipse class="diagram-node" cx="432" cy="560" rx="144" ry="42" />
|
||||
<text class="diagram-node-label" x="432" y="554" text-anchor="middle">Mengirim Bukti</text>
|
||||
<text class="diagram-node-label--small" x="432" y="576" text-anchor="middle">Pembayaran</text>
|
||||
|
||||
<ellipse class="diagram-node" cx="768" cy="150" rx="128" ry="36" />
|
||||
<text class="diagram-node-label" x="768" y="156" text-anchor="middle">Login Admin</text>
|
||||
|
||||
<ellipse class="diagram-node" cx="768" cy="240" rx="138" ry="42" />
|
||||
<text class="diagram-node-label" x="768" y="234" text-anchor="middle">Mengolah Data</text>
|
||||
<text class="diagram-node-label--small" x="768" y="256" text-anchor="middle">Kategori</text>
|
||||
|
||||
<ellipse class="diagram-node" cx="768" cy="340" rx="138" ry="42" />
|
||||
<text class="diagram-node-label" x="768" y="334" text-anchor="middle">Mengolah Data</text>
|
||||
<text class="diagram-node-label--small" x="768" y="356" text-anchor="middle">Produk</text>
|
||||
|
||||
<ellipse class="diagram-node" cx="768" cy="440" rx="138" ry="42" />
|
||||
<text class="diagram-node-label" x="768" y="434" text-anchor="middle">Mengolah Data</text>
|
||||
<text class="diagram-node-label--small" x="768" y="456" text-anchor="middle">Pembelian</text>
|
||||
|
||||
<ellipse class="diagram-node" cx="768" cy="540" rx="148" ry="42" />
|
||||
<text class="diagram-node-label" x="768" y="534" text-anchor="middle">Mencetak Laporan</text>
|
||||
<text class="diagram-node-label--small" x="768" y="556" text-anchor="middle">Pembelian</text>
|
||||
|
||||
<ellipse class="diagram-node" cx="768" cy="640" rx="152" ry="42" />
|
||||
<text class="diagram-node-label" x="768" y="634" text-anchor="middle">Mengelola Data</text>
|
||||
<text class="diagram-node-label--small" x="768" y="656" text-anchor="middle">Pelanggan</text>
|
||||
|
||||
<line class="diagram-connector" x1="160" y1="202" x2="304" y2="170" />
|
||||
<line class="diagram-connector" x1="160" y1="214" x2="304" y2="260" />
|
||||
<line class="diagram-connector" x1="160" y1="224" x2="288" y2="350" />
|
||||
<line class="diagram-connector" x1="160" y1="234" x2="288" y2="450" />
|
||||
<line class="diagram-connector" x1="160" y1="244" x2="288" y2="560" />
|
||||
|
||||
<line class="diagram-connector" x1="1040" y1="202" x2="896" y2="150" />
|
||||
<line class="diagram-connector" x1="1040" y1="214" x2="906" y2="240" />
|
||||
<line class="diagram-connector" x1="1040" y1="224" x2="906" y2="340" />
|
||||
<line class="diagram-connector" x1="1040" y1="234" x2="906" y2="440" />
|
||||
<line class="diagram-connector" x1="1040" y1="244" x2="916" y2="540" />
|
||||
<line class="diagram-connector" x1="1040" y1="254" x2="920" y2="640" />
|
||||
</svg>
|
||||
</div>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<section class="section-block pt-0">
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-6">
|
||||
<article class="actor-panel">
|
||||
<div class="actor-panel__head">
|
||||
<div>
|
||||
<span class="card-kicker">Aktor 01</span>
|
||||
<h3>User</h3>
|
||||
</div>
|
||||
<span class="actor-badge">User</span>
|
||||
</div>
|
||||
<p>Pelanggan berfokus pada alur pembelian: masuk ke sistem, melihat produk, mengisi keranjang, checkout, lalu mengirim bukti pembayaran.</p>
|
||||
<ul class="usecase-list">
|
||||
<li><span>1</span><div><strong>Login User</strong><br><small>Masuk ke sistem sebelum memulai transaksi.</small></div></li>
|
||||
<li><span>2</span><div><strong>Melihat Produk</strong><br><small>Menjelajahi daftar produk kue yang tersedia.</small></div></li>
|
||||
<li><span>3</span><div><strong>Memasukkan Produk ke Dalam Keranjang</strong><br><small>Menambah item pilihan ke keranjang belanja.</small></div></li>
|
||||
<li><span>4</span><div><strong>Checkout Keranjang Pembelian</strong><br><small>Menyelesaikan transaksi dan membuat pesanan.</small></div></li>
|
||||
<li><span>5</span><div><strong>Mengirim Bukti Pembayaran</strong><br><small>Mengunggah/menyerahkan bukti pembayaran setelah checkout.</small></div></li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<article class="actor-panel">
|
||||
<div class="actor-panel__head">
|
||||
<div>
|
||||
<span class="card-kicker">Aktor 02</span>
|
||||
<h3>Admin</h3>
|
||||
</div>
|
||||
<span class="actor-badge">Admin</span>
|
||||
</div>
|
||||
<p>Admin berfokus pada pengelolaan sistem: login ke dashboard lalu mengatur kategori, produk, pembelian, laporan, dan data pelanggan.</p>
|
||||
<ul class="usecase-list">
|
||||
<li><span>1</span><div><strong>Login Admin</strong><br><small>Mengakses area pengelolaan sistem.</small></div></li>
|
||||
<li><span>2</span><div><strong>Mengolah Data Kategori</strong><br><small>Tambah, ubah, atau hapus kategori produk.</small></div></li>
|
||||
<li><span>3</span><div><strong>Mengolah Data Produk</strong><br><small>Mengelola katalog produk yang dijual.</small></div></li>
|
||||
<li><span>4</span><div><strong>Mengolah Data Pembelian</strong><br><small>Memeriksa dan memproses transaksi pelanggan.</small></div></li>
|
||||
<li><span>5</span><div><strong>Mencetak Laporan Pembelian</strong><br><small>Menyusun laporan transaksi untuk kebutuhan operasional.</small></div></li>
|
||||
<li><span>6</span><div><strong>Mengelola Data Pelanggan</strong><br><small>Memonitor data customer yang tersimpan di sistem.</small></div></li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-block pt-0">
|
||||
<div class="surface-panel">
|
||||
<div class="section-heading mb-4">
|
||||
<span class="eyebrow">PlantUML</span>
|
||||
<h2 class="section-title">Kode sumber diagram versi pertama.</h2>
|
||||
<p class="section-copy mb-0">Kode ini sama dengan versi pertama yang sebelumnya sudah dipilih, jadi bisa langsung disalin untuk laporan atau digenerate ulang di PlantUML.</p>
|
||||
</div>
|
||||
<details class="code-panel" open>
|
||||
<summary>Lihat kode PlantUML</summary>
|
||||
<pre><code><?= h($plantumlSource) ?></code></pre>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
<?php store_page_end(); ?>
|
||||
Loading…
x
Reference in New Issue
Block a user