Merge pull request 'nico/25-feb-26' (#70) from nico/25-feb-26 into staggingweb
Reviewed-on: #70
This commit is contained in:
@@ -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");
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export default async function kategoriBeritaDelete(context: Context) {
|
|||||||
where: {
|
where: {
|
||||||
kategoriBeritaId: id,
|
kategoriBeritaId: id,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
deletedAt: null,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export default async function findUnique(
|
|||||||
where: {
|
where: {
|
||||||
id,
|
id,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
deletedAt: null,
|
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
image: true,
|
image: true,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export default async function kategoriPotensiDelete(context: Context) {
|
|||||||
where: {
|
where: {
|
||||||
kategoriId: id,
|
kategoriId: id,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
deletedAt: null,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -14,7 +13,6 @@ export default async function sejarahDesaFindFirst(request: Request) {
|
|||||||
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
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
* 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,13 +13,13 @@
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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();
|
||||||
@@ -55,8 +55,7 @@ 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({
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user