Compare commits

..

5 Commits

Author SHA1 Message Date
223b85a714 Fix CORS config for staging environment
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-02-25 22:55:36 +08:00
f1729151b3 Fix themeTokens light mode status colors
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-02-25 21:24:39 +08:00
8e8c133eea Fix eror build 2026-02-25 21:19:56 +08:00
1e7acac193 Fix eror build 2026-02-25 21:18:26 +08:00
42dcbcfb22 fix-admin-menu-desa-berita 2026-02-25 16:25:59 +08:00
18 changed files with 340 additions and 73 deletions

View File

@@ -26,7 +26,24 @@ export async function seedBerita() {
console.log("🔄 Seeding Berita..."); console.log("🔄 Seeding Berita...");
// Build a map of valid kategori IDs
const validKategoriIds = new Set<string>();
const kategoriList = await prisma.kategoriBerita.findMany({
select: { id: true, name: true },
});
kategoriList.forEach((k) => validKategoriIds.add(k.id));
console.log(`📋 Found ${validKategoriIds.size} valid kategori IDs in database`);
for (const b of beritaJson) { for (const b of beritaJson) {
// Validate kategoriBeritaId exists
if (!b.kategoriBeritaId || !validKategoriIds.has(b.kategoriBeritaId)) {
console.warn(
`⚠️ Skipping berita "${b.judul}": Invalid kategoriBeritaId "${b.kategoriBeritaId}"`,
);
continue;
}
let imageId: string | null = null; let imageId: string | null = null;
if (b.imageName) { if (b.imageName) {
@@ -44,26 +61,32 @@ export async function seedBerita() {
} }
} }
await prisma.berita.upsert({ try {
where: { id: b.id }, await prisma.berita.upsert({
update: { where: { id: b.id },
judul: b.judul, update: {
deskripsi: b.deskripsi, judul: b.judul,
content: b.content, deskripsi: b.deskripsi,
kategoriBeritaId: b.kategoriBeritaId, content: b.content,
imageId, kategoriBeritaId: b.kategoriBeritaId,
}, imageId,
create: { },
id: b.id, create: {
judul: b.judul, id: b.id,
deskripsi: b.deskripsi, judul: b.judul,
content: b.content, deskripsi: b.deskripsi,
kategoriBeritaId: b.kategoriBeritaId, content: b.content,
imageId, kategoriBeritaId: b.kategoriBeritaId,
}, imageId,
}); },
});
console.log(`✅ Berita seeded: ${b.judul}`); console.log(`✅ Berita seeded: ${b.judul}`);
} catch (error: any) {
console.error(
`❌ Failed to seed berita "${b.judul}": ${error.message}`,
);
}
} }
console.log("🎉 Berita seed selesai"); console.log("🎉 Berita seed selesai");

View File

