Files

642 lines
47 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useRef } from 'react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head } from '@inertiajs/react';
const NAV = [
{ id: 'intro', label: 'Pendahuluan', icon: <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg> },
{ id: 'quickstart', label: 'Quick Start', icon: <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg> },
{ id: 'stack', label: 'Tech Stack', icon: <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /></svg> },
{ id: 'auth', label: 'Autentikasi', icon: <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg> },
{ id: 'roles', label: 'Roles & Permission', icon: <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg> },
{ id: 'features', label: 'Fitur Lengkap', icon: <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 10h16M4 14h16M4 18h7" /></svg> },
{ id: 'api', label: 'REST API', icon: <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg> },
{ id: '2fa', label: 'Two-Factor Auth', icon: <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /></svg> },
{ id: 'settings', label: 'System Settings', icon: <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg> },
{ id: 'structure', label: 'Struktur Folder', icon: <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" /></svg> },
];
function Badge({ children, color = 'gray' }: { children: React.ReactNode; color?: string }) {
const colors: Record<string, string> = {
green: 'bg-emerald-50 text-emerald-700 border border-emerald-200',
blue: 'bg-blue-50 text-blue-700 border border-blue-200',
amber: 'bg-amber-50 text-amber-700 border border-amber-200',
red: 'bg-red-50 text-red-700 border border-red-200',
gray: 'bg-gray-50 text-gray-600 border border-gray-200',
purple: 'bg-purple-50 text-purple-700 border border-purple-200',
};
return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-lg text-[10px] font-bold uppercase tracking-widest ${colors[color]}`}>
{children}
</span>
);
}
function CodeBlock({ children, lang = 'bash' }: { children: string; lang?: string }) {
const [copied, setCopied] = useState(false);
const copy = () => {
navigator.clipboard.writeText(children);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="relative group mt-3">
<div className="flex items-center justify-between bg-[#1E2A28] rounded-t-xl px-4 py-2 border-b border-white/5">
<span className="text-[10px] text-gray-400 font-mono font-bold uppercase tracking-widest">{lang}</span>
<button onClick={copy} className="text-[10px] text-gray-400 font-bold hover:text-white transition-colors">
{copied ? '✓ Copied' : 'Copy'}
</button>
</div>
<pre className="bg-[#152320] text-emerald-300 text-xs font-mono p-4 rounded-b-xl overflow-x-auto leading-relaxed">
<code>{children}</code>
</pre>
</div>
);
}
function SectionHeader({ id, title, badge, badgeColor }: { id: string; title: string; badge?: string; badgeColor?: string }) {
return (
<div id={id} className="flex items-center gap-3 mb-6 pt-2 scroll-mt-6">
<h2 className="text-base font-black text-[#3D4E4B] dark:text-white tracking-tight">{title}</h2>
{badge && <Badge color={badgeColor}>{badge}</Badge>}
<div className="flex-1 h-px bg-gray-100 dark:bg-white/10"></div>
</div>
);
}
function Endpoint({ method, path, desc, auth = true }: { method: string; path: string; desc: string; auth?: boolean }) {
const methodColor: Record<string, string> = {
GET: 'bg-blue-100 text-blue-700',
POST: 'bg-emerald-100 text-emerald-700',
PATCH: 'bg-amber-100 text-amber-700',
DELETE: 'bg-red-100 text-red-700',
PUT: 'bg-purple-100 text-purple-700',
};
return (
<div className="flex items-center gap-3 py-3 border-b border-gray-50 last:border-0">
<span className={`shrink-0 px-2.5 py-1 rounded-lg text-[10px] font-black tracking-widest uppercase ${methodColor[method] ?? 'bg-gray-100 text-gray-600'}`}>{method}</span>
<code className="flex-1 text-xs font-mono text-[#3D4E4B] font-bold">{path}</code>
<span className="text-xs text-gray-400 font-medium hidden md:block">{desc}</span>
{auth && <span className="shrink-0 text-[10px] text-amber-600 font-bold">🔒 Auth</span>}
</div>
);
}
export default function DocsIndex() {
const [activeSection, setActiveSection] = useState('intro');
const contentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const ids = NAV.map(n => n.id);
const observer = new IntersectionObserver(
(entries) => {
const visible = entries
.filter(e => e.isIntersecting)
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
if (visible.length > 0) setActiveSection(visible[0].target.id);
},
{ rootMargin: '-20% 0px -70% 0px', threshold: 0 }
);
ids.forEach(id => { const el = document.getElementById(id); if (el) observer.observe(el); });
return () => observer.disconnect();
}, []);
const scrollTo = (id: string) => {
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
};
return (
<AuthenticatedLayout>
<Head title="Dokumentasi biiproject kit v2" />
<div className="flex items-center justify-between mb-8 anim-down">
<div>
<h1 className="text-xl font-bold text-[#3D4E4B] dark:text-white tracking-tight leading-none">Dokumentasi</h1>
<p className="text-sm font-semibold text-gray-400 tracking-tight mt-2">Panduan lengkap biiproject kit v2</p>
</div>
<Badge color="green">v2.0</Badge>
</div>
<div className="flex gap-8 anim-up">
{/* Sticky Sidebar Nav */}
<aside className="hidden lg:block w-52 shrink-0">
<div className="sticky top-6 bg-white dark:bg-[#1A2120] rounded-2xl border border-gray-100 dark:border-white/10 shadow-sm p-3 space-y-0.5">
{NAV.map(s => (
<button key={s.id} onClick={() => scrollTo(s.id)}
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-xs font-bold text-left transition-all ${activeSection === s.id ? 'bg-[#3D4E4B] text-white' : 'text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-white/5 hover:text-[#3D4E4B] dark:hover:text-white'}`}>
<span className={`shrink-0 ${activeSection === s.id ? 'text-[#D4A017]' : ''}`}>{s.icon}</span>
{s.label}
</button>
))}
</div>
</aside>
{/* Content */}
<div ref={contentRef} className="flex-1 min-w-0 space-y-12 pb-20">
{/* ── PENDAHULUAN ── */}
<section>
<SectionHeader id="intro" title="Pendahuluan" badge="Starter Kit" badgeColor="blue" />
<div className="bg-white dark:bg-[#1A2120] rounded-2xl border border-gray-100 dark:border-white/10 shadow-sm p-8 space-y-4">
<p className="text-sm text-gray-600 dark:text-gray-300 font-medium leading-relaxed">
<strong className="text-[#3D4E4B] dark:text-white">biiproject kit v2</strong> adalah starter kit enterprise berbasis <strong>Laravel 13 + React (Inertia.js)</strong> yang dirancang untuk mempercepat pembangunan aplikasi web dengan fitur manajemen pengguna, hak akses berbasis peran, monitoring aktivitas, notifikasi, dan konfigurasi sistem yang lengkap.
</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 pt-2">
{[
{ label: 'Users & Roles', icon: '👥' },
{ label: 'Activity Logs', icon: '📋' },
{ label: 'System Settings', icon: '⚙️' },
{ label: 'Two-Factor Auth', icon: '🔒' },
{ label: 'REST API v1', icon: '🔌' },
{ label: 'Dark Mode', icon: '🌙' },
{ label: 'Notifikasi', icon: '🔔' },
{ label: 'Mobile Ready', icon: '📱' },
].map(f => (
<div key={f.label} className="p-4 bg-gray-50 dark:bg-white/5 rounded-xl text-center">
<div className="text-2xl mb-1">{f.icon}</div>
<div className="text-[11px] font-bold text-[#3D4E4B] dark:text-white">{f.label}</div>
</div>
))}
</div>
</div>
</section>
{/* ── QUICK START ── */}
<section>
<SectionHeader id="quickstart" title="Quick Start" badge="Setup" badgeColor="green" />
<div className="bg-white dark:bg-[#1A2120] rounded-2xl border border-gray-100 dark:border-white/10 shadow-sm p-8 space-y-6">
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-2">1. Clone & Install</h3>
<CodeBlock lang="bash">{`git clone https://github.com/your-org/biiskit.git
cd biiskit
composer install
npm install`}</CodeBlock>
</div>
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-2">2. Konfigurasi Environment</h3>
<CodeBlock lang="bash">{`cp .env.example .env
php artisan key:generate
# Edit .env sesuai konfigurasi database
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=biiskit
DB_USERNAME=your_user
DB_PASSWORD=your_password`}</CodeBlock>
</div>
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-2">3. Migrasi & Seeder</h3>
<CodeBlock lang="bash">{`php artisan migrate --seed`}</CodeBlock>
<p className="text-xs text-gray-400 font-medium mt-2">Seeder akan membuat 3 akun default: <code className="bg-gray-100 dark:bg-white/10 px-1.5 py-0.5 rounded font-mono">superadmin</code>, <code className="bg-gray-100 dark:bg-white/10 px-1.5 py-0.5 rounded font-mono">admin</code>, dan <code className="bg-gray-100 dark:bg-white/10 px-1.5 py-0.5 rounded font-mono">user</code>.</p>
</div>
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-2">4. Jalankan Server</h3>
<CodeBlock lang="bash">{`# Terminal 1 — Laravel
php artisan serve
# Terminal 2 — Vite dev server
npm run dev`}</CodeBlock>
</div>
<div className="p-4 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-700/40 rounded-xl">
<p className="text-xs font-bold text-amber-700 dark:text-amber-400">Akses aplikasi di <code className="font-mono">http://localhost:8000</code> — redirect otomatis ke halaman login.</p>
</div>
</div>
</section>
{/* ── TECH STACK ── */}
<section>
<SectionHeader id="stack" title="Tech Stack" />
<div className="bg-white dark:bg-[#1A2120] rounded-2xl border border-gray-100 dark:border-white/10 shadow-sm p-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{[
{ layer: 'Backend', items: ['Laravel 13', 'PHP 8.3', 'PostgreSQL', 'Laravel Sanctum', 'Spatie Permission', 'Spatie Activity Log'] },
{ layer: 'Frontend', items: ['React 18', 'Inertia.js v2', 'TypeScript', 'Tailwind CSS v4', 'Vite 6', 'Chart.js'] },
{ layer: 'Keamanan', items: ['RBAC (Role-Based Access Control)', 'Two-Factor Auth (TOTP)', 'Sanctum API Tokens', 'Gate::before super-admin bypass', 'Bcrypt Password Hashing'] },
{ layer: 'DevOps', items: ['PostgreSQL + Redis (Docker ready)', 'Laravel Queue (database)', 'Cache: database driver', 'Pest PHP testing suite'] },
].map(s => (
<div key={s.layer} className="p-5 bg-gray-50 dark:bg-white/5 rounded-xl">
<div className="text-xs font-black text-[#D4A017] uppercase tracking-widest mb-3">{s.layer}</div>
<div className="space-y-1.5">
{s.items.map(i => (
<div key={i} className="flex items-center gap-2 text-xs font-semibold text-gray-600 dark:text-gray-300">
<span className="w-1.5 h-1.5 rounded-full bg-[#3D4E4B] dark:bg-[#D4A017] shrink-0"></span>
{i}
</div>
))}
</div>
</div>
))}
</div>
</div>
</section>
{/* ── AUTENTIKASI ── */}
<section>
<SectionHeader id="auth" title="Autentikasi" badge="Web + API" badgeColor="purple" />
<div className="bg-white dark:bg-[#1A2120] rounded-2xl border border-gray-100 dark:border-white/10 shadow-sm p-8 space-y-6">
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-4">Akun Bawaan (Seeder)</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs">
<thead>
<tr className="border-b border-gray-100 dark:border-white/10">
<th className="text-left font-black text-gray-400 uppercase tracking-widest py-2 pr-6">Email</th>
<th className="text-left font-black text-gray-400 uppercase tracking-widest py-2 pr-6">Password</th>
<th className="text-left font-black text-gray-400 uppercase tracking-widest py-2">Role</th>
</tr>
</thead>
<tbody>
{[
{ email: 'superadmin@biiskit.com', pw: 'password', role: 'super-admin', color: 'red' },
{ email: 'admin@biiskit.com', pw: 'password', role: 'admin', color: 'amber' },
{ email: 'user@biiskit.com', pw: 'password', role: 'user', color: 'blue' },
].map(u => (
<tr key={u.email} className="border-b border-gray-50 dark:border-white/5 last:border-0">
<td className="py-3 pr-6 font-mono text-[#3D4E4B] dark:text-white font-bold">{u.email}</td>
<td className="py-3 pr-6 font-mono text-gray-400">{u.pw}</td>
<td className="py-3"><Badge color={u.color}>{u.role}</Badge></td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-3">Alur Login Web</h3>
<div className="flex flex-wrap gap-2 items-center text-xs font-bold text-gray-500">
{['Form Login', '→', 'Auth Check', '→', '2FA Challenge?', '→', 'Email Verified?', '→', 'Dashboard'].map((s, i) => (
<span key={i} className={s === '→' ? 'text-gray-300' : 'px-3 py-1.5 bg-gray-50 dark:bg-white/5 rounded-lg text-[#3D4E4B] dark:text-white'}>{s}</span>
))}
</div>
<p className="text-xs text-gray-400 font-medium mt-2">2FA Challenge hanya muncul jika user telah mengaktifkan Two-Factor Auth di <code className="bg-gray-100 dark:bg-white/10 px-1 py-0.5 rounded font-mono">/settings#2fa</code>.</p>
</div>
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-2">Login via API</h3>
<CodeBlock lang="json">{`POST /api/v1/login
Content-Type: application/json
{
"email": "admin@biiskit.com",
"password": "password"
}
// Response
{
"token": "1|abc123...",
"user": { "id": 1, "email": "admin@biiskit.com", ... }
}`}</CodeBlock>
</div>
</div>
</section>
{/* ── ROLES & PERMISSIONS ── */}
<section>
<SectionHeader id="roles" title="Roles & Permission" badge="Spatie" badgeColor="green" />
<div className="bg-white dark:bg-[#1A2120] rounded-2xl border border-gray-100 dark:border-white/10 shadow-sm p-8 space-y-6">
<p className="text-sm text-gray-500 dark:text-gray-400 font-medium leading-relaxed">
Menggunakan <strong className="text-[#3D4E4B] dark:text-white">spatie/laravel-permission</strong>. Role <Badge color="red">super-admin</Badge> mendapat akses penuh via <code className="text-[11px] bg-gray-100 dark:bg-white/10 px-1.5 py-0.5 rounded font-mono">Gate::before</code> bypass tidak perlu assign permission satu per satu.
</p>
<div className="overflow-x-auto">
<table className="w-full text-xs">
<thead>
<tr className="border-b border-gray-100 dark:border-white/10">
<th className="text-left font-black text-gray-400 uppercase tracking-widest py-2 pr-6">Permission</th>
<th className="text-center font-black text-gray-400 uppercase tracking-widest py-2 px-4">user</th>
<th className="text-center font-black text-gray-400 uppercase tracking-widest py-2 px-4">admin</th>
<th className="text-center font-black text-gray-400 uppercase tracking-widest py-2 px-4">super-admin</th>
</tr>
</thead>
<tbody>
{[
{ perm: 'user.view', u: true, a: true, s: true },
{ perm: 'user.create', u: false, a: true, s: true },
{ perm: 'user.edit', u: false, a: true, s: true },
{ perm: 'user.delete', u: false, a: true, s: true },
{ perm: 'user.restore', u: false, a: true, s: true },
{ perm: 'user.force-delete', u: false, a: false, s: true },
{ perm: 'user.export', u: false, a: true, s: true },
{ perm: 'user.import', u: false, a: true, s: true },
{ perm: 'role.view', u: false, a: true, s: true },
{ perm: 'role.create', u: false, a: true, s: true },
{ perm: 'role.delete', u: false, a: true, s: true },
{ perm: 'role.manage', u: false, a: true, s: true },
{ perm: 'notifications.view', u: false, a: true, s: true },
{ perm: 'notifications.send', u: false, a: true, s: true },
{ perm: 'activity-logs.view', u: false, a: true, s: true },
{ perm: 'activity-logs.delete', u: false, a: false, s: true },
{ perm: 'settings.view', u: false, a: false, s: true },
{ perm: 'settings.edit', u: false, a: false, s: true },
{ perm: 'settings.test-email', u: false, a: false, s: true },
{ perm: 'documentation.view', u: false, a: false, s: true },
].map(row => (
<tr key={row.perm} className="border-b border-gray-50 dark:border-white/5 last:border-0">
<td className="py-3 pr-6 font-mono font-bold text-[#3D4E4B] dark:text-white">{row.perm}</td>
{[row.u, row.a, row.s].map((v, i) => (
<td key={i} className="py-3 text-center px-4">
{v ? <span className="text-emerald-500 font-black text-sm"></span> : <span className="text-gray-200 font-black text-sm"></span>}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-2">Pengecekan Permission di Controller</h3>
<CodeBlock lang="php">{`// Via Policy (model instance)
$this->authorize('update', $user);
// Via Gate string (tanpa model)
$this->authorize('user.delete');
// Via Blade / React (shared props)
// auth.permissions = ['user.view', 'user.create', ...]`}</CodeBlock>
</div>
</div>
</section>
{/* ── FITUR LENGKAP ── */}
<section>
<SectionHeader id="features" title="Fitur Lengkap" />
<div className="bg-white dark:bg-[#1A2120] rounded-2xl border border-gray-100 dark:border-white/10 shadow-sm p-8 space-y-6">
{[
{
title: 'Manajemen Pengguna',
icon: '👥',
items: [
'CRUD lengkap (tambah, edit, hapus, restore)',
'Soft delete dengan arsip & purge permanen',
'Bulk archive / restore / force-delete',
'Filter by status, role, pencarian nama/email',
'Sorting multi-kolom + pagination',
'Export ke Excel (.xlsx)',
'Import user massal via Excel/CSV',
'Assign multi-role per user',
],
},
{
title: 'Roles & Permission Manager',
icon: '🛡️',
items: [
'Kelola role dari UI (tambah / hapus role)',
'Assign/revoke permission per role via toggle',
'Super-admin bypass via Gate::before',
'3 role default: super-admin, admin, user',
],
},
{
title: 'Notifikasi',
icon: '🔔',
items: [
'Kirim notifikasi (email / in-app)',
'Target: all users, role tertentu, atau user spesifik',
'Log pengiriman dengan status (sent/failed)',
'Badge counter di topbar (unread last 7 hari)',
'Pagination history notifikasi',
],
},
{
title: 'Activity Logs',
icon: '📋',
items: [
'Log otomatis via spatie/laravel-activitylog',
'Filter by user, event, tanggal',
'Bulk delete logs',
'Tampilan subject & properties berubah',
],
},
{
title: 'Dashboard',
icon: '📊',
items: [
'Statistik total users, admin, active, inactive',
'Chart pendaftaran user 30 hari terakhir (Chart.js)',
'Tabel aktivitas terbaru',
'Quick actions (tambah user, lihat logs)',
],
},
{
title: 'Account Settings',
icon: '👤',
items: [
'Tab Profile: nama, email, telepon, bio, avatar upload',
'Tab Security & Password: ganti password',
'Tab Two-Factor Auth: aktifkan/nonaktifkan TOTP 2FA',
'Tab Danger Zone: hapus akun permanen',
'Tab aktif persisten saat reload (via URL hash)',
],
},
{
title: 'UI/UX',
icon: '🎨',
items: [
'Dark mode toggle (persisted di localStorage)',
'Sidebar responsif + burger menu mobile',
'Breadcrumb dinamis di topbar',
'Flash messages (success / error)',
'Animasi masuk halaman (anim-down, anim-up, anim-left)',
'Tab state persisten via URL hash (#tab-name)',
'Custom scrollbar',
'Error pages: 403, 404, 500',
],
},
].map(feature => (
<div key={feature.title} className="p-5 bg-gray-50 dark:bg-white/5 rounded-xl">
<div className="flex items-center gap-3 mb-3">
<span className="text-xl">{feature.icon}</span>
<div className="text-sm font-black text-[#3D4E4B] dark:text-white">{feature.title}</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-1.5">
{feature.items.map(item => (
<div key={item} className="flex items-start gap-2 text-xs font-medium text-gray-500 dark:text-gray-400">
<span className="text-emerald-500 font-black mt-0.5 shrink-0"></span>
{item}
</div>
))}
</div>
</div>
))}
</div>
</section>
{/* ── REST API ── */}
<section>
<SectionHeader id="api" title="REST API" badge="v1" badgeColor="blue" />
<div className="bg-white dark:bg-[#1A2120] rounded-2xl border border-gray-100 dark:border-white/10 shadow-sm p-8 space-y-6">
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-2">Base URL</h3>
<CodeBlock lang="http">{'http://your-domain.com/api/v1'}</CodeBlock>
</div>
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-2">Authentication Header</h3>
<CodeBlock lang="http">{'Authorization: Bearer {your-sanctum-token}'}</CodeBlock>
</div>
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-4">Endpoints</h3>
<div className="border border-gray-100 dark:border-white/10 rounded-xl overflow-hidden">
<div className="px-4 py-2.5 bg-gray-50 dark:bg-white/5 border-b border-gray-100 dark:border-white/10">
<span className="text-[10px] font-black text-gray-400 uppercase tracking-widest">Auth</span>
</div>
<div className="px-4">
<Endpoint method="POST" path="/api/v1/login" desc="Dapatkan Sanctum token" auth={false} />
<Endpoint method="GET" path="/api/v1/me" desc="Data user aktif" />
<Endpoint method="POST" path="/api/v1/logout" desc="Revoke token" />
</div>
<div className="px-4 py-2.5 bg-gray-50 dark:bg-white/5 border-y border-gray-100 dark:border-white/10">
<span className="text-[10px] font-black text-gray-400 uppercase tracking-widest">Users</span>
</div>
<div className="px-4">
<Endpoint method="GET" path="/api/v1/users" desc="List semua user" />
<Endpoint method="POST" path="/api/v1/users" desc="Buat user baru" />
<Endpoint method="GET" path="/api/v1/users/{id}" desc="Detail user" />
<Endpoint method="PATCH" path="/api/v1/users/{id}" desc="Update user" />
<Endpoint method="DELETE" path="/api/v1/users/{id}" desc="Hapus user" />
</div>
<div className="px-4 py-2.5 bg-gray-50 dark:bg-white/5 border-y border-gray-100 dark:border-white/10">
<span className="text-[10px] font-black text-gray-400 uppercase tracking-widest">App Config</span>
</div>
<div className="px-4">
<Endpoint method="GET" path="/api/v1/app-config" desc="Konfigurasi aplikasi publik" auth={false} />
</div>
</div>
</div>
</div>
</section>
{/* ── 2FA ── */}
<section>
<SectionHeader id="2fa" title="Two-Factor Authentication" badge="TOTP + EMAIL" badgeColor="amber" />
<div className="bg-white dark:bg-[#1A2120] rounded-2xl border border-gray-100 dark:border-white/10 shadow-sm p-8 space-y-6">
<p className="text-sm text-gray-500 dark:text-gray-400 font-medium leading-relaxed">
Sistem mendukung dual-modality 2FA: <strong className="text-[#3D4E4B] dark:text-white">TOTP (Google Authenticator)</strong> dan <strong className="text-[#3D4E4B] dark:text-white">Email Verification OTP</strong>. Integrasi 2FA dirancang dengan prioritas keamanan ketat dan kontrol administrasi global.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-5 bg-gray-50 dark:bg-white/5 rounded-xl border border-gray-100 dark:border-white/10">
<div className="text-xs font-black text-[#D4A017] uppercase tracking-widest mb-3">📱 Google Authenticator / TOTP</div>
<ul className="space-y-2 text-xs font-medium text-gray-500 dark:text-gray-400 list-disc list-inside">
<li>Protokol standar industri RFC 6238</li>
<li>Dilengkapi 8 Recovery Codes cadangan sekali pakai</li>
<li>Dapat di-scan instan via QR Code</li>
</ul>
</div>
<div className="p-5 bg-gray-50 dark:bg-white/5 rounded-xl border border-gray-100 dark:border-white/10">
<div className="text-xs font-black text-[#D4A017] uppercase tracking-widest mb-3"> Email OTP 2FA</div>
<ul className="space-y-2 text-xs font-medium text-gray-500 dark:text-gray-400 list-disc list-inside">
<li>Pengiriman kode 6-digit dinamis langsung ke email user</li>
<li>Masa berlaku kode terbatas (TTL 10 menit)</li>
<li>Mencegah aktivasi jika SMTP Mailer belum tervalidasi</li>
</ul>
</div>
</div>
<div>
<h3 className="text-xs font-black text-[#3D4E4B] dark:text-white uppercase tracking-widest mb-3">Alur Aktivasi Keamanan 2FA</h3>
<div className="space-y-3">
{[
{ step: '1', title: 'Verifikasi SMTP / Mail Delivery', desc: 'Sebelum mengaktifkan Email 2FA, pastikan SMTP Mailer di System Settings terkonfigurasi dengan benar dan di-tes sukses handshake-nya.' },
{ step: '2', title: 'Pilih Metode di Pengaturan Pengguna', desc: 'Akses halaman Account Settings (/settings) → tab "Two-Factor Auth" untuk memilih metode yang diinginkan.' },
{ step: '3', title: 'Konfirmasi Kode OTP / Autentikator', desc: 'Masukkan kode validasi perdana untuk memastikan setup sinkron dengan sistem sebelum metode di-lock.' },
{ step: '4', title: 'Global Admin Override (Force Disable)', desc: 'Apabila administrator menonaktifkan metode 2FA secara global di System Settings, bypass otomatis akan di-enforce di login screen.' },
].map(s => (
<div key={s.step} className="flex items-start gap-4 p-4 bg-gray-50 dark:bg-white/5 rounded-xl">
<div className="w-7 h-7 rounded-full bg-[#3D4E4B] text-white text-xs font-black flex items-center justify-center shrink-0">{s.step}</div>
<div>
<div className="text-sm font-bold text-[#3D4E4B] dark:text-white">{s.title}</div>
<div className="text-xs text-gray-400 font-medium mt-0.5">{s.desc}</div>
</div>
</div>
))}
</div>
</div>
</div>
</section>
{/* ── SYSTEM SETTINGS ── */}
<section>
<SectionHeader id="settings" title="System Settings" badge="Admin / Super Admin" badgeColor="red" />
<div className="bg-white dark:bg-[#1A2120] rounded-2xl border border-gray-100 dark:border-white/10 shadow-sm p-8 space-y-4">
<p className="text-sm text-gray-500 dark:text-gray-400 font-medium">Dapat diakses oleh pengguna dengan role <Badge color="red">super-admin</Badge> atau memiliki permission <Badge color="blue">settings.view</Badge>. Perubahan konfigurasi membutuhkan permission <Badge color="blue">settings.edit</Badge>. Tersedia di <code className="text-xs bg-gray-100 dark:bg-white/10 px-2 py-0.5 rounded font-mono">/system-settings</code>.</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{[
{ tab: 'General & Branding', items: ['Nama aplikasi', 'Logo upload', 'Teks logo fallback', 'Registrasi publik on/off', 'Verifikasi email on/off', 'Global TOTP 2FA toggle (Aktif/Nonaktif)', 'Global Email 2FA toggle (Aktif/Nonaktif)'] },
{ tab: 'Security & OAuth', items: ['Password minimum panjang', 'Wajib huruf besar/kecil/angka/simbol', 'Google OAuth (Client ID & Secret)', 'GitHub OAuth (Client ID & Secret)'] },
{ tab: 'Email / SMTP', items: ['Host & port SMTP', 'Enkripsi (TLS/SSL)', 'Username & password SMTP', 'From name & address', 'Test kirim email handshake real-time (butuh settings.test-email)'] },
{ tab: 'Mobile App Control', items: ['Versi terbaru & minimum Android', 'URL Play Store', 'Mode maintenance mobile app', 'Pesan maintenance kustom'] },
].map(t => (
<div key={t.tab} className="p-5 bg-gray-50 dark:bg-white/5 rounded-xl">
<div className="text-xs font-black text-[#D4A017] uppercase tracking-widest mb-3">{t.tab}</div>
{t.items.map(i => (
<div key={i} className="flex items-center gap-2 text-xs font-semibold text-gray-500 dark:text-gray-400 mb-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-gray-300 dark:bg-gray-600 shrink-0"></span>
{i}
</div>
))}
</div>
))}
</div>
</div>
</section>
{/* ── STRUKTUR FOLDER ── */}
<section>
<SectionHeader id="structure" title="Struktur Folder" />
<div className="bg-white dark:bg-[#1A2120] rounded-2xl border border-gray-100 dark:border-white/10 shadow-sm p-8 space-y-4">
<CodeBlock lang="text">{`biiskit/
├── app/
│ ├── Http/
│ │ ├── Controllers/ # Web + API controllers
│ │ │ └── Api/V1/ # REST API v1 controllers
│ │ ├── Middleware/
│ │ │ └── HandleInertiaRequests.php # Shared props (auth, settings)
│ │ └── Requests/ # Form request validation
│ ├── Models/
│ │ ├── User.php # SoftDeletes + HasRoles + HasPermissions
│ │ ├── Setting.php # System settings key-value store
│ │ └── NotificationLog.php # Notifikasi log
│ └── Policies/
│ └── UserPolicy.php # Gate policies untuk user CRUD
├── database/
│ ├── migrations/ # PostgreSQL migrations
│ └── seeders/
│ └── DatabaseSeeder.php # Roles, permissions, demo users
├── resources/js/
│ ├── Layouts/
│ │ ├── AuthenticatedLayout.tsx # Wrapper utama + mobile sidebar
│ │ └── components/
│ │ ├── Sidebar.tsx # Nav dengan permission check
│ │ └── Topbar.tsx # Breadcrumb + notif bell + dark mode
│ ├── Pages/
│ │ ├── Auth/ # Login, Register, Password reset
│ │ ├── Dashboard/ # Dashboard dengan chart
│ │ ├── Users/ # Index, Show (CRUD)
│ │ ├── Roles/ # Roles & permission manager
│ │ ├── Notifications/ # Kirim & history notifikasi
│ │ ├── ActivityLogs/ # Log aktivitas
│ │ ├── Settings/ # Account settings (profile, password, 2FA, danger zone)
│ │ ├── TwoFactor/ # 2FA challenge page (login flow, no auth)
│ │ ├── SystemSettings/ # Sistem config (super-admin)
│ │ ├── Docs/ # Halaman dokumentasi ini
│ │ └── Errors/ # 403, 404, 500 pages
│ └── Components/
│ ├── DataTable.tsx # Reusable sortable table
│ └── FlashMessage.tsx # Success/error flash
├── routes/
│ ├── web.php # Web routes (Inertia)
│ └── api.php # API routes (Sanctum)
└── tests/
├── Feature/ # Feature tests (Pest)
└── Unit/ # Unit tests`}</CodeBlock>
</div>
</section>
</div>
</div>
</AuthenticatedLayout>
);
}