Compare commits

..

1 Commits

13 changed files with 53 additions and 259 deletions

View File

@@ -236,7 +236,7 @@ model PrestasiDesa {
imageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
@@ -245,7 +245,7 @@ model KategoriPrestasiDesa {
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PrestasiDesa PrestasiDesa[]
}
@@ -263,7 +263,7 @@ model Responden {
kelompokUmurId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
@@ -272,7 +272,7 @@ model JenisKelaminResponden {
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
Responden Responden[]
}
@@ -282,7 +282,7 @@ model PilihanRatingResponden {
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
Responden Responden[]
}
@@ -292,7 +292,7 @@ model UmurResponden {
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
Responden Responden[]
}
@@ -326,7 +326,6 @@ model PosisiOrganisasiPPID {
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
children PosisiOrganisasiPPID[] @relation("Parent")
StrukturOrganisasiPPID StrukturOrganisasiPPID[]
@@ -346,7 +345,6 @@ model PegawaiPPID {
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
posisi PosisiOrganisasiPPID @relation(fields: [posisiId], references: [id])
strukturOrganisasi StrukturPPID[] // Relasi balik
StrukturOrganisasiPPID StrukturOrganisasiPPID[]
@@ -372,7 +370,7 @@ model VisiMisiPPID {
misi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
@@ -383,7 +381,7 @@ model DasarHukumPPID {
content String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
@@ -400,7 +398,7 @@ model ProfilePPID {
imageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
@@ -412,7 +410,7 @@ model DaftarInformasiPublik {
tanggal DateTime @db.Date
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
@@ -433,7 +431,7 @@ model PermohonanInformasiPublik {
caraMemperolehSalinanInformasiId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
@@ -442,7 +440,7 @@ model JenisInformasiDiminta {
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PermohonanInformasiPublik PermohonanInformasiPublik[]
}
@@ -452,7 +450,7 @@ model CaraMemperolehInformasi {
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PermohonanInformasiPublik PermohonanInformasiPublik[]
}
@@ -462,7 +460,7 @@ model CaraMemperolehSalinanInformasi {
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PermohonanInformasiPublik PermohonanInformasiPublik[]
}
@@ -476,7 +474,7 @@ model FormulirPermohonanKeberatan {
alasan String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
@@ -533,7 +531,7 @@ model SejarahDesa {
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
@@ -543,7 +541,7 @@ model VisiMisiDesa {
misi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
@@ -553,7 +551,7 @@ model LambangDesa {
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
@@ -564,7 +562,7 @@ model MaskotDesa {
images ProfileDesaImage[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}

View File

@@ -69,8 +69,8 @@ import { seedProfilPpd } from "./_seeder_list/ppid/profil-ppid/seed_profil_ppd";
(async () => {
// Always run seedAssets to handle new images without duplication
console.log("📂 Checking for new assets to seed...");
await seedAssets();
// console.log("📂 Checking for new assets to seed...");
// await seedAssets();
// // =========== FILE STORAGE ===========

View File

@@ -95,7 +95,7 @@ function Page() {
fz={{ base: 'md', md: 'lg' }}
lh={{ base: 1.4, md: 1.4 }}
>
{perbekel.nama || "I.B. Surya Prabhawa Manuaba, S.H., M.H."}
I.B. Surya Prabhawa Manuaba, S.H., M.H.
</Text>
</Paper>
</Stack>

View File

@@ -354,8 +354,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
borderLeft: `2px solid ${tokens.colors.primary}`,
}),
...(mounted && isChildActive && !isDark && {
backgroundColor: 'rgba(25, 113, 194, 0.1)',
borderLeft: `2px solid ${tokens.colors.primary}`,
backgroundColor: tokens.colors.bg.hover,
}),
}
}}

View File

@@ -1,40 +0,0 @@
import prisma from "@/lib/prisma";
import { requireAuth } from "@/lib/api-auth";
export default async function sejarahDesaFindFirst(request: Request) {
// ✅ Authentication check
const headers = new Headers(request.url);
const authResult = await requireAuth({ headers });
if (!authResult.authenticated) {
return authResult.response;
}
try {
// Get the first active record
const data = await prisma.sejarahDesa.findFirst({
where: {
isActive: true,
deletedAt: null
},
orderBy: { createdAt: 'asc' } // Get the oldest one first
});
if (!data) {
return Response.json({
success: false,
message: "Data tidak ditemukan",
}, {status: 404})
}
return Response.json({
success: true,
data,
}, {status: 200})
} catch (error) {
console.error("Gagal mengambil data sejarah desa:", error)
return Response.json({
success: false,
message: "Terjadi kesalahan saat mengambil data",
}, {status: 500})
}
}

View File

@@ -1,16 +1,11 @@
import Elysia, { t } from "elysia";
import sejarahDesaFindById from "./find-by-id";
import sejarahDesaUpdate from "./update";
import sejarahDesaFindFirst from "./find-first";
const SejarahDesa = new Elysia({
prefix: "/sejarah",
tags: ["Desa/Profile"],
})
.get("/first", async (context) => {
const response = await sejarahDesaFindFirst(new Request(context.request));
return response;
})
.get("/:id", async (context) => {
const response = await sejarahDesaFindById(new Request(context.request));
return response;

View File

@@ -1,14 +1,7 @@
import prisma from "@/lib/prisma";
import { requireAuth } from "@/lib/api-auth";
import { Context } from "elysia";
export default async function sejarahDesaUpdate(context: Context) {
// ✅ Authentication check
const authResult = await requireAuth(context);
if (!authResult.authenticated) {
return authResult.response;
}
try {
const id = context.params?.id as string;
const body = await context.body as {

View File

@@ -10,7 +10,8 @@ import {
SimpleGrid,
Skeleton,
Stack,
Text
Text,
useMantineColorScheme
} from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import { Prisma } from "@prisma/client";
@@ -23,6 +24,8 @@ type ProgramInovasiItem = Prisma.ProgramInovasiGetPayload<{ include: { image: tr
function ModuleItem({ data }: { data: ProgramInovasiItem }) {
const router = useTransitionRouter();
const { colorScheme } = useMantineColorScheme();
const isDark = colorScheme === "dark";
return (
<motion.div whileHover={{ scale: 1.03 }}>
@@ -34,7 +37,7 @@ function ModuleItem({ data }: { data: ProgramInovasiItem }) {
role="button"
tabIndex={0}
className="cursor-pointer transition-all"
bg="white"
bg={isDark ? "dark.6" : "white"}
>
<Center h={160}>
{data.image?.link ? (

View File

@@ -1,5 +1,3 @@
"use client";
import colors from "@/con/colors";
import { Box, Space, Stack } from "@mantine/core";
@@ -7,20 +5,21 @@ import { Navbar } from "@/app/darmasaba/_com/Navbar";
import Footer from "./_com/Footer";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<Stack gap={0} bg={colors.grey[1]}>
<Navbar />
<Space h={{
base: "3.9rem",
md: "2.5rem"
}} />
<Box style={{
overflow: "scroll"
}}>
{children}
</Box>
<Footer />
</Stack>
<Stack gap={0} bg={colors.grey[1]}>
<Navbar />
<Space h={{
base: "3.9rem",
md: "2.5rem"
}} />
<Box style={{
overflow: "scroll"
}}>
{children}
</Box>
<Footer />
</Stack>
)
}

View File

@@ -98,16 +98,16 @@ export default function RootLayout({
<html lang="id" {...mantineHtmlProps}>
<head>
<meta charSet="utf-8" />
<ColorSchemeScript defaultColorScheme="light" />
<ColorSchemeScript />
</head>
<body>
<MantineProvider theme={theme} defaultColorScheme="light">
<MantineProvider theme={theme}>
{children}
<LoadDataFirstClient />
<ToastContainer
position="bottom-center"
hideProgressBar
style={{ zIndex: 9999 }}
<ToastContainer
position="bottom-center"
hideProgressBar
style={{ zIndex: 9999 }}
/>
</MantineProvider>
</body>

View File

@@ -1,84 +0,0 @@
/**
* Authentication helper untuk API endpoints
*
* Usage:
* import { requireAuth } from "@/lib/api-auth";
*
* export default async function myEndpoint(context: Context) {
* const authResult = await requireAuth(context);
* if (!authResult.authenticated) {
* return authResult.response;
* }
* // Lanjut proses dengan authResult.user
* }
*/
import { getSession } from "@/lib/session";
export type AuthResult =
| { authenticated: true; user: any }
| { authenticated: false; response: Response };
export async function requireAuth(context: any): Promise<AuthResult> {
try {
// Cek session dari cookies
const session = await getSession();
if (!session || !session.user) {
return {
authenticated: false,
response: new Response(JSON.stringify({
success: false,
message: "Unauthorized - Silakan login terlebih dahulu"
}), {
status: 401,
headers: { 'Content-Type': 'application/json' }
})
};
}
// Check jika user masih aktif
if (!session.user.isActive) {
return {
authenticated: false,
response: new Response(JSON.stringify({
success: false,
message: "Akun Anda tidak aktif. Hubungi administrator."
}), {
status: 403,
headers: { 'Content-Type': 'application/json' }
})
};
}
return {
authenticated: true,
user: session.user
};
} catch (error) {
console.error("Auth error:", error);
return {
authenticated: false,
response: new Response(JSON.stringify({
success: false,
message: "Authentication error"
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
})
};
}
}
/**
* Optional auth - tidak error jika tidak authenticated
* Berguna untuk endpoint yang bisa diakses public atau private
*/
export async function optionalAuth(context: any): Promise<any> {
try {
const session = await getSession();
return session?.user || null;
} catch (error) {
return null;
}
}

View File

@@ -1,68 +0,0 @@
/**
* Session helper menggunakan iron-session
*
* Usage:
* import { getSession } from "@/lib/session";
*
* const session = await getSession();
* if (session?.user) {
* // User authenticated
* }
*/
import { getIronSession } from 'iron-session';
import { cookies } from 'next/headers';
export type SessionData = {
user?: {
id: string;
name: string;
roleId: number;
menuIds?: string[] | null;
isActive?: boolean;
};
};
export type Session = SessionData & {
save: () => Promise<void>;
destroy: () => Promise<void>;
};
const SESSION_OPTIONS = {
cookieName: 'desa-session',
password: process.env.SESSION_PASSWORD || 'default-password-change-in-production',
cookieOptions: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'lax' as const,
maxAge: 60 * 60 * 24 * 7, // 7 days
},
};
export async function getSession(): Promise<SessionData | null> {
try {
const cookieStore = await cookies();
const session = await getIronSession<SessionData>(
cookieStore,
SESSION_OPTIONS
);
return session;
} catch (error) {
console.error('Session error:', error);
return null;
}
}
export async function destroySession(): Promise<void> {
try {
const cookieStore = await cookies();
const session = await getIronSession<SessionData>(
cookieStore,
SESSION_OPTIONS
);
await session.destroy();
} catch (error) {
console.error('Destroy session error:', error);
}
}

View File

@@ -21,18 +21,17 @@ import { proxy, useSnapshot } from 'valtio';
const STORAGE_KEY = 'darmasaba-admin-dark-mode';
// Initialize from localStorage or default to light mode
// Initialize from localStorage or system preference
const getInitialDarkMode = (): boolean => {
if (typeof window === 'undefined') return false;
const stored = localStorage.getItem(STORAGE_KEY);
if (stored !== null) {
return stored === 'true';
}
// Default to light mode for first-time users
// System preference is NOT used as default to ensure consistent UX
return false;
// Fallback to system preference
return window.matchMedia('(prefers-color-scheme: dark)').matches;
};
class DarkModeStore {