@@ -0,0 +1,170 @@
/*
Warnings:
- You are about to alter the column `nama` on the `KategoriPotensi` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(100)`.
- You are about to alter the column `name` on the `PotensiDesa` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
- You are about to alter the column `kategoriId` on the `PotensiDesa` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(36)`.
- A unique constraint covering the columns `[nama]` on the table `KategoriPotensi` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[name]` on the table `PotensiDesa` will be added. If there are existing duplicate values, this will fail.
- Made the column `kategoriId` on table `PotensiDesa` required. This step will fail if there are existing NULL values in that column.
*/
-- DropForeignKey
ALTER TABLE "DataPerpustakaan" DROP CONSTRAINT "DataPerpustakaan_imageId_fkey";
-- DropForeignKey
ALTER TABLE "DesaDigital" DROP CONSTRAINT "DesaDigital_imageId_fkey";
-- DropForeignKey
ALTER TABLE "InfoTekno" DROP CONSTRAINT "InfoTekno_imageId_fkey";
-- DropForeignKey
ALTER TABLE "KegiatanDesa" DROP CONSTRAINT "KegiatanDesa_imageId_fkey";
-- DropForeignKey
ALTER TABLE "PengaduanMasyarakat" DROP CONSTRAINT "PengaduanMasyarakat_imageId_fkey";
-- DropForeignKey
ALTER TABLE "PotensiDesa" DROP CONSTRAINT "PotensiDesa_kategoriId_fkey";
-- DropForeignKey
ALTER TABLE "ProfileDesaImage" DROP CONSTRAINT "ProfileDesaImage_imageId_fkey";
-- AlterTable
ALTER TABLE "CaraMemperolehInformasi" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "CaraMemperolehSalinanInformasi" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "DaftarInformasiPublik" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "DasarHukumPPID" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "DataPerpustakaan" ALTER COLUMN "imageId" DROP NOT NULL;
-- AlterTable
ALTER TABLE "DesaDigital" ALTER COLUMN "imageId" DROP NOT NULL;
-- AlterTable
ALTER TABLE "FormulirPermohonanKeberatan" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "InfoTekno" ALTER COLUMN "imageId" DROP NOT NULL;
-- AlterTable
ALTER TABLE "JenisInformasiDiminta" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "JenisKelaminResponden" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "KategoriPotensi" ALTER COLUMN "nama" SET DATA TYPE VARCHAR(100),
ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "KategoriPrestasiDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "KegiatanDesa" ALTER COLUMN "imageId" DROP NOT NULL;
-- AlterTable
ALTER TABLE "LambangDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "MaskotDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "PegawaiPPID" ADD COLUMN "deletedAt" TIMESTAMP(3);
-- AlterTable
ALTER TABLE "PengaduanMasyarakat" ALTER COLUMN "imageId" DROP NOT NULL;
-- AlterTable
ALTER TABLE "PermohonanInformasiPublik" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "PilihanRatingResponden" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "PosisiOrganisasiPPID" ADD COLUMN "deletedAt" TIMESTAMP(3);
-- AlterTable
ALTER TABLE "PotensiDesa" ALTER COLUMN "name" SET DATA TYPE VARCHAR(255),
ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT,
ALTER COLUMN "kategoriId" SET NOT NULL,
ALTER COLUMN "kategoriId" SET DATA TYPE VARCHAR(36);
-- AlterTable
ALTER TABLE "PrestasiDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "ProfileDesaImage" ALTER COLUMN "imageId" DROP NOT NULL;
-- AlterTable
ALTER TABLE "ProfilePPID" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "Responden" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "SejarahDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "UmurResponden" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "VisiMisiDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "VisiMisiPPID" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;
-- CreateIndex
CREATE UNIQUE INDEX "KategoriPotensi_nama_key" ON "KategoriPotensi"("nama");
-- CreateIndex
CREATE UNIQUE INDEX "PotensiDesa_name_key" ON "PotensiDesa"("name");
-- AddForeignKey
ALTER TABLE "ProfileDesaImage" ADD CONSTRAINT "ProfileDesaImage_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PotensiDesa" ADD CONSTRAINT "PotensiDesa_kategoriId_fkey" FOREIGN KEY ("kategoriId") REFERENCES "KategoriPotensi"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DesaDigital" ADD CONSTRAINT "DesaDigital_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InfoTekno" ADD CONSTRAINT "InfoTekno_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PengaduanMasyarakat" ADD CONSTRAINT "PengaduanMasyarakat_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KegiatanDesa" ADD CONSTRAINT "KegiatanDesa_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DataPerpustakaan" ADD CONSTRAINT "DataPerpustakaan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -1,3 +1,3 @@
# Please do not edit this file manually # Please do not edit this file manually
# It should be added in your version-control system (e.g., Git) # It should be added in your version-control system (e.g., Git)
provider = "postgresql" provider = "postgresql"

View File

@@ -160,7 +160,7 @@ function ListKategoriBerita({ search }: { search: string }) {
)) ))
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={4}> <TableTd colSpan={3}> {/* ✅ Match column count (3 columns) */}
<Center py={24}> <Center py={24}>
<Text c="dimmed" fz="sm" lh={1.4}> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data kategori berita yang cocok Tidak ada data kategori berita yang cocok

View File

