diff --git a/bookoomoo-app/.env.example b/bookoomoo-app/.env.example
new file mode 100644
index 0000000..e5bc463
--- /dev/null
+++ b/bookoomoo-app/.env.example
@@ -0,0 +1,2 @@
+VITE_N8N_BASE_URL=https://your-n8n.example
+
diff --git a/bookoomoo-app/index.html b/bookoomoo-app/index.html
new file mode 100644
index 0000000..6ed21d2
--- /dev/null
+++ b/bookoomoo-app/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Bookoomoo
+
+
+
+
+
+
+
diff --git a/bookoomoo-app/package.json b/bookoomoo-app/package.json
new file mode 100644
index 0000000..a17d8cd
--- /dev/null
+++ b/bookoomoo-app/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "bookoomoo-app",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-router-dom": "^6.26.2",
+ "lucide-react": "^0.469.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.3.4",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.4.41",
+ "tailwindcss": "^3.4.14",
+ "vite": "^5.4.8"
+ }
+}
+
diff --git a/bookoomoo-app/postcss.config.js b/bookoomoo-app/postcss.config.js
new file mode 100644
index 0000000..b4a6220
--- /dev/null
+++ b/bookoomoo-app/postcss.config.js
@@ -0,0 +1,7 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
+
diff --git a/bookoomoo-app/src/App.jsx b/bookoomoo-app/src/App.jsx
new file mode 100644
index 0000000..fbf00fc
--- /dev/null
+++ b/bookoomoo-app/src/App.jsx
@@ -0,0 +1,48 @@
+import React from 'react'
+import { Routes, Route, Navigate } from 'react-router-dom'
+import Layout from './components/Layout'
+import DashboardPage from './pages/DashboardPage'
+import CreateStoryPage from './pages/CreateStoryPage'
+import UploadPage from './pages/UploadPage'
+import DownloadsPage from './pages/DownloadsPage'
+import PrintCheckoutPage from './pages/PrintCheckoutPage'
+import OrdersPage from './pages/OrdersPage'
+import StoriesPage from './pages/StoriesPage'
+import DonationsPage from './pages/DonationsPage'
+import TopUpPage from './pages/TopUpPage'
+import LoginPage from './pages/LoginPage'
+import { useAuth } from './lib/auth'
+
+function ProtectedRoute({ children }){
+ const { user } = useAuth()
+ if (!user) return
+ return children
+}
+
+export default function App(){
+ return (
+
+ } />
+
+
+
+ }
+ >
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+
+ )
+}
+
diff --git a/bookoomoo-app/src/components/Layout.jsx b/bookoomoo-app/src/components/Layout.jsx
new file mode 100644
index 0000000..071c4b3
--- /dev/null
+++ b/bookoomoo-app/src/components/Layout.jsx
@@ -0,0 +1,53 @@
+import React from 'react'
+import { Outlet, Link, useNavigate } from 'react-router-dom'
+import { BookOpen, Gift, Home, LogOut, Printer, Search, Upload, Wallet, Download, PlusCircle } from 'lucide-react'
+import { useAuth } from '../lib/auth'
+
+function Shell(){
+ const { logout } = useAuth()
+ const nav = useNavigate()
+ return (
+
+
+
+
+
📚
+
Bookoomoo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default function Layout(){
+ return
+}
+
diff --git a/bookoomoo-app/src/components/StatusBadge.jsx b/bookoomoo-app/src/components/StatusBadge.jsx
new file mode 100644
index 0000000..2ff8d3b
--- /dev/null
+++ b/bookoomoo-app/src/components/StatusBadge.jsx
@@ -0,0 +1,14 @@
+import React from 'react'
+export default function StatusBadge({ status }){
+ const map = {
+ Draft: 'bg-neutral-100 text-neutral-700',
+ 'PDF Ready': 'bg-blue-100 text-blue-700',
+ Printed: 'bg-emerald-100 text-emerald-700',
+ Completed: 'bg-emerald-100 text-emerald-700',
+ 'In Production': 'bg-amber-100 text-amber-700',
+ Delivered: 'bg-purple-100 text-purple-700',
+ }
+ const cls = map[status] || 'bg-neutral-100 text-neutral-700'
+ return {status}
+}
+
diff --git a/bookoomoo-app/src/index.css b/bookoomoo-app/src/index.css
new file mode 100644
index 0000000..3657a67
--- /dev/null
+++ b/bookoomoo-app/src/index.css
@@ -0,0 +1,33 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* Theme tokens */
+:root {
+ --bg: 255 255 255;
+ --surface: 255 255 255;
+ --text: 17 24 39;
+ --primary: 255 115 0; /* orange */
+ --accent: 14 165 233; /* sky */
+ --muted: 100 116 139;
+}
+.dark {
+ --bg: 15 23 42;
+ --surface: 2 6 23;
+ --text: 241 245 249;
+ --primary: 255 140 66;
+ --accent: 56 189 248;
+ --muted: 148 163 184;
+}
+
+@layer base {
+ html { font-family: Inter, system-ui, ui-sans-serif, sans-serif; }
+ body { @apply bg-[rgb(var(--bg))] text-[rgb(var(--text))] antialiased; }
+}
+
+@layer components {
+ .btn { @apply inline-flex items-center justify-center rounded-xl px-4 py-2 font-medium transition; }
+ .btn-primary { @apply btn bg-[rgb(var(--primary))] text-white hover:brightness-110 active:brightness-90; }
+ .card { @apply rounded-2xl border bg-white p-4 shadow-sm; }
+}
+
diff --git a/bookoomoo-app/src/lib/api.js b/bookoomoo-app/src/lib/api.js
new file mode 100644
index 0000000..dc87179
--- /dev/null
+++ b/bookoomoo-app/src/lib/api.js
@@ -0,0 +1,25 @@
+export const API = {
+ BASE_URL: import.meta.env.VITE_N8N_BASE_URL || '',
+
+ async get(path){
+ const res = await fetch(`${this.BASE_URL}${path}`)
+ if(!res.ok) throw new Error('Request failed')
+ return res.json()
+ },
+ async post(path, body){
+ const res = await fetch(`${this.BASE_URL}${path}`,{ method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) })
+ if(!res.ok) throw new Error('Request failed')
+ return res.json()
+ },
+
+ me(){ return this.get('/webhook/user/me') },
+ dashboard(){ return this.get('/webhook/dashboard/summary') },
+ listStories(){ return this.get('/webhook/stories/list') },
+ listOrders(){ return this.get('/webhook/orders/list') },
+ listDonations(){ return this.get('/webhook/donations/list') },
+ topupToken(data){ return this.post('/webhook/token/topup', data) },
+ genStory(payload){ return this.post('/webhook/story/generate', payload) },
+ genPDF(payload){ return this.post('/webhook/pdf/generate', payload) },
+ createPrintOrder(payload){ return this.post('/webhook/print/order', payload) },
+}
+
diff --git a/bookoomoo-app/src/lib/auth.jsx b/bookoomoo-app/src/lib/auth.jsx
new file mode 100644
index 0000000..0b7e910
--- /dev/null
+++ b/bookoomoo-app/src/lib/auth.jsx
@@ -0,0 +1,16 @@
+import React, { createContext, useContext, useEffect, useState } from 'react'
+
+const AuthCtx = createContext(null)
+
+export function AuthProvider({ children }){
+ const [user, setUser] = useState(null)
+ useEffect(()=>{
+ const saved = localStorage.getItem('bookoomoo:user')
+ if(saved) setUser(JSON.parse(saved))
+ },[])
+ const login = (payload)=>{ setUser(payload); localStorage.setItem('bookoomoo:user', JSON.stringify(payload)) }
+ const logout = ()=>{ setUser(null); localStorage.removeItem('bookoomoo:user') }
+ return {children}
+}
+export function useAuth(){ return useContext(AuthCtx) }
+
diff --git a/bookoomoo-app/src/main.jsx b/bookoomoo-app/src/main.jsx
new file mode 100644
index 0000000..4a30123
--- /dev/null
+++ b/bookoomoo-app/src/main.jsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import { BrowserRouter } from 'react-router-dom'
+import App from './App'
+import './index.css'
+import { AuthProvider } from './lib/auth'
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+
+
+
+
+
+)
+
diff --git a/bookoomoo-app/src/pages/CreateStoryPage.jsx b/bookoomoo-app/src/pages/CreateStoryPage.jsx
new file mode 100644
index 0000000..fbdbe04
--- /dev/null
+++ b/bookoomoo-app/src/pages/CreateStoryPage.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+export default function CreateStoryPage(){
+ return (
+
+
Buat Cerita
+
Form pembuatan cerita akan ditempatkan di sini.
+
+ )
+}
+
diff --git a/bookoomoo-app/src/pages/DashboardPage.jsx b/bookoomoo-app/src/pages/DashboardPage.jsx
new file mode 100644
index 0000000..cdd7e0d
--- /dev/null
+++ b/bookoomoo-app/src/pages/DashboardPage.jsx
@@ -0,0 +1,154 @@
+import React, { useEffect, useState } from 'react'
+import StatusBadge from '../components/StatusBadge'
+import { API } from '../lib/api'
+import { ArrowRight, BookOpen, Gift, Printer, Wallet, Download, PlusCircle, Upload } from 'lucide-react'
+
+function Card({ children }){ return {children}
}
+function Panel({ title, actionLabel, actionHref, right, children }){
+ return (
+
+
+
{title}
+
+ {right || null}
+ {actionLabel && actionHref && (
+
{actionLabel}
+ )}
+
+
+ {children}
+
+ )
+}
+function QuickAction({ icon, title, desc, href='#' }){
+ return (
+
+
+
+ )
+}
+
+export default function DashboardPage(){
+ const [loading, setLoading] = useState(true)
+ const [summary, setSummary] = useState({ user:{name:'—', tokenBalance:0}, stories:[], orders:[], donations:[] })
+
+ async function load(){
+ setLoading(true)
+ try{
+ const data = await API.dashboard()
+ setSummary(data)
+ } catch (e) {
+ // fallback demo data
+ setSummary({
+ user: { name: 'Pengguna Demo', tokenBalance: 3 },
+ stories: [
+ { id: 's1', title: 'Petualangan Kucing', lang: 'ID', theme: 'Hewan', createdAt: '2025-08-10', status: 'PDF Ready' },
+ ],
+ orders: [
+ { id: 'o1', type: 'Softcover A5', serial: 'INV-001', createdAt: '2025-08-09', status: 'In Production' },
+ ],
+ donations: [
+ { id: 'd1', beneficiary: 'PA Tunas Bangsa', createdAt: '2025-08-08' },
+ ],
+ })
+ } finally { setLoading(false) }
+ }
+
+ useEffect(()=>{ load() },[])
+ const { user, stories, orders, donations } = summary
+
+ return (
+
+
+
+
{user?.name || 'Halo, Pengguna'}
+
Buat buku cerita personal, unduh PDF, atau cetak fisik dengan program Buy 1 Donate 1.
+
+
+
+
+
+
+ Saldo Token
+ {user?.tokenBalance ?? 0}
+ 1 Token = 1x Generate PDF
+
+
+ Cerita Selesai
+ {stories?.length ?? 0}
+ Total semua waktu
+
+
+
+ {orders?.filter(o=>o.status!=='Completed').length ?? 0}
+ Sedang diproses
+
+
+ Donasi Tersalurkan
+ {donations?.length ?? 0}
+ Buy 1 Donate 1
+
+
+
+
+ } title="Buat Cerita" desc="Masukkan nama anak & pilih tema" href="/create"/>
+ } title="Upload Foto" desc="Opsional untuk personalisasi" href="/upload"/>
+ } title="Unduh PDF" desc="Gunakan token kamu" href="/downloads"/>
+ } title="Cetak Fisik" desc="Buy 1 Donate 1" href="/print"/>
+
+
+
+
+
+ {(stories||[]).slice(0,5).map((s)=>(
+ -
+
+
+
{s.title}
+
{s.lang} • {s.theme} • {s.createdAt}
+
+
+ Buka
+ ))}
+
+
+
+
+
+ {(orders||[]).slice(0,5).map((o)=>(
+ -
+
+
+
{o.type}
+
{o.serial} • {o.createdAt}
+
+
+ ))}
+
+
+
+ {(donations||[]).slice(0,5).map((d)=>(
+ -
+
+
+
{d.beneficiary || 'Penerima'}
+
{d.createdAt}
+
+ ))}
+
+
+
+
+
+ )
+}
+
diff --git a/bookoomoo-app/src/pages/DonationsPage.jsx b/bookoomoo-app/src/pages/DonationsPage.jsx
new file mode 100644
index 0000000..30a4849
--- /dev/null
+++ b/bookoomoo-app/src/pages/DonationsPage.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+export default function DonationsPage(){
+ return (
+
+
Donasi
+
Tracking program Buy 1 Donate 1.
+
+ )
+}
+
diff --git a/bookoomoo-app/src/pages/DownloadsPage.jsx b/bookoomoo-app/src/pages/DownloadsPage.jsx
new file mode 100644
index 0000000..9815898
--- /dev/null
+++ b/bookoomoo-app/src/pages/DownloadsPage.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+export default function DownloadsPage(){
+ return (
+
+
Unduh PDF
+
Daftar PDF tersedia untuk diunduh.
+
+ )
+}
+
diff --git a/bookoomoo-app/src/pages/LoginPage.jsx b/bookoomoo-app/src/pages/LoginPage.jsx
new file mode 100644
index 0000000..f3716a5
--- /dev/null
+++ b/bookoomoo-app/src/pages/LoginPage.jsx
@@ -0,0 +1,28 @@
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { useAuth } from '../lib/auth'
+
+export default function LoginPage(){
+ const { login } = useAuth()
+ const nav = useNavigate()
+ function onSubmit(e){
+ e.preventDefault()
+ const form = new FormData(e.currentTarget)
+ const name = form.get('name') || 'Pengguna'
+ login({ name, tokenBalance: 0 })
+ nav('/')
+ }
+ return (
+
+
+
+ )
+}
+
diff --git a/bookoomoo-app/src/pages/OrdersPage.jsx b/bookoomoo-app/src/pages/OrdersPage.jsx
new file mode 100644
index 0000000..f5946c2
--- /dev/null
+++ b/bookoomoo-app/src/pages/OrdersPage.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+export default function OrdersPage(){
+ return (
+
+
Order
+
Riwayat order dan status produksi.
+
+ )
+}
+
diff --git a/bookoomoo-app/src/pages/PrintCheckoutPage.jsx b/bookoomoo-app/src/pages/PrintCheckoutPage.jsx
new file mode 100644
index 0000000..695b691
--- /dev/null
+++ b/bookoomoo-app/src/pages/PrintCheckoutPage.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+export default function PrintCheckoutPage(){
+ return (
+
+
Cetak Fisik
+
Pilih opsi cetak dan checkout.
+
+ )
+}
+
diff --git a/bookoomoo-app/src/pages/StoriesPage.jsx b/bookoomoo-app/src/pages/StoriesPage.jsx
new file mode 100644
index 0000000..da40a77
--- /dev/null
+++ b/bookoomoo-app/src/pages/StoriesPage.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+export default function StoriesPage(){
+ return (
+
+
Cerita
+
Koleksi cerita kamu.
+
+ )
+}
+
diff --git a/bookoomoo-app/src/pages/TopUpPage.jsx b/bookoomoo-app/src/pages/TopUpPage.jsx
new file mode 100644
index 0000000..bc270e8
--- /dev/null
+++ b/bookoomoo-app/src/pages/TopUpPage.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+export default function TopUpPage(){
+ return (
+
+
Top Up
+
Isi saldo token untuk generate PDF.
+
+ )
+}
+
diff --git a/bookoomoo-app/src/pages/UploadPage.jsx b/bookoomoo-app/src/pages/UploadPage.jsx
new file mode 100644
index 0000000..494e765
--- /dev/null
+++ b/bookoomoo-app/src/pages/UploadPage.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+export default function UploadPage(){
+ return (
+
+
Upload Foto
+
Unggah foto untuk personalisasi cerita.
+
+ )
+}
+
diff --git a/bookoomoo-app/tailwind.config.js b/bookoomoo-app/tailwind.config.js
new file mode 100644
index 0000000..e40a94b
--- /dev/null
+++ b/bookoomoo-app/tailwind.config.js
@@ -0,0 +1,26 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ './index.html',
+ './src/**/*.{js,jsx,ts,tsx}',
+ ],
+ theme: {
+ extend: {
+ colors: {
+ bg: 'rgb(var(--bg))',
+ surface: 'rgb(var(--surface))',
+ text: 'rgb(var(--text))',
+ primary: 'rgb(var(--primary))',
+ accent: 'rgb(var(--accent))',
+ muted: 'rgb(var(--muted))',
+ },
+ fontFamily: {
+ sans: ['Inter', 'system-ui', 'ui-sans-serif', 'sans-serif'],
+ display: ['Poppins', 'Inter', 'ui-sans-serif', 'sans-serif'],
+ },
+ },
+ },
+ darkMode: 'class',
+ plugins: [],
+}
+
diff --git a/bookoomoo-app/vite.config.js b/bookoomoo-app/vite.config.js
new file mode 100644
index 0000000..54164ce
--- /dev/null
+++ b/bookoomoo-app/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+export default defineConfig({
+ plugins: [react()],
+})
+