diff --git a/PromptDashboard.md b/PromptDashboard.md new file mode 100644 index 0000000..ac3edbe --- /dev/null +++ b/PromptDashboard.md @@ -0,0 +1,302 @@ +Buat halaman dashboard admin modern untuk sistem pemerintahan desa bernama **Darmasaba Dashboard NOC**. + +Gunakan stack berikut: + +Frontend: + +* React 19 +* Bun runtime +* Vite +* TailwindCSS +* Mantine UI +* Mantine Charts atau Recharts +* Tabler Icons +* TanStack Router +* Dayjs + +UI harus modular dengan reusable components. + +Gunakan **TailwindCSS sebagai styling utama** dengan warna dari konfigurasi berikut: + +Primary: +darmasaba-navy (#1E3A5F) + +Secondary: +darmasaba-blue (#3B82F6) + +Success: +#22C55E + +Warning: +#FACC15 + +Danger: +#EF4444 + +Background: +#F5F8FB + +Dashboard harus memiliki **Light Mode dan Dark Mode**. + +Dark Mode Color Rules: +background: #0F172A +card: #1E293B +border: #334155 +text: #E2E8F0 + +Card style: + +* rounded-xl +* soft shadow +* padding besar +* border subtle +* smooth hover animation + +Gunakan grid layout responsive. + +--- + +SECTION 1 — PROGRAM KEGIATAN + +Buat 4 card horizontal di bagian atas yang menampilkan kegiatan desa. + +Setiap card memiliki: + +* header biru +* progress bar kegiatan +* tanggal kegiatan +* badge status + +Data card: + +1. + +Judul: Rakor 2025 +Tanggal: 3 Juli 2025 +Progress: 90% +Status: selesai + +2. + +Judul: Pemutakhiran Indeks Desa +Tanggal: 3 Juli 2025 +Progress: 85% +Status: selesai + +3. + +Judul: Mengurus Akta Cerai Warga +Tanggal: 3 Juli 2025 +Progress: 80% +Status: selesai + +4. + +Judul: Pasek 7 Desa Adat +Tanggal: 3 Juli 2025 +Progress: 92% +Status: selesai + +Progress bar: + +* rounded +* warna warning +* animasi smooth + +Status badge: + +* success color + +--- + +SECTION 2 — GRID DASHBOARD + +Layout: + +3 column grid. + +Left column (sidebar style): +Divisi Teraktif + +List item card dengan arrow icon. + +Data: + +Kesejahteraan — 37 kegiatan +Pemerintahan — 26 kegiatan +Keuangan — 17 kegiatan +Sekretaris Desa — 15 kegiatan +Tata Usaha TK — 14 kegiatan +Perangkat Kewilayahan — 12 kegiatan +Pelayanan — 10 kegiatan +Perencanaan — 9 kegiatan +Tata Usaha & Umum — 7 kegiatan + +Setiap item: + +* rounded +* hover effect +* arrow icon kanan + +--- + +Middle column: + +Jumlah Dokumen + +Gunakan **Bar Chart**. + +Kategori: + +* Gambar +* Dokumen + +Nilai: + +* Gambar: 300 +* Dokumen: 310 + +Gunakan: +Recharts atau Mantine Charts. + +--- + +Right column: + +Progres Kegiatan + +Gunakan **Pie Chart**. + +Data: + +Selesai — 83.33% +Dikerjakan — 16.67% +Segera Dikerjakan — 0% +Dibatalkan — 0% + +Legend harus berwarna. + +--- + +SECTION 3 — DISCUSSION PANEL + +Judul: Diskusi + +Tampilkan list diskusi internal staf. + +Item card memiliki: + +* icon chat +* judul pesan +* nama pengirim +* tanggal + +Contoh data: + +"Kepada Pelayanan, mohon di cek..." +Pengirim: I.B Surya Prabhawa Manu +Tanggal: 12 Apr 2025 + +"Kepada staf perencanaan @suar..." +Pengirim: Ni Nyoman Yuliani +Tanggal: 14 Jun 2025 + +"ijin atau mohon kepada KBD sar..." +Pengirim: Ni Wayan Martini +Tanggal: 12 Apr 2025 + +--- + +SECTION 4 — ACARA HARI INI + +Card sederhana. + +Jika tidak ada acara tampilkan: + +"Tidak ada acara hari ini" + +--- + +SECTION 5 — ARSIP DIGITAL PERANGKAT DESA + +Grid 2 column. + +Menu arsip: + +Surat Keputusan +Dokumentasi +Laporan Keuangan +Notulensi Rapat + +Setiap item berupa card clickable dengan: + +* icon dokumen +* border +* hover effect + +--- + +DESIGN STYLE + +Gunakan gaya: + +Modern Government Dashboard +Clean UI +Soft shadow +Rounded-xl +Spacing besar +Minimalistic + +--- + +RESPONSIVE RULES + +Desktop: +12 column grid + +Tablet: +6 column grid + +Mobile: +single column stack + +--- + +COMPONENT STRUCTURE + +src/components/dashboard + +activity-card.tsx +division-list.tsx +document-chart.tsx +progress-chart.tsx +discussion-panel.tsx +event-card.tsx +archive-card.tsx + +src/pages + +dashboard.tsx + +--- + +CODE QUALITY + +Gunakan: + +* React hooks +* reusable components +* Mantine components jika perlu +* Tailwind utility classes +* dark mode support +* responsive layout +* clean TypeScript + +--- + +Output: + +* Halaman dashboard lengkap +* Semua komponen reusable +* Chart sudah bekerja +* Layout identik dengan desain dashboard modern pemerintahan diff --git a/src/components/bumdes-page.tsx b/src/components/bumdes-page.tsx index b518f25..4b7e1b2 100644 --- a/src/components/bumdes-page.tsx +++ b/src/components/bumdes-page.tsx @@ -386,4 +386,4 @@ const BumdesPage = () => { ); }; -export default BumdesPage; \ No newline at end of file +export default BumdesPage; diff --git a/src/components/dashboard-content.tsx b/src/components/dashboard-content.tsx index 319137e..7407098 100644 --- a/src/components/dashboard-content.tsx +++ b/src/components/dashboard-content.tsx @@ -1,4 +1,4 @@ -import { Grid, Stack, useMantineColorScheme } from "@mantine/core"; +import { Grid, Image, Stack, useMantineColorScheme } from "@mantine/core"; import { CheckCircle, FileText, MessageCircle, Users } from "lucide-react"; import { ActivityList } from "./dashboard/activity-list"; import { ChartAPBDes } from "./dashboard/chart-apbdes"; @@ -8,149 +8,26 @@ import { SatisfactionChart } from "./dashboard/satisfaction-chart"; import { SDGSCard } from "./dashboard/sdgs-card"; import { StatCard } from "./dashboard/stat-card"; -// SDGs Icons -function EnergyIcon() { - return ( - - - - ); -} - -function PeaceIcon() { - return ( - - - - - ); -} - -function HealthIcon() { - return ( - - - - ); -} - -function PovertyIcon() { - return ( - - - - - ); -} - -function OceanIcon() { - return ( - - - - - - ); -} - const sdgsData = [ { title: "Desa Berenergi Bersih dan Terbarukan", score: 99.64, - icon: , - color: "#FACC15", - bgColor: "#FEF9C3", + image: "SDGS-7.png", }, { title: "Desa Damai Berkeadilan", score: 78.65, - icon: , - color: "#3B82F6", - bgColor: "#DBEAFE", + image: "SDGS-16.png", }, { title: "Desa Sehat dan Sejahtera", score: 77.37, - icon: , - color: "#22C55E", - bgColor: "#DCFCE7", + image: "SDGS-3.png", }, { title: "Desa Tanpa Kemiskinan", score: 52.62, - icon: , - color: "#EF4444", - bgColor: "#FEE2E2", - }, - { - title: "Desa Peduli Lingkungan Laut", - score: 50.0, - icon: , - color: "#06B6D4", - bgColor: "#CFFAFE", + image: "SDGS-1.png", }, ]; @@ -202,37 +79,35 @@ export function DashboardContent() { {/* Section 2: Chart & Division Progress */} - + - - - - - - {/* Section 3: APBDes Chart */} - - - {/* Section 4 & 5: Activity List & Satisfaction Chart */} - - - - - + + {/* Section 3: APBDes Chart */} + + + + + + + {/* */} + + + + + {/* Section 6: SDGs Desa Cards */} {sdgsData.map((sdg, index) => ( - + } title={sdg.title} score={sdg.score} - icon={sdg.icon} - color={sdg.color} - bgColor={sdg.bgColor} /> ))} diff --git a/src/components/dashboard/sdgs-card.tsx b/src/components/dashboard/sdgs-card.tsx index f9c8431..2732387 100644 --- a/src/components/dashboard/sdgs-card.tsx +++ b/src/components/dashboard/sdgs-card.tsx @@ -4,18 +4,10 @@ import type { ReactNode } from "react"; interface SDGSCardProps { title: string; score: number; - icon: ReactNode; - color: string; - bgColor: string; + image: ReactNode; } -export function SDGSCard({ - title, - score, - icon, - color, - bgColor, -}: SDGSCardProps) { +export function SDGSCard({ title, score, image }: SDGSCardProps) { const { colorScheme } = useMantineColorScheme(); const dark = colorScheme === "dark"; @@ -24,29 +16,28 @@ export function SDGSCard({ p="md" radius="xl" withBorder - bg={bgColor} style={{ - borderColor: dark ? "#334155" : bgColor, + borderColor: dark ? "#334155" : "white", boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)", }} + h="100%" > + {image} - + {title} - + {score.toFixed(2)} - - {icon} - ); diff --git a/src/components/demografi-pekerjaan.tsx b/src/components/demografi-pekerjaan.tsx index 8adffd2..cf189d2 100644 --- a/src/components/demografi-pekerjaan.tsx +++ b/src/components/demografi-pekerjaan.tsx @@ -408,4 +408,4 @@ const DemografiPekerjaan = () => { ); }; -export default DemografiPekerjaan; \ No newline at end of file +export default DemografiPekerjaan; diff --git a/src/components/header.tsx b/src/components/header.tsx index b66f355..cea0dd9 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -118,4 +118,4 @@ export function Header() { ); -} \ No newline at end of file +} diff --git a/src/components/help-page.tsx b/src/components/help-page.tsx index 2e3f2e5..1450a18 100644 --- a/src/components/help-page.tsx +++ b/src/components/help-page.tsx @@ -432,4 +432,4 @@ const HelpPage = () => { ); }; -export default HelpPage; \ No newline at end of file +export default HelpPage; diff --git a/src/components/jenna-analytic.tsx b/src/components/jenna-analytic.tsx index 519fac6..063a56c 100644 --- a/src/components/jenna-analytic.tsx +++ b/src/components/jenna-analytic.tsx @@ -280,4 +280,4 @@ const JennaAnalytic = () => { ); }; -export default JennaAnalytic; \ No newline at end of file +export default JennaAnalytic; diff --git a/src/components/keamanan-page.tsx b/src/components/keamanan-page.tsx index bd58882..b1dd262 100644 --- a/src/components/keamanan-page.tsx +++ b/src/components/keamanan-page.tsx @@ -322,4 +322,4 @@ const KeamananPage = () => { ); }; -export default KeamananPage; \ No newline at end of file +export default KeamananPage; diff --git a/src/components/keuangan-anggaran.tsx b/src/components/keuangan-anggaran.tsx index d271534..47ef4db 100644 --- a/src/components/keuangan-anggaran.tsx +++ b/src/components/keuangan-anggaran.tsx @@ -354,4 +354,4 @@ const KeuanganAnggaran = () => { ); }; -export default KeuanganAnggaran; \ No newline at end of file +export default KeuanganAnggaran; diff --git a/src/components/kinerja-divisi.tsx b/src/components/kinerja-divisi.tsx index 96fd77e..6d11190 100644 --- a/src/components/kinerja-divisi.tsx +++ b/src/components/kinerja-divisi.tsx @@ -1,13 +1,12 @@ import { Grid, Stack } from "@mantine/core"; -import { - ActivityCard, - ArchiveCard, - DiscussionPanel, - DivisionList, - DocumentChart, - EventCard, - ProgressChart, -} from "."; +import { ActivityCard, } from "./kinerja-divisi/activity-card"; +import { DivisionList } from "./kinerja-divisi/division-list"; +import { DocumentChart } from "./kinerja-divisi/document-chart"; +import { ProgressChart } from "./kinerja-divisi/progress-chart"; +import { DiscussionPanel } from "./kinerja-divisi/discussion-panel"; +import { EventCard } from "./kinerja-divisi/event-card"; +import { ArchiveCard } from "./kinerja-divisi/archive-card"; + // Data for program kegiatan (Section 1) const programKegiatanData = [ diff --git a/src/components/pengaduan-layanan-publik.tsx b/src/components/pengaduan-layanan-publik.tsx index 58787ae..cafff0d 100644 --- a/src/components/pengaduan-layanan-publik.tsx +++ b/src/components/pengaduan-layanan-publik.tsx @@ -840,4 +840,4 @@ const PengaduanLayananPublik = () => { ); }; -export default PengaduanLayananPublik; \ No newline at end of file +export default PengaduanLayananPublik; diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index ee25c14..6d79647 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -3,6 +3,7 @@ import { Box, Collapse, Group, + Image, Input, NavLink as MantineNavLink, Stack, @@ -60,30 +61,7 @@ export function Sidebar({ className }: SidebarProps) { return ( {/* Logo */} - - - - DESA - - - + - - - - Digitalisasi Desa Transparansi Kerja - - + Logo {/* Search */} @@ -204,4 +182,4 @@ export function Sidebar({ className }: SidebarProps) { ); -} \ No newline at end of file +} diff --git a/src/components/sosial-page.tsx b/src/components/sosial-page.tsx index ef448a7..c313fac 100644 --- a/src/components/sosial-page.tsx +++ b/src/components/sosial-page.tsx @@ -462,4 +462,4 @@ const SosialPage = () => { ); }; -export default SosialPage; \ No newline at end of file +export default SosialPage; diff --git a/src/index.css b/src/index.css index a461c50..f1d8c73 100644 --- a/src/index.css +++ b/src/index.css @@ -1 +1 @@ -@import "tailwindcss"; \ No newline at end of file +@import "tailwindcss"; diff --git a/src/index.ts b/src/index.ts index 746d140..13e163c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,240 +12,240 @@ const isProduction = process.env.NODE_ENV === "production"; // Auto-seed database in production (ensure admin user exists) if (isProduction && process.env.ADMIN_EMAIL) { - try { - console.log("🌱 Running database seed in production..."); - const { runSeed } = await import("../prisma/seed.ts"); - await runSeed(); - } catch (error) { - console.error("⚠️ Production seed failed:", error); - // Don't crash the server if seed fails - } + try { + console.log("🌱 Running database seed in production..."); + const { runSeed } = await import("../prisma/seed.ts"); + await runSeed(); + } catch (error) { + console.error("⚠️ Production seed failed:", error); + // Don't crash the server if seed fails + } } const app = new Elysia().use(api); if (!isProduction) { - // Development: Use Vite middleware - const { createVite } = await import("./vite"); - const vite = await createVite(); + // Development: Use Vite middleware + const { createVite } = await import("./vite"); + const vite = await createVite(); - // Serve PWA/TWA assets in dev (root and nested path support) - const _servePwaAsset = (srcPath: string) => () => Bun.file(srcPath); + // Serve PWA/TWA assets in dev (root and nested path support) + const _servePwaAsset = (srcPath: string) => () => Bun.file(srcPath); - app.post("/__open-in-editor", ({ body }) => { - const { relativePath, lineNumber, columnNumber } = body as { - relativePath: string; - lineNumber: number; - columnNumber: number; - }; + app.post("/__open-in-editor", ({ body }) => { + const { relativePath, lineNumber, columnNumber } = body as { + relativePath: string; + lineNumber: number; + columnNumber: number; + }; - openInEditor(relativePath, { - line: lineNumber, - column: columnNumber, - editor: "antigravity", - }); + openInEditor(relativePath, { + line: lineNumber, + column: columnNumber, + editor: "antigravity", + }); - return { ok: true }; - }); + return { ok: true }; + }); - // Vite middleware for other requests - app.all("*", async ({ request }) => { - const url = new URL(request.url); - const pathname = url.pathname; + // Vite middleware for other requests + app.all("*", async ({ request }) => { + const url = new URL(request.url); + const pathname = url.pathname; - // Serve transformed index.html for root or any path that should be handled by the SPA - if ( - pathname === "/" || - (!pathname.includes(".") && - !pathname.startsWith("/@") && - !pathname.startsWith("/inspector") && - !pathname.startsWith("/__open-stack-frame-in-editor") && - !pathname.startsWith("/api")) - ) { - try { - const htmlPath = path.resolve("src/index.html"); - let html = fs.readFileSync(htmlPath, "utf-8"); - html = await vite.transformIndexHtml(pathname, html); + // Serve transformed index.html for root or any path that should be handled by the SPA + if ( + pathname === "/" || + (!pathname.includes(".") && + !pathname.startsWith("/@") && + !pathname.startsWith("/inspector") && + !pathname.startsWith("/__open-stack-frame-in-editor") && + !pathname.startsWith("/api")) + ) { + try { + const htmlPath = path.resolve("src/index.html"); + let html = fs.readFileSync(htmlPath, "utf-8"); + html = await vite.transformIndexHtml(pathname, html); - return new Response(html, { - headers: { "Content-Type": "text/html" }, - }); - } catch (e) { - console.error(e); - } - } + return new Response(html, { + headers: { "Content-Type": "text/html" }, + }); + } catch (e) { + console.error(e); + } + } - return new Promise((resolve) => { - // Use a Proxy to mock Node.js req because Bun's Request is read-only - const req = new Proxy(request, { - get(target, prop) { - if (prop === "url") return pathname + url.search; - if (prop === "method") return request.method; - if (prop === "headers") - return Object.fromEntries(request.headers as any); - return (target as any)[prop]; - }, - }) as any; + return new Promise((resolve) => { + // Use a Proxy to mock Node.js req because Bun's Request is read-only + const req = new Proxy(request, { + get(target, prop) { + if (prop === "url") return pathname + url.search; + if (prop === "method") return request.method; + if (prop === "headers") + return Object.fromEntries(request.headers as any); + return (target as any)[prop]; + }, + }) as any; - const res = { - statusCode: 200, - setHeader(name: string, value: string) { - this.headers[name.toLowerCase()] = value; - }, - getHeader(name: string) { - return this.headers[name.toLowerCase()]; - }, - writeHead(code: number, headers: Record) { - this.statusCode = code; - Object.assign(this.headers, headers); - }, - write(chunk: any, callback?: () => void) { - // Collect chunks for streaming responses - if (!this._chunks) this._chunks = []; - this._chunks.push(chunk); - if (callback) callback(); - return true; // Indicate we can accept more data - }, - headers: {} as Record, - end(data: any) { - // Handle potential Buffer or string data from Vite - let body = data; - // If we have collected chunks from write() calls, combine them - if (this._chunks && this._chunks.length > 0) { - body = Buffer.concat(this._chunks); - } - if (data instanceof Uint8Array) { - body = data; - } else if (typeof data === "string") { - body = data; - } else if (data) { - body = String(data); - } + const res = { + statusCode: 200, + setHeader(name: string, value: string) { + this.headers[name.toLowerCase()] = value; + }, + getHeader(name: string) { + return this.headers[name.toLowerCase()]; + }, + writeHead(code: number, headers: Record) { + this.statusCode = code; + Object.assign(this.headers, headers); + }, + write(chunk: any, callback?: () => void) { + // Collect chunks for streaming responses + if (!this._chunks) this._chunks = []; + this._chunks.push(chunk); + if (callback) callback(); + return true; // Indicate we can accept more data + }, + headers: {} as Record, + end(data: any) { + // Handle potential Buffer or string data from Vite + let body = data; + // If we have collected chunks from write() calls, combine them + if (this._chunks && this._chunks.length > 0) { + body = Buffer.concat(this._chunks); + } + if (data instanceof Uint8Array) { + body = data; + } else if (typeof data === "string") { + body = data; + } else if (data) { + body = String(data); + } - resolve( - new Response(body || "", { - status: this.statusCode, - headers: this.headers, - }), - ); - }, - // Minimal event emitter mock - once() { - return this; - }, - on() { - return this; - }, - emit() { - return this; - }, - removeListener() { - return this; - }, - } as any; + resolve( + new Response(body || "", { + status: this.statusCode, + headers: this.headers, + }), + ); + }, + // Minimal event emitter mock + once() { + return this; + }, + on() { + return this; + }, + emit() { + return this; + }, + removeListener() { + return this; + }, + } as any; - vite.middlewares(req, res, (err: any) => { - if (err) { - console.error("Vite middleware error:", err); - resolve(new Response(err.stack || err.toString(), { status: 500 })); - return; - } - // If Vite doesn't handle it, return 404 - resolve(new Response("Not Found", { status: 404 })); - }); - }); - }); + vite.middlewares(req, res, (err: any) => { + if (err) { + console.error("Vite middleware error:", err); + resolve(new Response(err.stack || err.toString(), { status: 500 })); + return; + } + // If Vite doesn't handle it, return 404 + resolve(new Response("Not Found", { status: 404 })); + }); + }); + }); } else { - // Production: Final catch-all for static files and SPA fallback - app.all("*", async ({ request }) => { - const url = new URL(request.url); - const pathname = url.pathname; + // Production: Final catch-all for static files and SPA fallback + app.all("*", async ({ request }) => { + const url = new URL(request.url); + const pathname = url.pathname; - // 1. Try exact match in dist - let filePath = path.join( - "dist", - pathname === "/" ? "index.html" : pathname, - ); + // 1. Try exact match in dist + let filePath = path.join( + "dist", + pathname === "/" ? "index.html" : pathname, + ); - // 1.1 Special handling for PWA/TWA assets that might not be in dist (since we use custom bun build) - if (isProduction) { - const srcPath = path.join("src", pathname); - if (fs.existsSync(srcPath)) { - filePath = srcPath; - } - // Check public folder for static assets - const publicPath = path.join("public", pathname); - if (fs.existsSync(publicPath)) { - filePath = publicPath; - } - } + // 1.1 Special handling for PWA/TWA assets that might not be in dist (since we use custom bun build) + if (isProduction) { + const srcPath = path.join("src", pathname); + if (fs.existsSync(srcPath)) { + filePath = srcPath; + } + // Check public folder for static assets + const publicPath = path.join("public", pathname); + if (fs.existsSync(publicPath)) { + filePath = publicPath; + } + } - // 2. If not found and looks like an asset (has extension), try root of dist or src - if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { - if (pathname.includes(".") && !pathname.endsWith("/")) { - const filename = path.basename(pathname); + // 2. If not found and looks like an asset (has extension), try root of dist or src + if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { + if (pathname.includes(".") && !pathname.endsWith("/")) { + const filename = path.basename(pathname); - // Try root of dist - const fallbackDistPath = path.join("dist", filename); - if ( - fs.existsSync(fallbackDistPath) && - fs.statSync(fallbackDistPath).isFile() - ) { - filePath = fallbackDistPath; - } - // Try public folder - else { - const fallbackPublicPath = path.join("public", filename); - if ( - fs.existsSync(fallbackPublicPath) && - fs.statSync(fallbackPublicPath).isFile() - ) { - filePath = fallbackPublicPath; - } - } - // Special handling for PWA files in src - if (pathname.includes("assetlinks.json")) { - const srcFilename = pathname.includes("assetlinks.json") - ? ".well-known/assetlinks.json" - : filename; - const fallbackSrcPath = path.join("src", srcFilename); - if ( - fs.existsSync(fallbackSrcPath) && - fs.statSync(fallbackSrcPath).isFile() - ) { - filePath = fallbackSrcPath; - } - } - } - } + // Try root of dist + const fallbackDistPath = path.join("dist", filename); + if ( + fs.existsSync(fallbackDistPath) && + fs.statSync(fallbackDistPath).isFile() + ) { + filePath = fallbackDistPath; + } + // Try public folder + else { + const fallbackPublicPath = path.join("public", filename); + if ( + fs.existsSync(fallbackPublicPath) && + fs.statSync(fallbackPublicPath).isFile() + ) { + filePath = fallbackPublicPath; + } + } + // Special handling for PWA files in src + if (pathname.includes("assetlinks.json")) { + const srcFilename = pathname.includes("assetlinks.json") + ? ".well-known/assetlinks.json" + : filename; + const fallbackSrcPath = path.join("src", srcFilename); + if ( + fs.existsSync(fallbackSrcPath) && + fs.statSync(fallbackSrcPath).isFile() + ) { + filePath = fallbackSrcPath; + } + } + } + } - if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { - const file = Bun.file(filePath); - return new Response(file, { - headers: { - Vary: "Accept-Encoding", - }, - }); - } + if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { + const file = Bun.file(filePath); + return new Response(file, { + headers: { + Vary: "Accept-Encoding", + }, + }); + } - // 3. SPA Fallback: Serve index.html - const indexHtml = path.join("dist", "index.html"); - if (fs.existsSync(indexHtml)) { - return new Response(Bun.file(indexHtml), { - headers: { - Vary: "Accept-Encoding", - }, - }); - } + // 3. SPA Fallback: Serve index.html + const indexHtml = path.join("dist", "index.html"); + if (fs.existsSync(indexHtml)) { + return new Response(Bun.file(indexHtml), { + headers: { + Vary: "Accept-Encoding", + }, + }); + } - return new Response("Not Found", { status: 404 }); - }); + return new Response("Not Found", { status: 404 }); + }); } app.listen(PORT); console.log( - `🚀 Server running at http://localhost:${PORT} in ${isProduction ? "production" : "development"} mode`, + `🚀 Server running at http://localhost:${PORT} in ${isProduction ? "production" : "development"} mode`, ); export type ApiApp = typeof app; diff --git a/src/middleware/authMiddleware.tsx b/src/middleware/authMiddleware.tsx index 3f19767..08e9bd0 100644 --- a/src/middleware/authMiddleware.tsx +++ b/src/middleware/authMiddleware.tsx @@ -152,4 +152,4 @@ export function createProtectedRoute(options: ProtectedRouteOptions = {}) { * Default Middleware Export * ================================ */ -export const protectedRouteMiddleware = createProtectedRoute(); \ No newline at end of file +export const protectedRouteMiddleware = createProtectedRoute(); diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index d55fd41..e536635 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -28,4 +28,4 @@ export const Route = createRootRoute({ function RootComponent() { return ; -} \ No newline at end of file +} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index f3ca704..ad6162a 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -48,4 +48,4 @@ function DashboardPage() { ); -} \ No newline at end of file +} diff --git a/src/routes/pengaturan/route.tsx b/src/routes/pengaturan/route.tsx index d82dfdb..a6e59c5 100644 --- a/src/routes/pengaturan/route.tsx +++ b/src/routes/pengaturan/route.tsx @@ -1,11 +1,10 @@ -import { - AppShell, - Burger, - Group, - useMantineColorScheme, -} from "@mantine/core"; +import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core"; import { useDisclosure, useMediaQuery } from "@mantine/hooks"; -import { createFileRoute, Outlet, useRouterState } from "@tanstack/react-router"; +import { + createFileRoute, + Outlet, + useRouterState, +} from "@tanstack/react-router"; import { useEffect } from "react"; import { Header } from "@/components/header"; import { Sidebar } from "@/components/sidebar"; @@ -44,12 +43,7 @@ function PengaturanLayout() { > - +
diff --git a/src/utils/env.ts b/src/utils/env.ts index d853093..fe539a6 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -21,7 +21,7 @@ export const getEnv = (key: string, defaultValue = ""): string => { }; export const VITE_PUBLIC_URL = (() => { - // Priority: + // Priority: // 1. BETTER_AUTH_URL (standard for better-auth) // 2. VITE_PUBLIC_URL (our app standard) // 3. window.location.origin (browser fallback)