@@ -187,7 +187,7 @@ function ListBerita({ search }: { search: string }) {
<Pagination <Pagination
value={page} value={page}
onChange={(newPage) => { onChange={(newPage) => {
load(newPage, 10); load(newPage, 10, debouncedSearch); // ✅ Include search parameter
window.scrollTo({ top: 0, behavior: 'smooth' }); window.scrollTo({ top: 0, behavior: 'smooth' });
}} }}
total={totalPages} total={totalPages}

View File

@@ -95,7 +95,7 @@ function Page() {
fz={{ base: 'md', md: 'lg' }} fz={{ base: 'md', md: 'lg' }}
lh={{ base: 1.4, md: 1.4 }} 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> </Text>
</Paper> </Paper>
</Stack> </Stack>

View File

@@ -1,9 +1,9 @@
'use client' 'use client'
import { authStore } from "@/store/authStore";
import { useDarkMode } from "@/state/darkModeStore";
import { themeTokens, getActiveStateStyles } from "@/utils/themeTokens";
import { DarkModeToggle } from "@/components/admin/DarkModeToggle"; import { DarkModeToggle } from "@/components/admin/DarkModeToggle";
import { useDarkMode } from "@/state/darkModeStore";
import { authStore } from "@/store/authStore";
import { themeTokens } from "@/utils/themeTokens";
import { import {
ActionIcon, ActionIcon,
AppShell, AppShell,

View File

@@ -2,15 +2,49 @@ import prisma from "@/lib/prisma";
import { Context } from "elysia"; import { Context } from "elysia";
export default async function kategoriBeritaDelete(context: Context) { export default async function kategoriBeritaDelete(context: Context) {
const id = context.params.id as string; try {
const id = context.params?.id as string;
await prisma.kategoriBerita.delete({ if (!id) {
where: { id }, return Response.json({
}); success: false,
message: "ID tidak boleh kosong",
}, { status: 400 });
}
return { // ✅ Cek apakah kategori masih digunakan oleh berita
status: 200, const beritaCount = await prisma.berita.count({
success: true, where: {
message: "Sukses Menghapus kategori berita", kategoriBeritaId: id,
}; isActive: true,
},
});
if (beritaCount > 0) {
return Response.json({
success: false,
message: `Kategori tidak dapat dihapus karena masih digunakan oleh ${beritaCount} berita`,
}, { status: 400 });
}
// ✅ Soft delete (bukan hard delete)
await prisma.kategoriBerita.update({
where: { id },
data: {
deletedAt: new Date(),
isActive: false,
},
});
return {
success: true,
message: "Kategori berita berhasil dihapus",
};
} catch (error) {
console.error("Delete kategori error:", error);
return Response.json({
success: false,
message: "Gagal menghapus kategori: " + (error instanceof Error ? error.message : 'Unknown error'),
}, { status: 500 });
}
} }

View File

@@ -23,10 +23,9 @@ export default async function findUnique(
// ✅ Filter by isActive and deletedAt // ✅ Filter by isActive and deletedAt
const data = await prisma.potensiDesa.findFirst({ const data = await prisma.potensiDesa.findFirst({
where: { where: {
id, id,
isActive: true, isActive: true,
deletedAt: null,
}, },
include: { include: {
image: true, image: true,

View File

@@ -17,7 +17,6 @@ export default async function kategoriPotensiDelete(context: Context) {
where: { where: {
kategoriId: id, kategoriId: id,
isActive: true, isActive: true,
deletedAt: null,
}, },
}); });

View File

@@ -1,10 +1,9 @@
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import { requireAuth } from "@/lib/api-auth"; import { requireAuth } from "@/lib/api-auth";
export default async function sejarahDesaFindFirst(request: Request) { export default async function sejarahDesaFindFirst() {
// ✅ Authentication check // ✅ Authentication check
const headers = new Headers(request.url); const authResult = await requireAuth();
const authResult = await requireAuth({ headers });
if (!authResult.authenticated) { if (!authResult.authenticated) {
return authResult.response; return authResult.response;
} }
@@ -12,9 +11,8 @@ export default async function sejarahDesaFindFirst(request: Request) {
try { try {
// Get the first active record // Get the first active record
const data = await prisma.sejarahDesa.findFirst({ const data = await prisma.sejarahDesa.findFirst({
where: { where: {
isActive: true, isActive: true,
deletedAt: null
}, },
orderBy: { createdAt: 'asc' } // Get the oldest one first orderBy: { createdAt: 'asc' } // Get the oldest one first
}); });

View File

@@ -7,8 +7,8 @@ const SejarahDesa = new Elysia({
prefix: "/sejarah", prefix: "/sejarah",
tags: ["Desa/Profile"], tags: ["Desa/Profile"],
}) })
.get("/first", async (context) => { .get("/first", async () => {
const response = await sejarahDesaFindFirst(new Request(context.request)); const response = await sejarahDesaFindFirst();
return response; return response;
}) })
.get("/:id", async (context) => { .get("/:id", async (context) => {

View File

@@ -4,7 +4,7 @@ import { Context } from "elysia";
export default async function sejarahDesaUpdate(context: Context) { export default async function sejarahDesaUpdate(context: Context) {
// ✅ Authentication check // ✅ Authentication check
const authResult = await requireAuth(context); const authResult = await requireAuth();
if (!authResult.authenticated) { if (!authResult.authenticated) {
return authResult.response; return authResult.response;
} }

View File

@@ -46,11 +46,17 @@ fs.mkdir(UPLOAD_DIR_IMAGE, {
}).catch(() => {}); }).catch(() => {});
const corsConfig = { const corsConfig = {
origin: "*", origin: [
methods: ["GET", "POST", "PATCH", "DELETE", "PUT"] as HTTPMethod[], "http://localhost:3000",
allowedHeaders: "*", "http://localhost:3001",
"https://cld-dkr-desa-darmasaba-stg.wibudev.com",
"https://cld-dkr-staging-desa-darmasaba.wibudev.com",
"*", // Allow all origins in development
],
methods: ["GET", "POST", "PATCH", "DELETE", "PUT", "OPTIONS"] as HTTPMethod[],
allowedHeaders: ["Content-Type", "Authorization", "*"],
exposedHeaders: "*", exposedHeaders: "*",
maxAge: 5, maxAge: 86400, // 24 hours
credentials: true, credentials: true,
}; };

View File

@@ -2,7 +2,7 @@
import { useDarkMode } from '@/state/darkModeStore'; import { useDarkMode } from '@/state/darkModeStore';
import { themeTokens } from '@/utils/themeTokens'; import { themeTokens } from '@/utils/themeTokens';
import { Paper, Box, BoxProps, Divider, DividerProps } from '@mantine/core'; import { Box, BoxProps, Divider, DividerProps, Paper } from '@mantine/core';
import React from 'react'; import React from 'react';
/** /**
@@ -22,7 +22,6 @@ import React from 'react';
// ============================================================================ // ============================================================================
// Unified Card Component // Unified Card Component
* ============================================================================
interface UnifiedCardProps extends BoxProps { interface UnifiedCardProps extends BoxProps {
withBorder?: boolean; withBorder?: boolean;
@@ -63,12 +62,18 @@ export function UnifiedCard({
} }
}; };
const getShadow = () => {
if (shadow === 'none') return 'none';
return tokens.shadows[shadow];
};
return ( return (
<Paper <Paper
withBorder={withBorder} withBorder={withBorder}
bg={tokens.colors.bg.card} bg={tokens.colors.bg.card}
p={getPadding()} p={getPadding()}
radius={tokens.radius.lg} // 12-16px sesuai spec radius={tokens.radius.lg} // 12-16px sesuai spec
shadow={getShadow()}
style={{ style={{
borderColor: tokens.colors.border.default, borderColor: tokens.colors.border.default,
transition: hoverable transition: hoverable

View File

@@ -5,6 +5,8 @@ import { themeTokens, getResponsiveFz } from '@/utils/themeTokens';
import { Text, Title, Box, BoxProps } from '@mantine/core'; import { Text, Title, Box, BoxProps } from '@mantine/core';
import React from 'react'; import React from 'react';
type TextTruncate = 'end' | 'start' | boolean;
/** /**
* Unified Typography Components * Unified Typography Components
* *
@@ -73,7 +75,7 @@ export function UnifiedTitle({
const getColor = () => { const getColor = () => {
if (color === 'primary') return tokens.colors.text.primary; if (color === 'primary') return tokens.colors.text.primary;
if (color === 'secondary') return tokens.colors.text.secondary; if (color === 'secondary') return tokens.colors.text.secondary;
if (color === 'brand') return tokens.colors.brand; if (color === 'brand') return tokens.colors.text.brand;
return color; return color;
}; };
@@ -109,8 +111,14 @@ interface UnifiedTextProps {
align?: 'left' | 'center' | 'right'; align?: 'left' | 'center' | 'right';
color?: 'primary' | 'secondary' | 'tertiary' | 'muted' | 'brand' | 'link' | string; color?: 'primary' | 'secondary' | 'tertiary' | 'muted' | 'brand' | 'link' | string;
lineClamp?: number; lineClamp?: number;
truncate?: 'start' | 'end' | 'middle' | boolean; truncate?: TextTruncate;
span?: boolean; span?: boolean;
mt?: string;
mb?: string;
ml?: string;
mr?: string;
mx?: string;
my?: string;
style?: React.CSSProperties; style?: React.CSSProperties;
} }
@@ -123,6 +131,12 @@ export function UnifiedText({
lineClamp, lineClamp,
truncate, truncate,
span = false, span = false,
mt,
mb,
ml,
mr,
mx,
my,
style, style,
}: UnifiedTextProps) { }: UnifiedTextProps) {
const { isDark } = useDarkMode(); const { isDark } = useDarkMode();
@@ -163,7 +177,7 @@ export function UnifiedText({
case 'muted': case 'muted':
return tokens.colors.text.muted; return tokens.colors.text.muted;
case 'brand': case 'brand':
return tokens.colors.brand; return tokens.colors.text.brand;
case 'link': case 'link':
return tokens.colors.text.link; return tokens.colors.text.link;
default: default:
@@ -177,7 +191,7 @@ export function UnifiedText({
if (span) { if (span) {
return ( return (
<Text.Span <Text
ta={align} ta={align}
fz={typo.fz} fz={typo.fz}
fw={fw} fw={fw}
@@ -185,10 +199,16 @@ export function UnifiedText({
c={textColor} c={textColor}
lineClamp={lineClamp} lineClamp={lineClamp}
truncate={truncate} truncate={truncate}
mt={mt}
mb={mb}
ml={ml}
mr={mr}
mx={mx}
my={my}
style={style} style={style}
> >
{children} {children}
</Text.Span> </Text>
); );
} }
@@ -201,6 +221,12 @@ export function UnifiedText({
c={textColor} c={textColor}
lineClamp={lineClamp} lineClamp={lineClamp}
truncate={truncate} truncate={truncate}
mt={mt}
mb={mb}
ml={ml}
mr={mr}
mx={mx}
my={my}
style={style} style={style}
> >
{children} {children}

View File

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

View File

@@ -223,6 +223,10 @@ export const themeTokens = (isDark: boolean = false): ThemeTokens => {
hoverSoft: 'rgba(25, 113, 194, 0.03)', hoverSoft: 'rgba(25, 113, 194, 0.03)',
hoverMedium: 'rgba(25, 113, 194, 0.05)', hoverMedium: 'rgba(25, 113, 194, 0.05)',
activeAccent: 'rgba(25, 113, 194, 0.1)', activeAccent: 'rgba(25, 113, 194, 0.1)',
success: '#22c55e',
warning: '#facc15',
error: '#ef4444',
info: '#38bdf8',
}; };
const current = isDark ? darkColors : lightColors; const current = isDark ? darkColors : lightColors;
@@ -381,3 +385,7 @@ export const getActiveStateStyles = (isActive: boolean, isDark: boolean = false)
}, },
}; };
}; };