Phase 1 scaffold: auth for all apps

- Backend: Fastify with two listeners (public + internal), routes, services, DB migration + seed
- client_app: Flutter with BLoC, all auth screens (welcome, display name, register, OTP, force-register)
- mitra_app: Flutter with BLoC, OTP-only login
- control_center: React + Vite, email/password login, mitra/user management, anonymity settings
- Docs: phase1 plan, API contract, client app mockup
- CLAUDE.md and shared memory for all subprojects

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-05 10:08:42 +08:00
commit a7a2a32d27
85 changed files with 3953 additions and 0 deletions

View File

@@ -0,0 +1,511 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Client App Mockup — Halo Bestie</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Segoe UI', sans-serif;
background: #f0f0f0;
padding: 40px 20px;
}
h1 {
text-align: center;
margin-bottom: 8px;
color: #333;
}
.subtitle {
text-align: center;
color: #888;
margin-bottom: 40px;
font-size: 14px;
}
.screens {
display: flex;
flex-wrap: wrap;
gap: 32px;
justify-content: center;
}
.screen-label {
text-align: center;
font-size: 13px;
font-weight: 600;
color: #555;
margin-bottom: 10px;
}
/* Android phone frame */
.phone {
width: 320px;
height: 640px;
background: #1a1a1a;
border-radius: 40px;
padding: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.25);
position: relative;
}
.phone::before {
content: '';
display: block;
width: 80px;
height: 6px;
background: #333;
border-radius: 3px;
margin: 0 auto 8px;
}
.phone::after {
content: '';
display: block;
width: 40px;
height: 6px;
background: #333;
border-radius: 3px;
margin: 8px auto 0;
}
.screen {
width: 100%;
height: 560px;
background: #fff;
border-radius: 28px;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Status bar */
.status-bar {
background: #fff;
padding: 6px 16px;
display: flex;
justify-content: space-between;
font-size: 10px;
color: #333;
font-weight: 600;
}
/* App bar */
.app-bar {
background: #fff;
padding: 10px 16px;
display: flex;
align-items: center;
gap: 12px;
border-bottom: 1px solid #f0f0f0;
}
.app-bar .back-btn {
font-size: 18px;
color: #333;
cursor: pointer;
}
.app-bar .title {
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
}
/* Content area */
.content {
flex: 1;
padding: 24px 20px;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Colors */
:root {
--primary: #6C63FF;
--primary-light: #EEF0FF;
--text: #1a1a1a;
--text-light: #888;
--border: #e0e0e0;
--surface: #f8f8f8;
}
/* Components */
.btn-primary {
background: var(--primary);
color: white;
border: none;
border-radius: 12px;
padding: 14px;
font-size: 15px;
font-weight: 600;
width: 100%;
cursor: pointer;
text-align: center;
}
.btn-outline {
background: white;
color: var(--primary);
border: 1.5px solid var(--primary);
border-radius: 12px;
padding: 14px;
font-size: 15px;
font-weight: 600;
width: 100%;
cursor: pointer;
text-align: center;
}
.btn-social {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
border-radius: 12px;
padding: 13px;
font-size: 14px;
font-weight: 500;
width: 100%;
cursor: pointer;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.input-field {
width: 100%;
border: 1.5px solid var(--border);
border-radius: 12px;
padding: 13px 14px;
font-size: 14px;
color: var(--text);
outline: none;
}
.input-label {
font-size: 12px;
font-weight: 600;
color: var(--text-light);
margin-bottom: 6px;
}
.input-group {
display: flex;
flex-direction: column;
margin-bottom: 16px;
}
.divider-or {
display: flex;
align-items: center;
gap: 10px;
margin: 16px 0;
color: var(--text-light);
font-size: 12px;
}
.divider-or::before, .divider-or::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
.big-title {
font-size: 26px;
font-weight: 800;
color: var(--text);
margin-bottom: 6px;
}
.big-subtitle {
font-size: 14px;
color: var(--text-light);
margin-bottom: 36px;
}
.hero-icon {
font-size: 56px;
text-align: center;
margin-bottom: 16px;
}
.tag {
display: inline-block;
background: var(--primary-light);
color: var(--primary);
border-radius: 20px;
padding: 4px 12px;
font-size: 11px;
font-weight: 600;
margin-bottom: 12px;
}
.otp-boxes {
display: flex;
gap: 8px;
justify-content: center;
margin: 20px 0;
}
.otp-box {
width: 42px;
height: 50px;
border: 2px solid var(--border);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
font-weight: 700;
color: var(--primary);
}
.otp-box.active {
border-color: var(--primary);
}
.helper-text {
font-size: 12px;
color: var(--text-light);
text-align: center;
margin-top: 8px;
}
.banner {
background: #FFF3E0;
border: 1px solid #FFB74D;
border-radius: 12px;
padding: 14px;
font-size: 13px;
color: #E65100;
margin-bottom: 20px;
line-height: 1.5;
}
.home-greeting {
font-size: 22px;
font-weight: 700;
color: var(--text);
margin-bottom: 4px;
}
.home-sub {
font-size: 13px;
color: var(--text-light);
margin-bottom: 28px;
}
.home-card {
background: var(--primary-light);
border-radius: 16px;
padding: 20px;
text-align: center;
color: var(--primary);
font-size: 13px;
font-weight: 500;
}
.home-card .icon { font-size: 32px; margin-bottom: 8px; }
.bottom-nav {
display: flex;
border-top: 1px solid var(--border);
padding: 10px 0 6px;
}
.nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
font-size: 10px;
color: var(--text-light);
}
.nav-item.active { color: var(--primary); }
.nav-item .icon { font-size: 20px; }
</style>
</head>
<body>
<h1>Halo Bestie — Client App</h1>
<p class="subtitle">Phase 1 · Android Screen Mockups</p>
<div class="screens">
<!-- 1. Welcome -->
<div>
<div class="screen-label">1. Welcome</div>
<div class="phone">
<div class="screen">
<div class="status-bar"><span>9:41</span><span>● ● ▲</span></div>
<div class="content" style="justify-content: center; align-items: center; text-align: center;">
<div class="hero-icon">💬</div>
<div class="big-title">Halo Bestie</div>
<div class="big-subtitle">Tempat curhat kamu</div>
<div style="width: 100%; margin-top: 8px; display: flex; flex-direction: column; gap: 12px;">
<div class="btn-primary">Lanjut sebagai Tamu</div>
<div class="btn-outline">Daftar / Masuk</div>
</div>
</div>
</div>
</div>
</div>
<!-- 2. Pick Display Name -->
<div>
<div class="screen-label">2. Pick Display Name</div>
<div class="phone">
<div class="screen">
<div class="status-bar"><span>9:41</span><span>● ● ▲</span></div>
<div class="app-bar">
<span class="back-btn"></span>
<span class="title">Siapa namamu?</span>
</div>
<div class="content">
<div class="tag">Tamu</div>
<div style="font-size: 20px; font-weight: 700; color: var(--text); margin-bottom: 8px;">Pilih nama panggilanmu</div>
<div style="font-size: 13px; color: var(--text-light); margin-bottom: 28px; line-height: 1.6;">
Nama ini tidak akan terlihat oleh siapapun selain mitra kamu. Kamu bisa pakai nama samaran.
</div>
<div class="input-group">
<div class="input-label">NAMA PANGGILAN</div>
<input class="input-field" placeholder="contoh: Angin Malam" value="Angin Malam" />
</div>
<div style="margin-top: auto;">
<div class="btn-primary">Lanjut →</div>
</div>
</div>
</div>
</div>
</div>
<!-- 3. Register -->
<div>
<div class="screen-label">3. Daftar / Masuk</div>
<div class="phone">
<div class="screen">
<div class="status-bar"><span>9:41</span><span>● ● ▲</span></div>
<div class="app-bar">
<span class="back-btn"></span>
<span class="title">Masuk / Daftar</span>
</div>
<div class="content">
<div style="font-size: 20px; font-weight: 700; color: var(--text); margin-bottom: 6px;">Selamat datang</div>
<div style="font-size: 13px; color: var(--text-light); margin-bottom: 24px;">Masuk atau buat akun baru</div>
<div class="btn-social" style="margin-bottom: 10px;">
<span>🔵</span> Lanjut dengan Google
</div>
<div class="btn-social">
<span>🍎</span> Lanjut dengan Apple
</div>
<div class="divider-or">atau</div>
<div class="input-group">
<div class="input-label">NOMOR HP</div>
<input class="input-field" placeholder="+628xxxxxxxxxx" />
</div>
<div class="btn-primary">Kirim OTP</div>
</div>
</div>
</div>
</div>
<!-- 4. OTP -->
<div>
<div class="screen-label">4. Verifikasi OTP</div>
<div class="phone">
<div class="screen">
<div class="status-bar"><span>9:41</span><span>● ● ▲</span></div>
<div class="app-bar">
<span class="back-btn"></span>
<span class="title">Masukkan OTP</span>
</div>
<div class="content">
<div style="font-size: 20px; font-weight: 700; color: var(--text); margin-bottom: 8px;">Cek SMS kamu</div>
<div style="font-size: 13px; color: var(--text-light); margin-bottom: 28px; line-height: 1.6;">
Kode OTP telah dikirim ke<br/><strong style="color: var(--text);">+628123456789</strong>
</div>
<div class="otp-boxes">
<div class="otp-box">3</div>
<div class="otp-box">8</div>
<div class="otp-box">4</div>
<div class="otp-box active"></div>
<div class="otp-box"></div>
<div class="otp-box"></div>
</div>
<div class="helper-text">Kirim ulang dalam 00:47</div>
<div style="margin-top: auto;">
<div class="btn-primary">Verifikasi</div>
</div>
</div>
</div>
</div>
</div>
<!-- 5. Force Register Wall -->
<div>
<div class="screen-label">5. Force Register Wall</div>
<div class="phone">
<div class="screen">
<div class="status-bar"><span>9:41</span><span>● ● ▲</span></div>
<div class="app-bar">
<span class="title">Verifikasi Akun</span>
</div>
<div class="content">
<div class="banner">
⚠️ Untuk melanjutkan, kamu perlu mendaftarkan akun. Ini hanya memakan waktu sebentar.
</div>
<div style="font-size: 15px; font-weight: 600; color: var(--text); margin-bottom: 16px;">Pilih cara daftar</div>
<div class="btn-social" style="margin-bottom: 10px;">
<span>🔵</span> Lanjut dengan Google
</div>
<div class="btn-social">
<span>🍎</span> Lanjut dengan Apple
</div>
<div class="divider-or">atau</div>
<div class="input-group">
<div class="input-label">NOMOR HP</div>
<input class="input-field" placeholder="+628xxxxxxxxxx" />
</div>
<div class="btn-primary">Kirim OTP</div>
</div>
</div>
</div>
</div>
<!-- 6. Home (Phase 1 placeholder) -->
<div>
<div class="screen-label">6. Home (placeholder)</div>
<div class="phone">
<div class="screen">
<div class="status-bar"><span>9:41</span><span>● ● ▲</span></div>
<div class="app-bar">
<span class="title" style="flex:1;">Halo Bestie</span>
<span style="font-size: 20px; color: var(--text-light);">🔔</span>
</div>
<div class="content">
<div class="home-greeting">Halo, Angin Malam 👋</div>
<div class="home-sub">Semoga harimu menyenangkan</div>
<div class="home-card">
<div class="icon">💜</div>
<div style="font-size: 15px; font-weight: 700; color: var(--primary); margin-bottom: 6px;">Fitur segera hadir</div>
<div>Sesi curhat dengan mitra profesional akan tersedia di Phase 2.</div>
</div>
</div>
<div class="bottom-nav">
<div class="nav-item active"><span class="icon">🏠</span>Beranda</div>
<div class="nav-item"><span class="icon">💬</span>Sesi</div>
<div class="nav-item"><span class="icon">👤</span>Profil</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>