tambahannya
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -39,3 +39,10 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
# uploads
|
||||||
|
/uploads
|
||||||
|
|
||||||
|
# cache
|
||||||
|
/cache
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -15,13 +15,16 @@
|
|||||||
"@elysiajs/swagger": "^1.2.0",
|
"@elysiajs/swagger": "^1.2.0",
|
||||||
"@mantine/carousel": "^7.16.2",
|
"@mantine/carousel": "^7.16.2",
|
||||||
"@mantine/core": "^7.16.2",
|
"@mantine/core": "^7.16.2",
|
||||||
|
"@mantine/dropzone": "^7.17.0",
|
||||||
"@mantine/hooks": "^7.16.2",
|
"@mantine/hooks": "^7.16.2",
|
||||||
|
"@paljs/types": "^8.1.0",
|
||||||
"@prisma/client": "^6.3.1",
|
"@prisma/client": "^6.3.1",
|
||||||
"@tabler/icons-react": "^3.30.0",
|
"@tabler/icons-react": "^3.30.0",
|
||||||
"@types/bun": "^1.2.2",
|
"@types/bun": "^1.2.2",
|
||||||
"@types/lodash": "^4.17.15",
|
"@types/lodash": "^4.17.15",
|
||||||
"add": "^2.0.6",
|
"add": "^2.0.6",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
|
"bun": "^1.2.2",
|
||||||
"elysia": "^1.2.12",
|
"elysia": "^1.2.12",
|
||||||
"embla-carousel-autoplay": "^8.5.2",
|
"embla-carousel-autoplay": "^8.5.2",
|
||||||
"embla-carousel-react": "^7.1.0",
|
"embla-carousel-react": "^7.1.0",
|
||||||
@@ -29,11 +32,14 @@
|
|||||||
"get-port": "^7.1.0",
|
"get-port": "^7.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"motion": "^12.4.1",
|
"motion": "^12.4.1",
|
||||||
|
"nanoid": "^5.1.0",
|
||||||
"next": "15.1.6",
|
"next": "15.1.6",
|
||||||
"next-view-transitions": "^0.3.4",
|
"next-view-transitions": "^0.3.4",
|
||||||
|
"p-limit": "^6.2.0",
|
||||||
"prisma": "^6.3.1",
|
"prisma": "^6.3.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-simple-toasts": "^6.1.0",
|
||||||
"readdirp": "^4.1.1",
|
"readdirp": "^4.1.1",
|
||||||
"swr": "^2.3.2",
|
"swr": "^2.3.2",
|
||||||
"valtio": "^2.1.3"
|
"valtio": "^2.1.3"
|
||||||
|
|||||||
BIN
public/assets/images/spash.png
Normal file
BIN
public/assets/images/spash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/no-image.jpg
Normal file
BIN
public/no-image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
41
src/app/_com/SpashScreen.tsx
Normal file
41
src/app/_com/SpashScreen.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import colors from "@/con/colors";
|
||||||
|
import images from "@/con/images";
|
||||||
|
import { Flex, Image, Paper, Stack, Text } from "@mantine/core";
|
||||||
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
|
|
||||||
|
export default function SpashScreen() {
|
||||||
|
useShallowEffect(() => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
window.location.href = "/darmasaba";
|
||||||
|
}, 3000);
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
w={"100%"}
|
||||||
|
h={"100vh"}
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
bg={colors["blue-button"]}
|
||||||
|
>
|
||||||
|
<Paper p={"md"} miw={320}>
|
||||||
|
<Flex>
|
||||||
|
<Image
|
||||||
|
src={images["darmasaba-icon"]}
|
||||||
|
alt="darmasaba"
|
||||||
|
w={100}
|
||||||
|
h={100}
|
||||||
|
/>
|
||||||
|
<Stack p={"md"} gap={"0"}>
|
||||||
|
<Text>Pemerintah Desa</Text>
|
||||||
|
<Text c={colors["blue-button"]} fz={"2rem"} fw={"bold"}>
|
||||||
|
DARMASABA
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Flex>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
src/app/admin/_com/AdminNav.tsx
Normal file
18
src/app/admin/_com/AdminNav.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"use client";
|
||||||
|
import { Button, Stack } from "@mantine/core";
|
||||||
|
import { Link } from "next-view-transitions";
|
||||||
|
|
||||||
|
export default function AdminNav() {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Button.Group p={"md"}>
|
||||||
|
<Button component={Link} href="/admin/images">
|
||||||
|
Images
|
||||||
|
</Button>
|
||||||
|
<Button component={Link} href="/admin/csv">
|
||||||
|
CSV
|
||||||
|
</Button>
|
||||||
|
</Button.Group>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
139
src/app/admin/_com/ListImage.tsx
Normal file
139
src/app/admin/_com/ListImage.tsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
"use client";
|
||||||
|
import stateListImage from "@/state/state-list-image";
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
SimpleGrid,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
|
import { IconSearch, IconX } from "@tabler/icons-react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { useState } from "react";
|
||||||
|
import toast from "react-simple-toasts";
|
||||||
|
import { useSnapshot } from "valtio";
|
||||||
|
|
||||||
|
export default function ListImage() {
|
||||||
|
const { list, total } = useSnapshot(stateListImage);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
// get url
|
||||||
|
console.log(window.location.origin);
|
||||||
|
stateListImage.load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
let timeOut: NodeJS.Timer;
|
||||||
|
return (
|
||||||
|
<Stack p={"lg"}>
|
||||||
|
<Group justify="end">
|
||||||
|
<TextInput
|
||||||
|
leftSection={<IconSearch />}
|
||||||
|
rightSection={
|
||||||
|
<ActionIcon
|
||||||
|
variant="transparent"
|
||||||
|
onClick={() => {
|
||||||
|
stateListImage.load();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconX />
|
||||||
|
</ActionIcon>
|
||||||
|
}
|
||||||
|
placeholder="Cari"
|
||||||
|
onChange={(e) => {
|
||||||
|
if (timeOut) clearTimeout(timeOut);
|
||||||
|
timeOut = setTimeout(() => {
|
||||||
|
stateListImage.load({ search: e.target.value });
|
||||||
|
}, 200);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<SimpleGrid
|
||||||
|
cols={{
|
||||||
|
base: 3,
|
||||||
|
md: 5,
|
||||||
|
lg: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{list &&
|
||||||
|
list.map((v, k) => {
|
||||||
|
return (
|
||||||
|
<Paper key={k} shadow="sm">
|
||||||
|
<Stack pos={"relative"} gap={0} justify="space-between">
|
||||||
|
<motion.div
|
||||||
|
onClick={() => {
|
||||||
|
// copy to clipboard
|
||||||
|
navigator.clipboard.writeText(v.url);
|
||||||
|
toast("Berhasil disalin");
|
||||||
|
}}
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.8 }}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
h={100}
|
||||||
|
src={v.url + "?size=100"}
|
||||||
|
alt={v.name}
|
||||||
|
fit="cover"
|
||||||
|
loading="lazy"
|
||||||
|
style={{
|
||||||
|
objectFit: "cover",
|
||||||
|
objectPosition: "center",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
<Box p={"md"} h={54}>
|
||||||
|
<Text lineClamp={2} fz={"xs"}>
|
||||||
|
{v.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Group justify="end">
|
||||||
|
<Button.Group>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
// copy to clipboard
|
||||||
|
navigator.clipboard.writeText(v.url);
|
||||||
|
toast("Berhasil disalin");
|
||||||
|
}}
|
||||||
|
variant="subtle"
|
||||||
|
size="compact-xs"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
loading={loading}
|
||||||
|
size="compact-xs"
|
||||||
|
onClick={() => {
|
||||||
|
stateListImage.del({ name: v.name }).finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
delete
|
||||||
|
</Button>
|
||||||
|
</Button.Group>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SimpleGrid>
|
||||||
|
{total && (
|
||||||
|
<Pagination
|
||||||
|
total={total}
|
||||||
|
onChange={(e) => {
|
||||||
|
stateListImage.page = e;
|
||||||
|
stateListImage.load();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
49
src/app/admin/_com/UploadCsv.tsx
Normal file
49
src/app/admin/_com/UploadCsv.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"use client";
|
||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Group, Stack, Text } from "@mantine/core";
|
||||||
|
import { Dropzone } from "@mantine/dropzone";
|
||||||
|
import { useState } from "react";
|
||||||
|
import toast from "react-simple-toasts";
|
||||||
|
|
||||||
|
export default function UploadCsv() {
|
||||||
|
return (
|
||||||
|
<Stack p={"md"}>
|
||||||
|
<DropUpload />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropUpload() {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack justify="center" align="center">
|
||||||
|
<Group>
|
||||||
|
<Dropzone
|
||||||
|
loading={loading}
|
||||||
|
miw={460}
|
||||||
|
// accept csv
|
||||||
|
accept={["text/csv"]}
|
||||||
|
onDrop={async (droppedFiles) => {
|
||||||
|
if (droppedFiles.length < 0) {
|
||||||
|
return toast("Tidak ada file yang diunggah");
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
for (const file of droppedFiles) {
|
||||||
|
await ApiFetch.api["upl-csv-single"].post({
|
||||||
|
name: file.name,
|
||||||
|
file,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<Text ta="center">Drop Csv here</Text>
|
||||||
|
</Stack>
|
||||||
|
</Dropzone>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
34
src/app/admin/_com/UploadImage.tsx
Normal file
34
src/app/admin/_com/UploadImage.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
"use client";
|
||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import stateListImage from "@/state/state-list-image";
|
||||||
|
import { Stack, Text } from "@mantine/core";
|
||||||
|
import { Dropzone, IMAGE_MIME_TYPE } from "@mantine/dropzone";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export default function UploadImage() {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
return (
|
||||||
|
<Stack p={"md"}>
|
||||||
|
<Dropzone
|
||||||
|
loading={loading}
|
||||||
|
accept={IMAGE_MIME_TYPE} // Hanya menerima tipe file gambar
|
||||||
|
onDrop={async (droppedFiles) => {
|
||||||
|
setLoading(true);
|
||||||
|
for (const file of droppedFiles) {
|
||||||
|
await ApiFetch.api["upl-img-single"].post({
|
||||||
|
file: file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
stateListImage.load();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack align="center">
|
||||||
|
<Text ta="center">Drop images here</Text>
|
||||||
|
</Stack>
|
||||||
|
</Dropzone>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
src/app/admin/csv/page.tsx
Normal file
10
src/app/admin/csv/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Stack } from "@mantine/core";
|
||||||
|
import UploadCsv from "../_com/UploadCsv";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<UploadCsv />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
src/app/admin/images/page.tsx
Normal file
12
src/app/admin/images/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Stack } from "@mantine/core";
|
||||||
|
import ListImage from "../_com/ListImage";
|
||||||
|
import UploadImage from "../_com/UploadImage";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<UploadImage />
|
||||||
|
<ListImage />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
src/app/admin/layout.tsx
Normal file
11
src/app/admin/layout.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Stack } from "@mantine/core";
|
||||||
|
import AdminNav from "./_com/AdminNav";
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Stack gap={0}>
|
||||||
|
<AdminNav />
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
src/app/admin/page.tsx
Normal file
9
src/app/admin/page.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Container, Stack } from "@mantine/core";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<Stack h={"100%"}>
|
||||||
|
<Container>admin</Container>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
src/app/api/[[...slugs]]/_lib/img-del.ts
Normal file
20
src/app/api/[[...slugs]]/_lib/img-del.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
async function imgDel({
|
||||||
|
name,
|
||||||
|
UPLOAD_DIR_IMAGE,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
UPLOAD_DIR_IMAGE: string;
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
await fs.unlink(path.join(UPLOAD_DIR_IMAGE, name));
|
||||||
|
return "ok";
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default imgDel;
|
||||||
61
src/app/api/[[...slugs]]/_lib/img.ts
Normal file
61
src/app/api/[[...slugs]]/_lib/img.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
import sharp from "sharp";
|
||||||
|
|
||||||
|
async function img({
|
||||||
|
name,
|
||||||
|
UPLOAD_DIR_IMAGE,
|
||||||
|
ROOT,
|
||||||
|
size,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
UPLOAD_DIR_IMAGE: string;
|
||||||
|
ROOT: string;
|
||||||
|
size?: number; // Ukuran opsional (tidak ada default)
|
||||||
|
}) {
|
||||||
|
const completeName = path.basename(name); // Nama file lengkap
|
||||||
|
const ext = path.extname(name).toLowerCase(); // Ekstensi file dalam huruf kecil
|
||||||
|
// const fileNameWithoutExt = path.basename(name, ext); // Nama file tanpa ekstensi
|
||||||
|
|
||||||
|
// Default image jika terjadi kesalahan
|
||||||
|
const noImage = path.join(ROOT, "public/no-image.jpg");
|
||||||
|
|
||||||
|
// Validasi ekstensi file
|
||||||
|
if (![".jpg", ".jpeg", ".png"].includes(ext)) {
|
||||||
|
console.warn(`Ekstensi file tidak didukung: ${ext}`);
|
||||||
|
return new Response(await fs.readFile(noImage), {
|
||||||
|
headers: { "Content-Type": "image/jpeg" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Path ke file asli
|
||||||
|
const filePath = path.join(UPLOAD_DIR_IMAGE, completeName);
|
||||||
|
|
||||||
|
// Periksa apakah file ada
|
||||||
|
await fs.stat(filePath);
|
||||||
|
|
||||||
|
// Metadata gambar asli
|
||||||
|
const metadata = await sharp(filePath).metadata();
|
||||||
|
|
||||||
|
// Proses resize menggunakan sharp
|
||||||
|
const resizedImageBuffer = await sharp(filePath)
|
||||||
|
.resize(size || metadata.width) // Gunakan size jika diberikan, jika tidak gunakan width asli
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
return new Response(resizedImageBuffer, {
|
||||||
|
headers: {
|
||||||
|
"Cache-Control": "public, max-age=3600, stale-while-revalidate=600",
|
||||||
|
"Content-Type": "image/jpeg",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Gagal memproses file: ${name}`, error);
|
||||||
|
// Jika file tidak ditemukan atau gagal diproses, kembalikan default image
|
||||||
|
return new Response(await fs.readFile(noImage), {
|
||||||
|
headers: { "Content-Type": "image/jpeg" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default img;
|
||||||
30
src/app/api/[[...slugs]]/_lib/imgs.ts
Normal file
30
src/app/api/[[...slugs]]/_lib/imgs.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import fs from "fs/promises";
|
||||||
|
|
||||||
|
async function imgs({
|
||||||
|
search = "",
|
||||||
|
page = 1,
|
||||||
|
count = 20,
|
||||||
|
UPLOAD_DIR_IMAGE,
|
||||||
|
}: {
|
||||||
|
search?: string;
|
||||||
|
page?: number;
|
||||||
|
count?: number;
|
||||||
|
UPLOAD_DIR_IMAGE: string;
|
||||||
|
}) {
|
||||||
|
const files = await fs.readdir(UPLOAD_DIR_IMAGE);
|
||||||
|
|
||||||
|
return files
|
||||||
|
.filter(
|
||||||
|
(file) =>
|
||||||
|
file.endsWith(".jpg") || file.endsWith(".png") || file.endsWith(".jpeg")
|
||||||
|
)
|
||||||
|
.filter((file) => file.includes(search))
|
||||||
|
.slice((page - 1) * count, page * count)
|
||||||
|
.map((file) => ({
|
||||||
|
name: file,
|
||||||
|
url: `/api/img/${file}`,
|
||||||
|
total: files.length,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default imgs;
|
||||||
12
src/app/api/[[...slugs]]/_lib/upl-csv-single.ts
Normal file
12
src/app/api/[[...slugs]]/_lib/upl-csv-single.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export async function uplCsvSingle({
|
||||||
|
fileName,
|
||||||
|
file,
|
||||||
|
}: {
|
||||||
|
fileName: string;
|
||||||
|
file: File;
|
||||||
|
}) {
|
||||||
|
const textFile = await file.text();
|
||||||
|
console.log(fileName, textFile);
|
||||||
|
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
16
src/app/api/[[...slugs]]/_lib/upl-csv.ts
Normal file
16
src/app/api/[[...slugs]]/_lib/upl-csv.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
async function uplCsv({ files }: { files: File[] }) {
|
||||||
|
if (!Array.isArray(files) || files.length === 0) {
|
||||||
|
throw new Error("Tidak ada file yang diunggah");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const textFile = await file.text();
|
||||||
|
const fileName = file.name;
|
||||||
|
|
||||||
|
console.log(textFile, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default uplCsv;
|
||||||
32
src/app/api/[[...slugs]]/_lib/upl-img-single.ts
Normal file
32
src/app/api/[[...slugs]]/_lib/upl-img-single.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
export async function uplImgSingle({
|
||||||
|
fileName,
|
||||||
|
file,
|
||||||
|
UPLOAD_DIR_IMAGE,
|
||||||
|
}: {
|
||||||
|
fileName: string;
|
||||||
|
file: File;
|
||||||
|
UPLOAD_DIR_IMAGE: string;
|
||||||
|
}) {
|
||||||
|
if (!fileName || typeof fileName !== "string" || fileName.trim() === "") {
|
||||||
|
console.warn(`Nama file tidak valid: ${fileName}`);
|
||||||
|
fileName = nanoid() + ".jpg";
|
||||||
|
}
|
||||||
|
const ext = path.extname(fileName).toLowerCase();
|
||||||
|
const fileNameWithoutExt = path.basename(fileName, ext);
|
||||||
|
const fileNameKebabCase = _.kebabCase(fileNameWithoutExt) + ext;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||||||
|
const filePath = path.join(UPLOAD_DIR_IMAGE, fileNameKebabCase);
|
||||||
|
await fs.writeFile(filePath, buffer);
|
||||||
|
return filePath;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/app/api/[[...slugs]]/_lib/upl-img.ts
Normal file
52
src/app/api/[[...slugs]]/_lib/upl-img.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import path from "path";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
|
async function uplImg({
|
||||||
|
files,
|
||||||
|
UPLOAD_DIR_IMAGE,
|
||||||
|
}: {
|
||||||
|
files: File[];
|
||||||
|
UPLOAD_DIR_IMAGE: string;
|
||||||
|
}) {
|
||||||
|
// Validasi input
|
||||||
|
if (!Array.isArray(files) || files.length === 0) {
|
||||||
|
throw new Error("Tidak ada file yang diunggah");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
let fileName = file.name;
|
||||||
|
|
||||||
|
// Validasi nama file
|
||||||
|
if (!fileName || typeof fileName !== "string" || fileName.trim() === "") {
|
||||||
|
console.warn(`Nama file tidak valid: ${fileName}`);
|
||||||
|
fileName = nanoid() + ".jpg";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitasi nama file untuk mencegah path traversal
|
||||||
|
const sanitizedFileName = sanitizeFileName(fileName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Konversi file ke buffer
|
||||||
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||||||
|
|
||||||
|
// Tulis file ke direktori uploads
|
||||||
|
const filePath = path.join(UPLOAD_DIR_IMAGE, sanitizedFileName);
|
||||||
|
await fs.writeFile(filePath, buffer);
|
||||||
|
|
||||||
|
console.log(`File berhasil diunggah: ${sanitizedFileName}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Gagal mengunggah file ${fileName}:`, error);
|
||||||
|
throw new Error(`Gagal mengunggah file: ${fileName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk membersihkan nama file dari karakter yang tidak aman
|
||||||
|
function sanitizeFileName(fileName: string): string {
|
||||||
|
return fileName.replace(/[^a-zA-Z0-9._\-]/g, "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
export default uplImg;
|
||||||
@@ -1,8 +1,34 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import cors, { HTTPMethod } from "@elysiajs/cors";
|
import cors, { HTTPMethod } from "@elysiajs/cors";
|
||||||
import swagger from "@elysiajs/swagger";
|
import swagger from "@elysiajs/swagger";
|
||||||
import { Elysia } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import getPotensi from "./_lib/get-potensi";
|
import getPotensi from "./_lib/get-potensi";
|
||||||
|
import img from "./_lib/img";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
import uplImg from "./_lib/upl-img";
|
||||||
|
import imgs from "./_lib/imgs";
|
||||||
|
import uplCsv from "./_lib/upl-csv";
|
||||||
|
import imgDel from "./_lib/img-del";
|
||||||
|
import { uplImgSingle } from "./_lib/upl-img-single";
|
||||||
|
import { uplCsvSingle } from "./_lib/upl-csv-single";
|
||||||
|
const ROOT = process.cwd();
|
||||||
|
|
||||||
|
if (!process.env.WIBU_UPLOAD_DIR)
|
||||||
|
throw new Error("WIBU_UPLOAD_DIR is not defined");
|
||||||
|
|
||||||
|
const UPLOAD_DIR = path.join(ROOT, process.env.WIBU_UPLOAD_DIR);
|
||||||
|
const UPLOAD_DIR_IMAGE = path.join(UPLOAD_DIR, "image");
|
||||||
|
|
||||||
|
// create uploads dir
|
||||||
|
fs.mkdir(UPLOAD_DIR, {
|
||||||
|
recursive: true,
|
||||||
|
}).catch(() => {});
|
||||||
|
|
||||||
|
// create image uploads dir
|
||||||
|
fs.mkdir(UPLOAD_DIR_IMAGE, {
|
||||||
|
recursive: true,
|
||||||
|
}).catch(() => {});
|
||||||
|
|
||||||
const corsConfig = {
|
const corsConfig = {
|
||||||
origin: "*",
|
origin: "*",
|
||||||
@@ -13,7 +39,6 @@ const corsConfig = {
|
|||||||
credentials: true,
|
credentials: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
async function layanan() {
|
async function layanan() {
|
||||||
const data = await prisma.layanan.findMany();
|
const data = await prisma.layanan.findMany();
|
||||||
return { data };
|
return { data };
|
||||||
@@ -21,12 +46,126 @@ async function layanan() {
|
|||||||
const ApiServer = new Elysia()
|
const ApiServer = new Elysia()
|
||||||
.use(swagger({ path: "/api/docs" }))
|
.use(swagger({ path: "/api/docs" }))
|
||||||
.use(cors(corsConfig))
|
.use(cors(corsConfig))
|
||||||
.group("/api", app => app
|
.onError(({ code }) => {
|
||||||
|
if (code === "NOT_FOUND") {
|
||||||
|
return {
|
||||||
|
status: 404,
|
||||||
|
body: "Route not found :(",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.group("/api", (app) =>
|
||||||
|
app
|
||||||
.get("/layanan", layanan)
|
.get("/layanan", layanan)
|
||||||
.get("/potensi", getPotensi)
|
.get("/potensi", getPotensi)
|
||||||
|
.get(
|
||||||
|
"/img/:name",
|
||||||
|
({ params, query }) => {
|
||||||
|
return img({
|
||||||
|
name: params.name,
|
||||||
|
UPLOAD_DIR_IMAGE,
|
||||||
|
ROOT,
|
||||||
|
size: query.size,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: t.Object({
|
||||||
|
name: t.String(),
|
||||||
|
}),
|
||||||
|
query: t.Optional(
|
||||||
|
t.Object({
|
||||||
|
size: t.Optional(t.Number()),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
.delete(
|
||||||
|
"/img/:name",
|
||||||
|
({ params }) => {
|
||||||
|
return imgDel({
|
||||||
|
name: params.name,
|
||||||
|
UPLOAD_DIR_IMAGE,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: t.Object({
|
||||||
|
name: t.String(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/imgs",
|
||||||
|
({ query }) => {
|
||||||
|
return imgs({
|
||||||
|
search: query.search,
|
||||||
|
page: query.page,
|
||||||
|
count: query.count,
|
||||||
|
UPLOAD_DIR_IMAGE,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: t.Optional(
|
||||||
|
t.Object({
|
||||||
|
page: t.Number({ default: 1 }),
|
||||||
|
count: t.Number({ default: 10 }),
|
||||||
|
search: t.String({ default: "" }),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.post(
|
||||||
|
"/upl-img",
|
||||||
|
({ body }) => {
|
||||||
|
console.log(body.title);
|
||||||
|
return uplImg({ files: body.files, UPLOAD_DIR_IMAGE });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
title: t.String(),
|
||||||
|
files: t.Files({ multiple: true }),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.post(
|
||||||
|
"/upl-img-single",
|
||||||
|
({ body }) => {
|
||||||
|
return uplImgSingle({
|
||||||
|
fileName: body.name,
|
||||||
|
file: body.file,
|
||||||
|
UPLOAD_DIR_IMAGE,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
name: t.String(),
|
||||||
|
file: t.File(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.post(
|
||||||
|
"/upl-csv-single",
|
||||||
|
({ body }) => {
|
||||||
|
return uplCsvSingle({ fileName: body.name, file: body.file });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
name: t.String(),
|
||||||
|
file: t.File(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.post(
|
||||||
|
"/upl-csv",
|
||||||
|
({ body }) => {
|
||||||
|
return uplCsv({ files: body.files });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
files: t.Files(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
export const GET = ApiServer.handle;
|
export const GET = ApiServer.handle;
|
||||||
export const POST = ApiServer.handle;
|
export const POST = ApiServer.handle;
|
||||||
@@ -34,5 +173,4 @@ export const PATCH = ApiServer.handle;
|
|||||||
export const DELETE = ApiServer.handle;
|
export const DELETE = ApiServer.handle;
|
||||||
export const PUT = ApiServer.handle;
|
export const PUT = ApiServer.handle;
|
||||||
|
|
||||||
export type AppServer = typeof ApiServer
|
export type AppServer = typeof ApiServer;
|
||||||
|
|
||||||
|
|||||||
6
src/app/darmasaba/(pages)/module/[sub]/page.tsx
Normal file
6
src/app/darmasaba/(pages)/module/[sub]/page.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default async function Page({ params }: { params: Promise<{ sub: string }> }) {
|
||||||
|
const { sub } = await params
|
||||||
|
return <div>
|
||||||
|
{sub}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
5
src/app/darmasaba/(tambahan)/penghargaan/page.tsx
Normal file
5
src/app/darmasaba/(tambahan)/penghargaan/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export default function Page() {
|
||||||
|
return <div>
|
||||||
|
penghargaan
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import { ActionIcon, Box, Burger, Group, Stack, Text } from "@mantine/core";
|
|||||||
import { IconHome, IconSquareArrowRight } from "@tabler/icons-react";
|
import { IconHome, IconSquareArrowRight } from "@tabler/icons-react";
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useSnapshot } from "valtio";
|
import { useSnapshot } from "valtio";
|
||||||
import { MenuItem } from "../../types/menu-item";
|
import { MenuItem } from "../../../../types/menu-item";
|
||||||
import { NavbarMainMenu } from "./NavbarMainMenu";
|
import { NavbarMainMenu } from "./NavbarMainMenu";
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ export function Navbar() {
|
|||||||
<Stack hiddenFrom="sm" bg={colors.grey[2]}>
|
<Stack hiddenFrom="sm" bg={colors.grey[2]}>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<ActionIcon variant="transparent" onClick={() => {
|
<ActionIcon variant="transparent" onClick={() => {
|
||||||
router.push("/")
|
router.push("/darmasaba")
|
||||||
stateNav.mobileOpen = false
|
stateNav.mobileOpen = false
|
||||||
}}>
|
}}>
|
||||||
<IconHome />
|
<IconHome />
|
||||||
@@ -6,7 +6,7 @@ import { ActionIcon, Button, Container, Flex, Image, Stack } from "@mantine/core
|
|||||||
import { useHover } from "@mantine/hooks"
|
import { useHover } from "@mantine/hooks"
|
||||||
import { useTransitionRouter } from 'next-view-transitions'
|
import { useTransitionRouter } from 'next-view-transitions'
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from "valtio"
|
||||||
import { MenuItem } from "../../types/menu-item"
|
import { MenuItem } from "../../../../types/menu-item"
|
||||||
import { NavbarSearch } from "./NavBarSearch"
|
import { NavbarSearch } from "./NavBarSearch"
|
||||||
import { NavbarSubMenu } from "./NavbarSubMenu"
|
import { NavbarSubMenu } from "./NavbarSubMenu"
|
||||||
import { IconSearch } from "@tabler/icons-react"
|
import { IconSearch } from "@tabler/icons-react"
|
||||||
@@ -26,7 +26,7 @@ export function NavbarMainMenu({ listNavbar }: {
|
|||||||
md: "nowrap"
|
md: "nowrap"
|
||||||
}}>
|
}}>
|
||||||
<ActionIcon radius={"100"} onClick={() => {
|
<ActionIcon radius={"100"} onClick={() => {
|
||||||
router.push("/")
|
router.push("/darmasaba")
|
||||||
stateNav.clear()
|
stateNav.clear()
|
||||||
}} >
|
}} >
|
||||||
<Image radius={"100"} src={"/assets/images/darmasaba-icon.png"} alt="icon" w={24} h={24} loading="lazy" />
|
<Image radius={"100"} src={"/assets/images/darmasaba-icon.png"} alt="icon" w={24} h={24} loading="lazy" />
|
||||||
@@ -4,7 +4,7 @@ import stateNav from "@/state/state-nav";
|
|||||||
import { Button, Container, Stack } from "@mantine/core";
|
import { Button, Container, Stack } from "@mantine/core";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { motion } from "motion/react";
|
import { motion } from "motion/react";
|
||||||
import { MenuItem } from "../../types/menu-item";
|
import { MenuItem } from "../../../../types/menu-item";
|
||||||
import { useTransitionRouter } from 'next-view-transitions'
|
import { useTransitionRouter } from 'next-view-transitions'
|
||||||
|
|
||||||
export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
|
export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import images from "@/con/images";
|
import images from "@/con/images";
|
||||||
import stateNav from "@/state/state-nav";
|
|
||||||
import { Center, Image, Paper, SimpleGrid } from "@mantine/core";
|
import { Center, Image, Paper, SimpleGrid } from "@mantine/core";
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useTransitionRouter } from 'next-view-transitions';
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
@@ -12,8 +11,7 @@ function ModuleItem({ item }: { item: string }) {
|
|||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
stateNav.module = item;
|
router.push(`/module/c`);
|
||||||
router.push("/module");
|
|
||||||
}}
|
}}
|
||||||
p={"md"}
|
p={"md"}
|
||||||
bg={"white"}
|
bg={"white"}
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
import ModuleView from "./ModuleView";
|
import ModuleView from "./ModuleView";
|
||||||
import SosmedView from "./SosmedView";
|
import SosmedView from "./SosmedView";
|
||||||
|
|
||||||
function Content1() {
|
function LandingPage() {
|
||||||
return (
|
return (
|
||||||
<Stack bg={colors["blue-button"]}>
|
<Stack bg={colors["blue-button"]}>
|
||||||
<Flex
|
<Flex
|
||||||
@@ -184,4 +184,4 @@ function Content1() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Content1;
|
export default LandingPage;
|
||||||
67
src/app/darmasaba/_com/main-page/penghargaan/index.tsx
Normal file
67
src/app/darmasaba/_com/main-page/penghargaan/index.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
'use client';
|
||||||
|
import { Stack, Box, Container, Button, Text } from "@mantine/core";
|
||||||
|
import { useTransitionRouter } from 'next-view-transitions'
|
||||||
|
|
||||||
|
function Penghargaan() {
|
||||||
|
const router = useTransitionRouter()
|
||||||
|
return (
|
||||||
|
<Stack pos={"relative"} h={720}>
|
||||||
|
<video
|
||||||
|
width="320"
|
||||||
|
height="240"
|
||||||
|
loop
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<source src="/assets/videos/award.mp4" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
background: "rgba(0,0,0,0.6)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Container w={{ base: "100%", md: "80%" }} p={"xl"} h={720}>
|
||||||
|
<Stack justify="center" align="center">
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
fw={"bold"}
|
||||||
|
fz={"2.4rem"}
|
||||||
|
c={"white"}
|
||||||
|
>
|
||||||
|
Penghargaan
|
||||||
|
</Text>
|
||||||
|
<Stack align="center" gap={0}>
|
||||||
|
<Text fz={"1.4rem"} c={"white"}>
|
||||||
|
Juara 2 Lomba Video Pendek
|
||||||
|
</Text>
|
||||||
|
<Text fz={"1.4rem"} c={"white"}>
|
||||||
|
Juara 2 Duta Investasi
|
||||||
|
</Text>
|
||||||
|
<Text fz={"1.4rem"} c={"white"}>
|
||||||
|
Juara Favorit Lomba Video Pendek
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Button onClick={() => router.push("/penghargaan")} variant="white" radius={100}>
|
||||||
|
Selanjutnya
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Penghargaan;
|
||||||
22
src/app/darmasaba/layout.tsx
Normal file
22
src/app/darmasaba/layout.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import colors from "@/con/colors";
|
||||||
|
import { Box, Space, Stack } from "@mantine/core";
|
||||||
|
import Footer from "@/app/darmasaba/_com/Footer";
|
||||||
|
import { Navbar } from "@/app/darmasaba/_com/Navbar";
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Stack gap={0} bg={colors.grey[1]}>
|
||||||
|
<Navbar />
|
||||||
|
<Space h={{
|
||||||
|
base: "2.2rem",
|
||||||
|
md: "2.5rem"
|
||||||
|
}} />
|
||||||
|
<Box style={{
|
||||||
|
overflow: "scroll"
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
<Footer />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
24
src/app/darmasaba/page.tsx
Normal file
24
src/app/darmasaba/page.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import LandingPage from "@/app/darmasaba/_com/main-page/landing-page";
|
||||||
|
import Penghargaan from "@/app/darmasaba/_com/main-page/penghargaan";
|
||||||
|
import Content3 from "@/app/darmasaba/_com/main-page/layanan";
|
||||||
|
import Content4 from "@/app/darmasaba/_com/main-page/content-4";
|
||||||
|
import Content5 from "@/app/darmasaba/_com/main-page/content-5";
|
||||||
|
import Content6 from "@/app/darmasaba/_com/main-page/content-6";
|
||||||
|
import colors from "@/con/colors";
|
||||||
|
// import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Stack } from "@mantine/core";
|
||||||
|
import Content7 from "@/app/darmasaba/_com/main-page/content-7";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<Stack bg={colors.grey[1]} gap={"4rem"}>
|
||||||
|
<LandingPage />
|
||||||
|
<Penghargaan />
|
||||||
|
<Content3 />
|
||||||
|
<Content4 />
|
||||||
|
<Content5 />
|
||||||
|
<Content6 />
|
||||||
|
<Content7 />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
src/app/error.tsx
Normal file
9
src/app/error.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
'use client'
|
||||||
|
export default function Error() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Something went wrong!</h1>
|
||||||
|
<p>Sorry, something went wrong.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,19 +2,22 @@
|
|||||||
// All packages except `@mantine/hooks` require styles imports
|
// All packages except `@mantine/hooks` require styles imports
|
||||||
import "@mantine/carousel/styles.css";
|
import "@mantine/carousel/styles.css";
|
||||||
import "@mantine/core/styles.css";
|
import "@mantine/core/styles.css";
|
||||||
|
import '@mantine/dropzone/styles.css';
|
||||||
import "animate.css";
|
import "animate.css";
|
||||||
|
import 'react-simple-toasts/dist/style.css';
|
||||||
|
import 'react-simple-toasts/dist/theme/dark.css';
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
import LoadDataFirstClient from "@/com/LoadDataFirstClient";
|
|
||||||
|
|
||||||
|
import LoadDataFirstClient from "@/app/darmasaba/_com/LoadDataFirstClient";
|
||||||
import {
|
import {
|
||||||
ColorSchemeScript,
|
ColorSchemeScript,
|
||||||
MantineProvider,
|
MantineProvider,
|
||||||
createTheme,
|
createTheme,
|
||||||
mantineHtmlProps,
|
mantineHtmlProps,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { MainLayout } from "../com/MainLayout";
|
|
||||||
import { ViewTransitions } from "next-view-transitions";
|
import { ViewTransitions } from "next-view-transitions";
|
||||||
import { WebVitals } from "./_com/WebVitals";
|
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "desa darmasaba",
|
title: "desa darmasaba",
|
||||||
@@ -47,10 +50,7 @@ export default function RootLayout({
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<MantineProvider theme={theme}>
|
<MantineProvider theme={theme}>
|
||||||
<MainLayout>
|
|
||||||
<WebVitals />
|
|
||||||
{children}
|
{children}
|
||||||
</MainLayout>
|
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
</body>
|
</body>
|
||||||
<LoadDataFirstClient />
|
<LoadDataFirstClient />
|
||||||
|
|||||||
8
src/app/not-found.tsx
Normal file
8
src/app/not-found.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Not Found</h1>
|
||||||
|
<p>This is the not found page</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,34 +1,12 @@
|
|||||||
import Content1 from "@/com/main-page/content-1";
|
|
||||||
import Content2 from "@/com/main-page/content-2";
|
|
||||||
import Content3 from "@/com/main-page/layanan";
|
|
||||||
import Content4 from "@/com/main-page/content-4";
|
|
||||||
import Content5 from "@/com/main-page/content-5";
|
|
||||||
import Content6 from "@/com/main-page/content-6";
|
|
||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
// import ApiFetch from "@/lib/api-fetch";
|
// import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Stack } from "@mantine/core";
|
import { Stack } from "@mantine/core";
|
||||||
import Content7 from "@/com/main-page/content-7";
|
import SpashScreen from "./_com/SpashScreen";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<Stack bg={colors.grey[1]} gap={"4rem"}>
|
<Stack bg={colors.grey[1]} gap={"4rem"}>
|
||||||
<Content1 />
|
<SpashScreen />
|
||||||
<Content2 />
|
|
||||||
<Content3 />
|
|
||||||
<Content4 />
|
|
||||||
<Content5 />
|
|
||||||
<Content6 />
|
|
||||||
<Content7 />
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function Content3Loader() {
|
|
||||||
// const { data } = await fetch("/api/layanan").then((v) => v.json());
|
|
||||||
// return <Content3 data={data} />;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async function Content4Loader() {
|
|
||||||
// const { data } = await ApiFetch.api.potensi.get();
|
|
||||||
// return <Content4 data={data?.data as any} />;
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
'use client';
|
|
||||||
import { Stack, Box, Container, Button, Text } from "@mantine/core";
|
|
||||||
|
|
||||||
function Content2() {
|
|
||||||
return (
|
|
||||||
<Stack pos={"relative"} h={720}>
|
|
||||||
<video
|
|
||||||
width="320"
|
|
||||||
height="240"
|
|
||||||
loop
|
|
||||||
autoPlay
|
|
||||||
muted
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "cover",
|
|
||||||
overflow: "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<source src="/assets/videos/award.mp4" type="video/mp4" />
|
|
||||||
</video>
|
|
||||||
<Box
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
background: "rgba(0,0,0,0.6)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"} h={720}>
|
|
||||||
<Stack justify="center" align="center">
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
fw={"bold"}
|
|
||||||
fz={"2.4rem"}
|
|
||||||
c={"white"}
|
|
||||||
>
|
|
||||||
Penghargaan
|
|
||||||
</Text>
|
|
||||||
<Stack align="center" gap={0}>
|
|
||||||
<Text fz={"1.4rem"} c={"white"}>
|
|
||||||
Juara 2 Lomba Video Pendek
|
|
||||||
</Text>
|
|
||||||
<Text fz={"1.4rem"} c={"white"}>
|
|
||||||
Juara 2 Duta Investasi
|
|
||||||
</Text>
|
|
||||||
<Text fz={"1.4rem"} c={"white"}>
|
|
||||||
Juara Favorit Lomba Video Pendek
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
<Button variant="white" radius={100}>
|
|
||||||
Selanjutnya
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Container>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Content2;
|
|
||||||
@@ -2,37 +2,37 @@ const navbarListMenu = [
|
|||||||
{
|
{
|
||||||
id: "1",
|
id: "1",
|
||||||
name: "Desa",
|
name: "Desa",
|
||||||
href: "/desa",
|
href: "/darmasaba/desa",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: "1.1",
|
id: "1.1",
|
||||||
name: "profile",
|
name: "profile",
|
||||||
href: "/desa/profile"
|
href: "/darmasaba/desa/profile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "1.2",
|
id: "1.2",
|
||||||
name: "potensi",
|
name: "potensi",
|
||||||
href: "/desa/potensi"
|
href: "/darmasaba/desa/potensi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "1.3",
|
id: "1.3",
|
||||||
name: "berita",
|
name: "berita",
|
||||||
href: "/desa/berita"
|
href: "/darmasaba/desa/berita"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "1.4",
|
id: "1.4",
|
||||||
name: "pengumuman",
|
name: "pengumuman",
|
||||||
href: "/desa/pengumuman"
|
href: "/darmasaba/desa/pengumuman"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "1.5",
|
id: "1.5",
|
||||||
name: "galery",
|
name: "galery",
|
||||||
href: "/desa/galery"
|
href: "/darmasaba/desa/galery"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "1.6",
|
id: "1.6",
|
||||||
name: "layanan",
|
name: "layanan",
|
||||||
href: "/desa/layanan"
|
href: "/darmasaba/desa/layanan"
|
||||||
},
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
@@ -40,240 +40,240 @@ const navbarListMenu = [
|
|||||||
{
|
{
|
||||||
id: "2",
|
id: "2",
|
||||||
name: "Kesehatan",
|
name: "Kesehatan",
|
||||||
href: "/kesehatan",
|
href: "/darmasaba/kesehatan",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: "2.1",
|
id: "2.1",
|
||||||
name: "Posyandu",
|
name: "Posyandu",
|
||||||
href: "/kesehatan/posyandu"
|
href: "/darmasaba/kesehatan/posyandu"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2.2",
|
id: "2.2",
|
||||||
name: "Data Kesehatan Warga",
|
name: "Data Kesehatan Warga",
|
||||||
href: "/kesehatan/data-kesehatan-warga"
|
href: "/darmasaba/kesehatan/data-kesehatan-warga"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2.3",
|
id: "2.3",
|
||||||
name: "Puskesmas",
|
name: "Puskesmas",
|
||||||
href: "/kesehatan/puskesmas"
|
href: "/darmasaba/kesehatan/puskesmas"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2.4",
|
id: "2.4",
|
||||||
name: "Program Kesehatan",
|
name: "Program Kesehatan",
|
||||||
href: "/kesehatan/program-kesehatan"
|
href: "/darmasaba/kesehatan/program-kesehatan"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2.5",
|
id: "2.5",
|
||||||
name: "Penanganan Darurat",
|
name: "Penanganan Darurat",
|
||||||
href: "/kesehatan/penanganan-darurat"
|
href: "/darmasaba/kesehatan/penanganan-darurat"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2.6",
|
id: "2.6",
|
||||||
name: "Kontak Darurat",
|
name: "Kontak Darurat",
|
||||||
href: "/kesehatan/kontak-darurat"
|
href: "/darmasaba/kesehatan/kontak-darurat"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2.7",
|
id: "2.7",
|
||||||
name: "Info Wabah/Penyakit",
|
name: "Info Wabah/Penyakit",
|
||||||
href: "/kesehatan/info-wabah-penyakit"
|
href: "/darmasaba/kesehatan/info-wabah-penyakit"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "3",
|
id: "3",
|
||||||
name: "Keamanan",
|
name: "Keamanan",
|
||||||
href: "/keamanan",
|
href: "/darmasaba/keamanan",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: "3.1",
|
id: "3.1",
|
||||||
name: "Keamanan Lingkungan (Pecalang/Patwal)",
|
name: "Keamanan Lingkungan (Pecalang/Patwal)",
|
||||||
href: "/keamanan/keamanan-lingkungan"
|
href: "/darmasaba/keamanan/keamanan-lingkungan"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "3.2",
|
id: "3.2",
|
||||||
name: "Polsek Terdekat",
|
name: "Polsek Terdekat",
|
||||||
href: "/keamanan/polsek-terdekat"
|
href: "/darmasaba/keamanan/polsek-terdekat"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "3.3",
|
id: "3.3",
|
||||||
name: "Kontak Darurat",
|
name: "Kontak Darurat",
|
||||||
href: "/keamanan/kontak-darurat"
|
href: "/darmasaba/keamanan/kontak-darurat"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "3.4",
|
id: "3.4",
|
||||||
name: "Pencegahan Kriminalitas",
|
name: "Pencegahan Kriminalitas",
|
||||||
href: "/keamanan/pencegahan-kriminalitas"
|
href: "/darmasaba/keamanan/pencegahan-kriminalitas"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "3.5",
|
id: "3.5",
|
||||||
name: "Laporan Publik",
|
name: "Laporan Publik",
|
||||||
href: "/keamanan/laporan-publik"
|
href: "/darmasaba/keamanan/laporan-publik"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "3.6",
|
id: "3.6",
|
||||||
name: "Tips Keamanan",
|
name: "Tips Keamanan",
|
||||||
href: "/keamanan/tips-keamanan"
|
href: "/darmasaba/keamanan/tips-keamanan"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "4",
|
id: "4",
|
||||||
name: "Ekonomi",
|
name: "Ekonomi",
|
||||||
href: "/ekonomi",
|
href: "/darmasaba/ekonomi",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: "4.1",
|
id: "4.1",
|
||||||
name: "Pasar Desa",
|
name: "Pasar Desa",
|
||||||
href: "/ekonomi/pasar-desa"
|
href: "/darmasaba/ekonomi/pasar-desa"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "4.2",
|
id: "4.2",
|
||||||
name: "Koperasi",
|
name: "Koperasi",
|
||||||
href: "/ekonomi/koperasi"
|
href: "/darmasaba/ekonomi/koperasi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "4.3",
|
id: "4.3",
|
||||||
name: "UMKM",
|
name: "UMKM",
|
||||||
href: "/ekonomi/umkm"
|
href: "/darmasaba/ekonomi/umkm"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "4.4",
|
id: "4.4",
|
||||||
name: "Data Ekonomi Desa",
|
name: "Data Ekonomi Desa",
|
||||||
href: "/ekonomi/data-ekonomi-desa"
|
href: "/darmasaba/ekonomi/data-ekonomi-desa"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "4.5",
|
id: "4.5",
|
||||||
name: "Pelatihan Wirausaha",
|
name: "Pelatihan Wirausaha",
|
||||||
href: "/ekonomi/pelatihan-wirausaha"
|
href: "/darmasaba/ekonomi/pelatihan-wirausaha"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "4.6",
|
id: "4.6",
|
||||||
name: "Bantuan & Pendanaan",
|
name: "Bantuan & Pendanaan",
|
||||||
href: "/ekonomi/bantuan-pendanaan"
|
href: "/darmasaba/ekonomi/bantuan-pendanaan"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "4.7",
|
id: "4.7",
|
||||||
name: "Investasi Desa",
|
name: "Investasi Desa",
|
||||||
href: "/ekonomi/investasi-desa"
|
href: "/darmasaba/ekonomi/investasi-desa"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "4.8",
|
id: "4.8",
|
||||||
name: "Produk Unggulan",
|
name: "Produk Unggulan",
|
||||||
href: "/ekonomi/produk-unggulan"
|
href: "/darmasaba/ekonomi/produk-unggulan"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "4.9",
|
id: "4.9",
|
||||||
name: "Lowongan Kerja Lokal",
|
name: "Lowongan Kerja Lokal",
|
||||||
href: "/ekonomi/lowongan-kerja-lokal"
|
href: "/darmasaba/ekonomi/lowongan-kerja-lokal"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
id: "5",
|
id: "5",
|
||||||
name: "Inovasi",
|
name: "Inovasi",
|
||||||
href: "/inovasi",
|
href: "/darmasaba/inovasi",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: "5.1",
|
id: "5.1",
|
||||||
name: "Desa Digital/Smart Village",
|
name: "Desa Digital/Smart Village",
|
||||||
href: "/inovasi/desa-digital-smart-village"
|
href: "/darmasaba/inovasi/desa-digital-smart-village"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "5.2",
|
id: "5.2",
|
||||||
name: "Layanan Online Desa",
|
name: "Layanan Online Desa",
|
||||||
href: "/inovasi/layanan-online-desa"
|
href: "/darmasaba/inovasi/layanan-online-desa"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "5.3",
|
id: "5.3",
|
||||||
name: "Program Kreatif Desa",
|
name: "Program Kreatif Desa",
|
||||||
href: "/inovasi/program-kreatif-desa"
|
href: "/darmasaba/inovasi/program-kreatif-desa"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "5.4",
|
id: "5.4",
|
||||||
name: "Kolaborasi Inovasi",
|
name: "Kolaborasi Inovasi",
|
||||||
href: "/inovasi/kolaborasi-inovasi"
|
href: "/darmasaba/inovasi/kolaborasi-inovasi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "5.5",
|
id: "5.5",
|
||||||
name: "Info Teknologi Tepat Guna",
|
name: "Info Teknologi Tepat Guna",
|
||||||
href: "/inovasi/info-teknologi-tepat-guna"
|
href: "/darmasaba/inovasi/info-teknologi-tepat-guna"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "5.6",
|
id: "5.6",
|
||||||
name: "Ajukan Ide Inovatif",
|
name: "Ajukan Ide Inovatif",
|
||||||
href: "/inovasi/ajukan-ide-inovatif"
|
href: "/darmasaba/inovasi/ajukan-ide-inovatif"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
id: "6",
|
id: "6",
|
||||||
name: "Lingkungan",
|
name: "Lingkungan",
|
||||||
href: "/lingkungan",
|
href: "/darmasaba/lingkungan",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: "6.1",
|
id: "6.1",
|
||||||
name: "Pengelolaan Sampah (Bank Sampah)",
|
name: "Pengelolaan Sampah (Bank Sampah)",
|
||||||
href: "/lingkungan/pengelolaan-sampah-bank-sampah"
|
href: "/darmasaba/lingkungan/pengelolaan-sampah-bank-sampah"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "6.2",
|
id: "6.2",
|
||||||
name: "Program Penghijauan",
|
name: "Program Penghijauan",
|
||||||
href: "/lingkungan/program-penghijauan"
|
href: "/darmasaba/lingkungan/program-penghijauan"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "6.3",
|
id: "6.3",
|
||||||
name: "Data Lingkungan Desa",
|
name: "Data Lingkungan Desa",
|
||||||
href: "/lingkungan/data-lingkungan-desa"
|
href: "/darmasaba/lingkungan/data-lingkungan-desa"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "6.4",
|
id: "6.4",
|
||||||
name: "Gotong Royong",
|
name: "Gotong Royong",
|
||||||
href: "/lingkungan/gotong-royong"
|
href: "/darmasaba/lingkungan/gotong-royong"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "6.5",
|
id: "6.5",
|
||||||
name: "Edukasi Lingkungan",
|
name: "Edukasi Lingkungan",
|
||||||
href: "/lingkungan/edukasi-lingkungan"
|
href: "/darmasaba/lingkungan/edukasi-lingkungan"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "6.6",
|
id: "6.6",
|
||||||
name: "Konservasi Adat Bali",
|
name: "Konservasi Adat Bali",
|
||||||
href: "/lingkungan/konservasi-adat-bali"
|
href: "/darmasaba/lingkungan/konservasi-adat-bali"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
id: "7",
|
id: "7",
|
||||||
name: "Pendidikan",
|
name: "Pendidikan",
|
||||||
href: "/pendidikan",
|
href: "/darmasaba/pendidikan",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: "7.1",
|
id: "7.1",
|
||||||
name: "Info Sekolah & PAUD",
|
name: "Info Sekolah & PAUD",
|
||||||
href: "/pendidikan/info-sekolah-paud"
|
href: "/darmasaba/pendidikan/info-sekolah-paud"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "7.2",
|
id: "7.2",
|
||||||
name: "Beasiswa Desa",
|
name: "Beasiswa Desa",
|
||||||
href: "/pendidikan/beasiswa-desa"
|
href: "/darmasaba/pendidikan/beasiswa-desa"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "7.3",
|
id: "7.3",
|
||||||
name: "Program Pendidikan Anak",
|
name: "Program Pendidikan Anak",
|
||||||
href: "/pendidikan/program-pendidikan-anak"
|
href: "/darmasaba/pendidikan/program-pendidikan-anak"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "7.4",
|
id: "7.4",
|
||||||
name: "Bimbingan Belajar Desa",
|
name: "Bimbingan Belajar Desa",
|
||||||
href: "/pendidikan/bimbingan-belajar-desa"
|
href: "/darmasaba/pendidikan/bimbingan-belajar-desa"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "7.5",
|
id: "7.5",
|
||||||
name: "Pendidikan Non Formal",
|
name: "Pendidikan Non Formal",
|
||||||
href: "/pendidikan/pendidikan-non-formal"
|
href: "/darmasaba/pendidikan/pendidikan-non-formal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "7.6",
|
id: "7.6",
|
||||||
name: "Perpustakaan Desa",
|
name: "Perpustakaan Desa",
|
||||||
href: "/pendidikan/perpustakaan-desa"
|
href: "/darmasaba/pendidikan/perpustakaan-desa"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/con/router.ts
Normal file
40
src/con/router.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
|
||||||
|
const pages = {
|
||||||
|
"home": "/",
|
||||||
|
"darmasaba": {
|
||||||
|
"home": "/daramasaba",
|
||||||
|
"desa": "/darmasaba/desa",
|
||||||
|
"ekonomi": "/darmasaba/ekonomi",
|
||||||
|
"keamanan": "/darmasaba/keamanan",
|
||||||
|
"kesehatan": "/darmasaba/kesehatan",
|
||||||
|
"inovasi": "/darmasaba/inovasi",
|
||||||
|
"lingkungan": "/darmasaba/lingkungan",
|
||||||
|
"pendidikan": "/darmasaba/pendidikan",
|
||||||
|
"module": {
|
||||||
|
"daves": "/darmasaba/module/daves",
|
||||||
|
"mangan": "/darmasaba/module/mangan",
|
||||||
|
"bicara-darma": "/darmasaba/module/bicara-darma",
|
||||||
|
"bares": "/darmasaba/module/bares",
|
||||||
|
"sajjana-dharma-raksaka": "/darmasaba/module/sajjana-dharma-raksaka",
|
||||||
|
"pdkt": "/darmasaba/module/pdkt",
|
||||||
|
"gelah-melah": "/darmasaba/module/gelah-melah",
|
||||||
|
"inovasi-desa-darmasaba": "/darmasaba/module/inovasi-desa-darmasaba"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const apies = {
|
||||||
|
"/api": "home",
|
||||||
|
"/api/about": "about",
|
||||||
|
"/api/contact": "contact",
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = {
|
||||||
|
pages,
|
||||||
|
apies
|
||||||
|
} as const
|
||||||
|
export default router
|
||||||
67
src/schema.ts
Normal file
67
src/schema.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { SchemaObject } from '@paljs/types'
|
||||||
|
|
||||||
|
export const schema: SchemaObject = {
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
name: 'Layanan',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'String',
|
||||||
|
isId: true,
|
||||||
|
unique: false,
|
||||||
|
defaultValue: 'cuid()',
|
||||||
|
list: false,
|
||||||
|
required: true,
|
||||||
|
kind: 'scalar',
|
||||||
|
documentation: '',
|
||||||
|
relationField: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'String',
|
||||||
|
isId: false,
|
||||||
|
unique: true,
|
||||||
|
list: false,
|
||||||
|
required: true,
|
||||||
|
kind: 'scalar',
|
||||||
|
documentation: '',
|
||||||
|
relationField: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
documentation: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Potensi',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'String',
|
||||||
|
isId: true,
|
||||||
|
unique: false,
|
||||||
|
defaultValue: 'cuid()',
|
||||||
|
list: false,
|
||||||
|
required: true,
|
||||||
|
kind: 'scalar',
|
||||||
|
documentation: '',
|
||||||
|
relationField: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'String',
|
||||||
|
isId: false,
|
||||||
|
unique: true,
|
||||||
|
list: false,
|
||||||
|
required: true,
|
||||||
|
kind: 'scalar',
|
||||||
|
documentation: '',
|
||||||
|
relationField: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
documentation: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
enums: [],
|
||||||
|
dataSource: { provider: 'postgresql', url: 'env("DATABASE_URL")' },
|
||||||
|
generators: [{ name: 'client', provider: 'prisma-client-js' }],
|
||||||
|
}
|
||||||
38
src/state/state-list-image.ts
Normal file
38
src/state/state-list-image.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
|
||||||
|
const stateListImage = proxy<{
|
||||||
|
list: { name: string; url: string; total: number }[] | null;
|
||||||
|
page: number;
|
||||||
|
count: number;
|
||||||
|
total: number | undefined;
|
||||||
|
load: (params?: { search?: string }) => Promise<void>;
|
||||||
|
del: ({ name }: { name: string }) => Promise<void>;
|
||||||
|
}>({
|
||||||
|
list: null,
|
||||||
|
page: 1,
|
||||||
|
count: 20,
|
||||||
|
total: undefined,
|
||||||
|
async load(params?: { search?: string }) {
|
||||||
|
const { search = "" } = params ?? {};
|
||||||
|
const { data } = await ApiFetch.api.imgs.get({
|
||||||
|
query: {
|
||||||
|
page: this.page,
|
||||||
|
count: this.count,
|
||||||
|
search,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.list = data;
|
||||||
|
if (data?.[0]?.total) {
|
||||||
|
this.total = Math.ceil(data[0].total / this.count);
|
||||||
|
} else {
|
||||||
|
this.total = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async del({ name }: { name: string }) {
|
||||||
|
await ApiFetch.api.img({ name }).delete();
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default stateListImage;
|
||||||
@@ -2,5 +2,8 @@ declare module "bun" {
|
|||||||
interface Env {
|
interface Env {
|
||||||
PORT: string;
|
PORT: string;
|
||||||
NEXT_PUBLIC_WIBU_URL: string;
|
NEXT_PUBLIC_WIBU_URL: string;
|
||||||
|
DATABASE_URL: string;
|
||||||
|
WIBU_UPLOAD_DIR: string;
|
||||||
|
WIBU_CACHE_DIR: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"telemetry": {
|
|
||||||
"notifiedAt": "1739668984025",
|
|
||||||
"anonymousId": "da4db3546c4d6315299db5f269034c4669decd44840966bd3dc435bc184f5d61",
|
|
||||||
"salt": "f26f15c27a3239f30152845755757b47"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user