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
-
-
+
{/* 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)