tambahannnya

This commit is contained in:
bipproduction
2025-02-15 21:56:18 +08:00
parent 00355814bc
commit 2115af8126
84 changed files with 2189 additions and 320 deletions

111
NOTE.md Normal file
View File

@@ -0,0 +1,111 @@
#!/bin/bash
# Load environment variables from .env file
if [ -f ".env" ]; then
echo "Loading environment variables from .env..."
export $(grep -v '^#' .env | xargs)
else
echo ".env file not found! Please create a .env file with the required variables."
exit 1
fi
# Konfigurasi
PROJECT_NAME="hipmi"
REPO_NAME="hipmi"
PROJECT_PATH="/root/projects/staging/$PROJECT_NAME/"
RELEASE_NAME=$1
RELEASE_PATH="$PROJECT_PATH/releases"
ENV_PATH="$PROJECT_PATH/shared/env"
SCRIPT_PATH="$PROJECT_PATH/scripts"
ENV_NAME="staging"
REPO="https://github.com/bipproduction/$REPO_NAME.git"
BRANCH="staging"
# Jika RELEASE_NAME tidak diberikan, ambil SHA dari commit terakhir di branch staging
if [ -z "$RELEASE_NAME" ]; then
echo "No release name provided. Fetching the latest commit SHA from the $BRANCH branch..."
if [ -z "$TOKEN" ]; then
echo "GitHub token not found in .env file!"
exit 1
fi
# Ambil SHA commit terbaru dari API GitHub
API_URL="https://api.github.com/repos/bipproduction/$REPO_NAME/branches/$BRANCH"
RESPONSE=$(curl -s -H "Authorization: token $TOKEN" "$API_URL")
# Ekstrak SHA commit terbaru
SHA=$(echo "$RESPONSE" | grep -oP '"sha":\s*"\K[^"]+' | head -n 1)
if [ -z "$SHA" ]; then
echo "Failed to fetch the latest commit SHA!"
echo "API Response: $RESPONSE"
exit 1
fi
# Validasi panjang SHA (harus 40 karakter)
if [ ${#SHA} -ne 40 ]; then
echo "Invalid SHA length: $SHA"
exit 1
fi
RELEASE_NAME=$SHA
echo "Using commit SHA as release name: $RELEASE_NAME"
fi
# VALIDASI INPUT
if [ -z "$RELEASE_NAME" ]; then
echo "Release name is empty! Please provide a valid release name or allow fetching the latest commit SHA."
exit 1
fi
# CEK EXISTING DIRECTORY
if [ -d "$RELEASE_PATH/$RELEASE_NAME" ]; then
echo "Error: Directory '$RELEASE_PATH/$RELEASE_NAME' already exists. Please choose a different release name."
exit 1
fi
# CLONE REPOSITORY
echo "Cloning repository..."
git clone -b "$BRANCH" "$REPO" "$RELEASE_PATH/$RELEASE_NAME" || { echo "Failed to clone repository!"; exit 1; }
# CHECK ENVIRONMENT FILE
if [ ! -f "$ENV_PATH/.env.$ENV_NAME" ]; then
echo "Environment file $ENV_PATH/.env.$ENV_NAME not found!"
exit 1
fi
# COPY ENV FILE
cp "$ENV_PATH/.env.$ENV_NAME" "$RELEASE_PATH/$RELEASE_NAME/.env"
# NAVIGATE TO RELEASE DIRECTORY
cd "$RELEASE_PATH/$RELEASE_NAME" || { echo "Failed to navigate to directory!"; exit 1; }
# INSTALL DEPENDENCIES
echo "Installing dependencies with --smol flag..."
bun i || { echo "Install dependencies failed!"; exit 1; }
# BUILD RELEASE
echo "Building release with --smol flag..."
nice -n 19 bun --smol run build || { echo "Build failed!"; exit 1; }
# PUSH PRISMA
echo "Pushing Prisma schema to database..."
nice -n 19 bunx prisma db push || { echo "Prisma DB push failed!"; exit 1; }
# PROMOTE RELEASE
SOURCE_DIR="$RELEASE_PATH/$RELEASE_NAME"
TARGET_DIR="$PROJECT_PATH/current"
if [ -L "$TARGET_DIR" ]; then
echo "Removing existing symlink at $TARGET_DIR..."
rm "$TARGET_DIR" || { echo "Failed to remove existing symlink!"; exit 1; }
fi
ln -s "$SOURCE_DIR" "$TARGET_DIR" || { echo "Failed to create symlink!"; exit 1; }
if [ ! -L "$TARGET_DIR" ]; then
echo "Error: Symlink creation failed!"
exit 1
fi
echo "Symlink successfully created from $SOURCE_DIR to $TARGET_DIR"
# RESTART SERVER
echo "Restarting server..."
pm2 restart "$PROJECT_NAME-$ENV_NAME" || { echo "Failed to restart $PROJECT_NAME-$ENV_NAME namespace!"; exit 1; }

BIN
bun.lockb

Binary file not shown.

BIN
compressed_pdf.pdf Normal file

Binary file not shown.

View File

@@ -1,7 +1,20 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
async headers() {
return [
{
source: '/assets/:path*', // Path ke folder gambar
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=3600, stale-while-revalidate=600', // Cache selama 1 jam, validasi ulang setelah 10 menit
},
],
},
];
},
};
export default nextConfig;

View File

@@ -9,17 +9,46 @@
"lint": "next lint"
},
"dependencies": {
"@elysiajs/cors": "^1.2.0",
"@elysiajs/eden": "^1.2.0",
"@elysiajs/stream": "^1.1.0",
"@elysiajs/swagger": "^1.2.0",
"@mantine/carousel": "^7.16.2",
"@mantine/core": "^7.16.2",
"@mantine/hooks": "^7.16.2",
"@prisma/client": "^6.3.1",
"@types/bun": "^1.2.2",
"@types/lodash": "^4.17.15",
"add": "^2.0.6",
"animate.css": "^4.1.1",
"compress-pdf": "^0.5.2",
"elysia": "^1.2.12",
"embla-carousel-autoplay": "^8.5.2",
"embla-carousel-react": "^7.1.0",
"framer-motion": "^12.4.1",
"lodash": "^4.17.21",
"motion": "^12.4.1",
"next": "15.1.6",
"next-view-transitions": "^0.3.4",
"prisma": "^6.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "15.1.6"
"react-multi-carousel": "^2.8.5",
"react-scroll-motion": "^0.3.5",
"readdirp": "^4.1.1",
"swr": "^2.3.2",
"valtio": "^2.1.3"
},
"devDependencies": {
"typescript": "^5",
"@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.1.6",
"@eslint/eslintrc": "^3"
"postcss": "^8.5.1",
"postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1",
"typescript": "^5"
}
}

14
postcss.config.cjs Normal file
View File

@@ -0,0 +1,14 @@
module.exports = {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
variables: {
'mantine-breakpoint-xs': '36em',
'mantine-breakpoint-sm': '48em',
'mantine-breakpoint-md': '62em',
'mantine-breakpoint-lg': '75em',
'mantine-breakpoint-xl': '88em',
},
},
},
};

View File

@@ -0,0 +1,35 @@
[
{
"name": "surat keterangan domisili organisasi"
},
{
"name": "surat keterangan penghasilan"
},
{
"name": "surat keterangan tidak mampu"
},
{
"name": "surat keterangan kelahiran"
},
{
"name": "surat keterangan usaha"
},
{
"name": "surat keterangan tempat usaha"
},
{
"name": "surat keterangan belum kawin"
},
{
"name": "surat keterangan kelakuan baik (pengantar skck)"
},
{
"name": "surat keterangan kematian"
},
{
"name": "surat keterangan beda biodata diri"
},
{
"name": "surat keterangan yatim / piatu / yatim piatu"
}
]

View File

@@ -0,0 +1,41 @@
[
{
"name": "TPS3R Pudak Mesari"
},
{
"name": "Bumdes Pudak Mesari"
},
{
"name": "Pertanian"
},
{
"name": "Jogging Track Tegeh Aban, Karang Gadon dan Munduk Uma Desa"
},
{
"name": "Taman Beji Cengana"
},
{
"name": "Dam Tanah Putih"
},
{
"name": "Gumuh Sari Water Park"
},
{
"name": "UMKM"
},
{
"name": "Kawasan Kuliner"
},
{
"name": "IKM berbasis Pengolahan Pangan"
},
{
"name": "Genteng"
},
{
"name": "Peternakan Ikan Lele"
},
{
"name": "Pemotongan Daging"
}
]

24
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,24 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Layanan {
id String @id @default(cuid())
name String @unique
}
model Potensi {
id String @id @default(cuid())
name String @unique
}

39
prisma/seed.ts Normal file
View File

@@ -0,0 +1,39 @@
import layanan from './data/list-layanan.json'
import potensi from './data/list-potensi.json'
import prisma from '@/lib/prisma';
; (async () => {
for (const l of layanan) {
await prisma.layanan.upsert({
where: {
name: l.name
},
update: {
name: l.name
},
create: {
name: l.name
}
})
}
console.log("layanan success ...")
for (const p of potensi) {
await prisma.potensi.upsert({
where: {
name: p.name
},
update: {
name: p.name
},
create: {
name: p.name
}
})
}
console.log("potensi success ...")
})();

View File

@@ -0,0 +1,35 @@
[
{
"name": "surat keterangan domisili organisasi"
},
{
"name": "surat keterangan penghasilan"
},
{
"name": "surat keterangan tidak mampu"
},
{
"name": "surat keterangan kelahiran"
},
{
"name": "surat keterangan usaha"
},
{
"name": "surat keterangan tempat usaha"
},
{
"name": "surat keterangan belum kawin"
},
{
"name": "surat keterangan kelakuan baik (pengantar skck)"
},
{
"name": "surat keterangan kematian"
},
{
"name": "surat keterangan beda biodata diri"
},
{
"name": "surat keterangan yatim / piatu / yatim piatu"
}
]

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

BIN
public/assets/images/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

View File

@@ -0,0 +1,11 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client";
import { useReportWebVitals } from "next/web-vitals";
export function WebVitals() {
useReportWebVitals((metric) => {
// console.log(metric);
});
return null;
}

128
src/app/animate/page.tsx Normal file
View File

@@ -0,0 +1,128 @@
"use client"
import {
motion,
MotionValue,
useScroll,
useSpring,
useTransform,
} from "motion/react"
import { useRef } from "react"
function useParallax(value: MotionValue<number>, distance: number) {
return useTransform(value, [0, 1], [-distance, distance])
}
function Image({ id }: { id: number }) {
const ref = useRef(null)
const { scrollYProgress } = useScroll({ target: ref })
const y = useParallax(scrollYProgress, 300)
return (
<section className="img-container">
<div ref={ref}>
<img
src={`https://placehold.co/40${id}`}
alt="A London skyscraper"
/>
</div>
<motion.h2
// Hide until scroll progress is measured
initial={{ visibility: "hidden" }}
animate={{ visibility: "visible" }}
style={{ y }}
>{`#00${id}`}</motion.h2>
</section>
)
}
export default function Parallax() {
const { scrollYProgress } = useScroll()
const scaleX = useSpring(scrollYProgress, {
stiffness: 100,
damping: 30,
restDelta: 0.001,
})
return (
<div id="example">
{[1, 2, 3, 4, 5].map((image) => (
<Image key={image} id={image} />
))}
<motion.div className="progress" style={{ scaleX }} />
<StyleSheet />
</div>
)
}
/**
* ============== Styles ================
*/
function StyleSheet() {
return (
<style>{`
html {
scroll-snap-type: y mandatory;
}
.img-container {
height: 100vh;
scroll-snap-align: start;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.img-container > div {
width: 300px;
height: 400px;
margin: 20px;
background: #f5f5f5;
overflow: hidden;
}
.img-container img {
width: 300px;
height: 400px;
}
@media (max-width: 500px) {
.img-container > div {
width: 150px;
height: 200px;
}
.img-container img {
width: 150px;
height: 200px;
}
}
.img-container h2 {
color: #4ff0b7;
margin: 0;
font-family: JetBrains Mono, monospace;
font-size: 50px;
font-weight: 700;
letter-spacing: -3px;
line-height: 1.2;
position: absolute;
display: inline-block;
top: calc(50% - 25px);
left: calc(50% + 120px);
}
.progress {
position: fixed;
left: 0;
right: 0;
height: 5px;
background: #4ff0b7;
bottom: 50px;
transform: scaleX(0);
}
`}</style>
)
}

View File

@@ -0,0 +1,10 @@
import prisma from "@/lib/prisma";
async function getPotensi() {
const data = await prisma.potensi.findMany()
return {
data
}
}
export default getPotensi

View File

@@ -0,0 +1,38 @@
import prisma from "@/lib/prisma";
import cors, { HTTPMethod } from "@elysiajs/cors";
import swagger from "@elysiajs/swagger";
import { Elysia } from "elysia";
import getPotensi from "./_lib/get-potensi";
const corsConfig = {
origin: "*",
methods: ["GET", "POST", "PATCH", "DELETE", "PUT"] as HTTPMethod[],
allowedHeaders: "*",
exposedHeaders: "*",
maxAge: 5,
credentials: true,
};
async function layanan() {
const data = await prisma.layanan.findMany();
return { data };
}
const ApiServer = new Elysia()
.use(swagger({ path: "/api/docs" }))
.use(cors(corsConfig))
.group("/api", app => app
.get("/layanan", layanan)
.get("/potensi", getPotensi)
)
export const GET = ApiServer.handle;
export const POST = ApiServer.handle;
export const PATCH = ApiServer.handle;
export const DELETE = ApiServer.handle;
export const PUT = ApiServer.handle;
export type AppServer = typeof ApiServer

7
src/app/desa/page.tsx Normal file
View File

@@ -0,0 +1,7 @@
import { Stack } from "@mantine/core";
export default function Page() {
return <Stack>
desa
</Stack>
}

7
src/app/ekonomi/page.tsx Normal file
View File

@@ -0,0 +1,7 @@
import { Stack } from "@mantine/core";
export default function Page() {
return <Stack>
ekonomi
</Stack>
}

View File

@@ -1,42 +1,31 @@
:root {
--background: #ffffff;
--foreground: #171717;
/* styles/globals.css */
@font-face {
font-family: 'San Francisco';
src: url('/assets/fonts/font.otf') format('opentype');
font-weight: normal;
font-style: normal;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
.glass {
background: rgba(255, 255, 255, 0.2);
-webkit-backdrop-filter: blur(40px);
backdrop-filter: blur(40px);
position: fixed;
z-index: 1;
width: 100%;
height: 100vh;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
.glass2 {
background: rgba(255, 255, 255, 0.3);
-webkit-backdrop-filter: blur(40px);
backdrop-filter: blur(40px);
position: fixed;
z-index: 1;
}
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
.glass3 {
background: rgba(255, 255, 255, 0.3);
-webkit-backdrop-filter: blur(40px);
backdrop-filter: blur(40px);
}

7
src/app/inovasi/page.tsx Normal file
View File

@@ -0,0 +1,7 @@
import { Stack } from "@mantine/core";
export default function Page() {
return <Stack>
inovasi
</Stack>
}

View File

@@ -0,0 +1,7 @@
import { Stack } from "@mantine/core";
export default function Page() {
return <Stack>
keamanan
</Stack>
}

View File

@@ -0,0 +1,7 @@
import { Stack } from "@mantine/core";
export default function Page() {
return <Stack>
kesehatan
</Stack>
}

View File

@@ -1,32 +1,60 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
// Import styles of packages that you've installed.
// All packages except `@mantine/hooks` require styles imports
import "@mantine/carousel/styles.css";
import "@mantine/core/styles.css";
import "animate.css";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
import LoadDataFirstClient from "@/com/LoadDataFirstClient";
import {
ColorSchemeScript,
MantineProvider,
createTheme,
mantineHtmlProps,
} from "@mantine/core";
import { MainLayout } from "../com/MainLayout";
import { ViewTransitions } from "next-view-transitions";
import { WebVitals } from "./_com/WebVitals";
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
export const metadata = {
title: "desa darmasaba",
description: "Desa Darmasaba Kabupaten Badung",
};
const theme = createTheme({
fontFamily:
"San Francisco, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif",
fontFamilyMonospace:
"SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace",
headings: { fontFamily: "San Francisco, sans-serif" },
});
export default function RootLayout({
children,
}: Readonly<{
}: {
children: React.ReactNode;
}>) {
}) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable}`}>
<ViewTransitions>
<html lang="en" {...mantineHtmlProps}>
<head>
<ColorSchemeScript />
<link
rel="icon"
href="/assets/images/darmasaba-icon.png"
sizes="any"
/>
</head>
<body>
<MantineProvider theme={theme}>
<MainLayout>
<WebVitals />
{children}
</MainLayout>
</MantineProvider>
</body>
<LoadDataFirstClient />
</html>
</ViewTransitions>
);
}

View File

@@ -0,0 +1,7 @@
import { Stack } from "@mantine/core";
export default function Page() {
return <Stack>
lingkungan
</Stack>
}

15
src/app/module/page.tsx Normal file
View File

@@ -0,0 +1,15 @@
'use client'
import { Stack } from "@mantine/core"
import { useSnapshot } from "valtio"
import stateNav from "@/state/state-nav"
export default function Page() {
const { module, isSearch } = useSnapshot(stateNav)
return (
<Stack h={"100vh"} >
{JSON.stringify({ module, isSearch })}
</Stack>
)
}

View File

@@ -1,168 +0,0 @@
.page {
--gray-rgb: 0, 0, 0;
--gray-alpha-200: rgba(var(--gray-rgb), 0.08);
--gray-alpha-100: rgba(var(--gray-rgb), 0.05);
--button-primary-hover: #383838;
--button-secondary-hover: #f2f2f2;
display: grid;
grid-template-rows: 20px 1fr 20px;
align-items: center;
justify-items: center;
min-height: 100svh;
padding: 80px;
gap: 64px;
font-family: var(--font-geist-sans);
}
@media (prefers-color-scheme: dark) {
.page {
--gray-rgb: 255, 255, 255;
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
--button-primary-hover: #ccc;
--button-secondary-hover: #1a1a1a;
}
}
.main {
display: flex;
flex-direction: column;
gap: 32px;
grid-row-start: 2;
}
.main ol {
font-family: var(--font-geist-mono);
padding-left: 0;
margin: 0;
font-size: 14px;
line-height: 24px;
letter-spacing: -0.01em;
list-style-position: inside;
}
.main li:not(:last-of-type) {
margin-bottom: 8px;
}
.main code {
font-family: inherit;
background: var(--gray-alpha-100);
padding: 2px 4px;
border-radius: 4px;
font-weight: 600;
}
.ctas {
display: flex;
gap: 16px;
}
.ctas a {
appearance: none;
border-radius: 128px;
height: 48px;
padding: 0 20px;
border: none;
border: 1px solid transparent;
transition:
background 0.2s,
color 0.2s,
border-color 0.2s;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 20px;
font-weight: 500;
}
a.primary {
background: var(--foreground);
color: var(--background);
gap: 8px;
}
a.secondary {
border-color: var(--gray-alpha-200);
min-width: 180px;
}
.footer {
grid-row-start: 3;
display: flex;
gap: 24px;
}
.footer a {
display: flex;
align-items: center;
gap: 8px;
}
.footer img {
flex-shrink: 0;
}
/* Enable hover only on non-touch devices */
@media (hover: hover) and (pointer: fine) {
a.primary:hover {
background: var(--button-primary-hover);
border-color: transparent;
}
a.secondary:hover {
background: var(--button-secondary-hover);
border-color: transparent;
}
.footer a:hover {
text-decoration: underline;
text-underline-offset: 4px;
}
}
@media (max-width: 600px) {
.page {
padding: 32px;
padding-bottom: 80px;
}
.main {
align-items: center;
}
.main ol {
text-align: center;
}
.ctas {
flex-direction: column;
}
.ctas a {
font-size: 14px;
height: 40px;
padding: 0 16px;
}
a.secondary {
min-width: auto;
}
.footer {
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
}
@media (prefers-color-scheme: dark) {
.logo {
filter: invert();
}
}

View File

@@ -1,95 +1,32 @@
import Image from "next/image";
import styles from "./page.module.css";
import Content1 from "@/com/main-page/content-1";
import Content2 from "@/com/main-page/content-2";
import Content3 from "@/com/main-page/content-3";
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 ApiFetch from "@/lib/api-fetch";
import { Stack } from "@mantine/core";
export default function Home() {
export default function Page() {
return (
<div className={styles.page}>
<main className={styles.main}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol>
<li>
Get started by editing <code>src/app/page.tsx</code>.
</li>
<li>Save and see your changes instantly.</li>
</ol>
<div className={styles.ctas}>
<a
className={styles.primary}
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className={styles.logo}
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
className={styles.secondary}
>
Read our docs
</a>
</div>
</main>
<footer className={styles.footer}>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div>
<Stack bg={colors.grey[1]} gap={"4rem"}>
<Content1 />
<Content2 />
<Content3 />
<Content4 />
<Content5 />
<Content6 />
</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} />;
// }

View File

@@ -0,0 +1,7 @@
import { Stack } from "@mantine/core";
export default function Page() {
return <Stack>
pendidikan
</Stack>
}

14
src/com/Footer.tsx Normal file
View File

@@ -0,0 +1,14 @@
import { Stack, Container, Center, Text } from "@mantine/core";
function Footer() {
return <Stack>
<Container w={{ base: "100%", md: "80%" }} p={"xl"} h={720} >
<Center>
<Text fz={"3.4rem"}>Footer</Text>
</Center>
</Container>
</Stack>
}
export default Footer;

View File

@@ -0,0 +1,8 @@
"use client";
export default function LoadDataFirstClient() {
return null;
}

17
src/com/MainLayout.tsx Normal file
View File

@@ -0,0 +1,17 @@
import { Space, Stack } from "@mantine/core";
import { Navbar } from "./Navbar";
import Footer from "./Footer";
export function MainLayout({ children }: { children: React.ReactNode }) {
return (
<Stack gap={0}>
<Navbar />
<Space h={{
base: "2.2rem",
md: "2.5rem"
}} />
{children}
<Footer />
</Stack>
)
}

23
src/com/NavBarSearch.tsx Normal file
View File

@@ -0,0 +1,23 @@
import stateNav from "@/state/state-nav";
import { Container, Stack, TextInput } from "@mantine/core";
export function NavbarSearch() {
return <Container w={{
base: '100%',
md: '80%',
}} fluid py={"xl"}
onMouseLeave={stateNav.clear}
>
<Stack pt={"xl"}>
<TextInput
autoFocus
styles={{
input: {
borderRadius: "xl",
color: "black",
// backgroundColor: "rgba(255, 255, 255, 0.3)"
}
}} size="lg" variant="transparent" placeholder="Cari" />
</Stack>
</Container>
}

32
src/com/Navbar.tsx Normal file
View File

@@ -0,0 +1,32 @@
"use client";
import colors from "@/con/colors";
import navbarListMenu from "@/con/navbar-list-menu";
import stateNav from "@/state/state-nav";
import { Box, Burger, Group } from "@mantine/core";
import { useSnapshot } from "valtio";
import { NavbarMainMenu } from "./NavbarMainMenu";
export function Navbar() {
const { item, isSearch } = useSnapshot(stateNav);
return (
<Box>
<Box
className="glass2"
w={"100%"}
pos={"fixed"}
top={0}
style={{
zIndex: 100,
}}
>
<NavbarMainMenu listNavbar={navbarListMenu} />
<Group justify="end">
<Box hiddenFrom="sm">
<Burger color={colors["blue-button"]} />
</Box>
</Group>
</Box>
{(item || isSearch) && <Box className="glass" />}
</Box>
);
}

View File

@@ -0,0 +1,70 @@
'use client'
import colors from "@/con/colors"
import stateNav from "@/state/state-nav"
import { ActionIcon, Button, Container, Flex, Image, Stack } from "@mantine/core"
import { useHover } from "@mantine/hooks"
import { useTransitionRouter } from 'next-view-transitions'
import { useSnapshot } from "valtio"
import { MenuItem } from "../../types/menu-item"
import { NavbarSearch } from "./NavBarSearch"
import { NavbarSubMenu } from "./NavbarSubMenu"
export function NavbarMainMenu({ listNavbar }: {
listNavbar: MenuItem[]
}) {
const { item, isSearch } = useSnapshot(stateNav)
const router = useTransitionRouter()
return <Stack gap={0} visibleFrom="sm" bg={colors["white-trans-1"]}>
<Container pos={"relative"} w={{
base: '100%',
md: '80%',
}} fluid>
<Flex align={"center"} justify={"space-between"} wrap={{
base: "wrap",
md: "nowrap"
}}>
<ActionIcon radius={"100"} onClick={() => {
router.push("/")
stateNav.clear()
}} >
<Image radius={"100"} src={"/assets/images/darmasaba-icon.png"} alt="icon" w={24} h={24} loading="lazy" />
</ActionIcon>
{listNavbar.map((item, k) => {
return <MenuItemCom key={k} item={item} />
})}
<ActionIcon variant="transparent" c={isSearch ? 'grey' : colors["blue-button"]}
onClick={() => {
stateNav.item = null
stateNav.isSearch = !stateNav.isSearch
}}
>
{/* TODO: add icon search */}
{/* <MdSearch size={"1.5rem"} /> */}
</ActionIcon>
</Flex>
</Container>
{item && <NavbarSubMenu item={item as MenuItem[]} />}
{isSearch && <NavbarSearch />}
</Stack>
}
function MenuItemCom({ item, }: { item: MenuItem }) {
const { ref, hovered } = useHover()
const router = useTransitionRouter()
return <Button
ref={ref}
color={hovered ? "grey" : colors["blue-button"]}
onMouseEnter={() => {
stateNav.item = item.children || null
stateNav.isSearch = false
}}
variant="transparent"
onClick={() => {
router.push(item.href)
stateNav.clear()
}}
>{item.name}</Button>
}

50
src/com/NavbarSubMenu.tsx Normal file
View File

@@ -0,0 +1,50 @@
"use client";
import stateNav from "@/state/state-nav";
import { Button, Container, Stack } from "@mantine/core";
import { motion } from "motion/react";
import Link from "next/link";
import { MenuItem } from "../../types/menu-item";
import _ from "lodash";
export function NavbarSubMenu({ item }: { item: MenuItem[] | null }) {
return (
<motion.div
key={_.uniqueId()}
initial={{ opacity: 0.5 }}
animate={{ opacity: 1 }}
transition={{ duration: 1 }}
>
<Container
key={stateNav.item?.[0]?.id}
onMouseLeave={() => {
stateNav.item = null;
stateNav.isSearch = false;
}}
w={{
base: "100%",
md: "80%",
}}
fluid
>
<Stack gap={0} align="start" py={"xl"}>
{item &&
item.map((item, k) => {
return (
<Button
fz={"lg"}
color="dark.9"
variant="transparent"
component={Link}
href={item.href}
key={k}
>
{item.name}
</Button>
);
})}
</Stack>
</Container>
</motion.div>
);
}

View File

@@ -0,0 +1,49 @@
import images from "@/con/images";
import stateNav from "@/state/state-nav";
import { Card, Image, SimpleGrid, Stack } from "@mantine/core";
import { useHover } from "@mantine/hooks";
import { useTransitionRouter } from 'next-view-transitions'
const listImageModule = Object.values(images.module);
function ModuleItem({ item }: { item: string }) {
const router = useTransitionRouter();
const { ref, hovered } = useHover();
return (
<Card
onClick={() => {
stateNav.module = item;
router.push("/module");
}}
ref={ref}
p={"md"}
bg={"white"}
radius={"32"}
style={{
border: `2px solid ${hovered ? "lightgray" : "transparent"}`,
}}
>
<Stack justify="end" h={"100%"}>
<Image src={item} alt="icon" fit="contain" loading="lazy" />
</Stack>
</Card>
);
}
function ModuleView() {
return (
<SimpleGrid
cols={{
base: 3,
md: 4,
}}
>
{listImageModule.map((item, k) => {
return <ModuleItem key={k} item={item} />;
})}
</SimpleGrid>
);
}
export default ModuleView;

View File

@@ -0,0 +1,25 @@
import images from "@/con/images";
import { Flex, ActionIcon, Image } from "@mantine/core";
function SosmedView() {
const listSosmed = Object.values(images.sosmed);
return (
<Flex gap={"md"}>
{listSosmed.map((item, k) => {
return (
<ActionIcon
variant="transparent"
key={k}
w={32}
h={32}
pos={"relative"}
>
<Image src={item} alt="icon" loading="lazy" fit="contain" />
</ActionIcon>
);
})}
</Flex>
);
}
export default SosmedView;

View File

@@ -0,0 +1,203 @@
"use client";
import colors from "@/con/colors";
import {
BackgroundImage,
Flex,
Box,
Card,
Stack,
Image,
Text,
Button,
Title,
} from "@mantine/core";
import ModuleView from "./ModuleView";
import SosmedView from "./SosmedView";
function Content1() {
return (
<BackgroundImage src={"/assets/images/bg-blur.png"}>
<Flex
gap={"md"}
wrap={{
base: "wrap",
md: "nowrap",
}}
>
<Stack
gap={"xl"}
w={{
base: "100%",
md: "60%",
}}
h={820}
py={{
base: "xs",
md: "72",
}}
px={{
base: "xs",
md: "100",
}}
>
<Card
radius={"32"}
bg={colors.grey[1]}
p={{
base: "xs",
md: "32",
}}
>
<Stack gap={42}>
<Flex
gap={"md"}
wrap={{
base: "wrap",
md: "nowrap",
}}
>
<Flex gap={"md"} flex={1}>
<Box
bg={"white"}
w={{
base: 64,
md: 72,
}}
h={{
base: 64,
md: 72,
}}
style={{
borderRadius: 24,
}}
p={"sm"}
>
<Image
src={"/assets/images/darmasaba-icon.png"}
alt="icon"
loading="lazy"
fit="contain"
/>
</Box>
<Box
w={{
base: 64,
md: 72,
}}
h={{
base: 64,
md: 72,
}}
style={{
borderRadius: 24,
}}
p={"sm"}
bg={"white"}
>
<Image
src={"/assets/images/pudak-icon.png"}
alt="icon"
fit="contain"
loading="lazy"
/>
</Box>
</Flex>
<Stack flex={2} gap={0} justify="end" c={colors["blue-button"]}>
<Text
fz={{
base: "1.5rem",
md: "1.4rem",
}}
>
Pemerintah Desa
</Text>
<Title>DARMASABA</Title>
{/* <Text
fw={"bold"}
fz={{
base: "3rem",
md: "3rem",
sm: ""
}}
>
DARMASABA
</Text> */}
</Stack>
</Flex>
<ModuleView />
<SosmedView />
</Stack>
</Card>
<Stack
align="center"
justify={"center"}
>
<Text c={"white"} style={{
textAlign: "center"
}} maw={300}>Tambahkan Text Apa aja disini untuk lebih detail yang berwarna putih</Text>
<Button
radius={100}
w={{
base: "100%",
sm: "300",
}}
px={42}
size="lg"
variant="fill"
bg={"gray.1"}
c={"dark"}
>
Button 1
</Button>
</Stack>
</Stack>
<Stack
justify={"end"}
align={"end"}
pos={"relative"}
w={{
base: "100%",
md: "40%",
}}
px={"xl"}
>
<Image
src={"/assets/images/perbekel.png"}
alt="perbekel"
fit="contain" // Menyesuaikan gambar agar sesuai dengan kontainer
width={"100%"}
h={{
base: "500",
md: "100%",
}}
loading="lazy"
/>
<Box
pos={"absolute"}
bottom={0}
p={{
base: "xs",
md: "md",
}}
>
<Card
px={"lg"}
radius={"32"}
className="glass3"
style={{
border: `1px solid white`,
}}
>
<Text>Perbekel Desa Darmasaba</Text>
<Text c={colors["blue-button"]} fw={"bolder"} fz={"1rem"}>
I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P.
</Text>
</Card>
</Box>
</Stack>
</Flex>
</BackgroundImage>
);
}
export default Content1;

View File

@@ -0,0 +1,65 @@
'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;

View File

@@ -0,0 +1,118 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client";
import colors from "@/con/colors";
import images from "@/con/images";
import ApiFetch from "@/lib/api-fetch";
import { Carousel } from "@mantine/carousel";
import {
BackgroundImage,
Box,
Button,
Container,
Divider,
Group,
Stack,
Text,
useMantineTheme,
} from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
import Autoplay from "embla-carousel-autoplay";
import _ from "lodash";
import { useRef } from "react";
import useSWR from "swr";
type DataSlider = {
id: string;
name: string;
};
const textHeading = {
title: "Layanan",
des: "Terwujudnya Layanan umum bertajuk Sistem administrasi Kependudukan Terintegrasi di Desa berbasi Elektronik, Smart dan Aman",
};
function Content3() {
const { data, isLoading } = useSWR(
"/",
(url) => ApiFetch.api.layanan.get().then(({ data }) => data?.data),
{
fallbackData: [],
}
);
return (
<Stack pos={"relative"} bg={colors.grey[1]} gap={"42"} py={"xl"}>
<Container w={{ base: "100%", md: "50%" }} p={"xl"}>
<Stack align="center" gap={"0"}>
<Text fz={"3.4rem"} fw={"bold"}>
{textHeading.title}
</Text>
<Text
style={{
textAlign: "center",
}}
>
{textHeading.des}
</Text>
<Box p={"md"}>
<Button variant="filled" bg={"dark"} radius={100}>
Lanjutkan
</Button>
</Box>
</Stack>
</Container>
<Slider data={data as any} />
<Divider />
</Stack>
);
}
const height = 720;
function Slider({ data }: { data: DataSlider[] }) {
const theme = useMantineTheme();
const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
const autoplay = useRef(Autoplay({ delay: 2000 }));
const slides = data.map((item) => (
<Carousel.Slide key={item.id}>
<BackgroundImage src={images["bg-slide3"]} h={height} p="xl" radius="md">
<Stack justify="space-between" h={"100%"} gap={0}>
<Box p={"lg"}>
<Text
fw={"bold"}
c={"white"}
size={"3.5rem"}
style={{
textAlign: "center",
}}
>
{_.startCase(item.name)}
</Text>
</Box>
<Group justify="center">
<Button px={46} radius={"100"} size="md">
Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
</Carousel.Slide>
));
return (
<Carousel
plugins={[autoplay.current]}
onMouseEnter={autoplay.current.stop}
onMouseLeave={autoplay.current.reset}
height={height}
slideSize={{ base: "100%", sm: "50%", md: "33.333333%" }}
slideGap={{ base: "xl", sm: "md" }}
loop
align="start"
slidesToScroll={mobile ? 1 : 2}
>
{slides}
</Carousel>
);
}
export default Content3;

View File

@@ -0,0 +1,126 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client";
import colors from "@/con/colors";
import images from "@/con/images";
import ApiFetch from "@/lib/api-fetch";
import {
BackgroundImage,
Box,
Button,
Divider,
Group,
SimpleGrid,
Stack,
Text,
Title,
} from "@mantine/core";
import _ from "lodash";
import { motion } from "motion/react";
import useSWR from "swr";
type DataPotensi = {
id: string;
name: string;
};
const textHeading = {
title: "Potensi",
des: "segenap sumber daya alam dan sumber daya manusia yang dimiliki desa sebagai modal dasar yang perlu dikelola dan dikembangkan bagi kelangsungan dan perkembangan desa",
};
function Content4() {
const { data, isLoading } = useSWR("/", (url) =>
ApiFetch.api.potensi.get().then(({ data }) => data?.data)
);
if (isLoading) return <Text>loading ...</Text>;
return (
<Stack p={"sm"} gap={"4rem"}>
<Box
w={{
base: "100%",
sm: "60%",
}}
>
<Text fz={"4.4rem"}>{textHeading.title}</Text>
<Text size={"1.4rem"}>{textHeading.des}</Text>
</Box>
<SimpleGrid
cols={{
base: 1,
sm: 2,
}}
>
{_.take(data, 4).map((v, k) => (
<motion.div
key={k}
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.8 }}
>
<BackgroundImage
src={images.tps}
h={320}
key={k}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0,
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack
justify="end"
h={"100%"}
p={"md"}
align="start"
pos={"absolute"}
style={{
zIndex: 1,
}}
>
<Text fw={"bold"} c={"gray.1"} size={"2.4rem"}>
{v.name}
</Text>
<Text
style={{
textAlign: "center",
}}
c={"blue"}
>
Tambahkan Text Indikasi Keberhasilan
</Text>
</Stack>
</BackgroundImage>
</motion.div>
))}
</SimpleGrid>
<Stack align="center">
<Group>
<Stack gap={0}>
<Title>Text Lanjutan Mengarahkan</Title>
<Text
style={{
textAlign: "center",
}}
>
deskripsi singkat sebelum tombol dibawah setelah ini
</Text>
</Stack>
</Group>
<Group>
<Button variant="outline" radius={100} size="md">
Selanjutnya
</Button>
</Group>
</Stack>
<Divider />
</Stack>
);
}
export default Content4;

View File

@@ -0,0 +1,38 @@
import React from "react";
import { motion, useScroll, useTransform } from "framer-motion";
import { Image } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
const FlipOnScroll = () => {
// Menggunakan hook useScroll untuk mendeteksi posisi scroll
const { scrollYProgress } = useScroll();
// Menggunakan useTransform untuk mengubah nilai scroll menjadi rotasi
const rotate = useTransform(scrollYProgress, [0, 1], [10, 360 * 1]); // Rotasi dari 0 hingga 360 derajat
useShallowEffect(() => {
rotate.on("change", (latest) => {
console.log(latest)
})
}, [])
return (
<div style={{ backgroundColor: "gray", padding: "50px" }}>
<h1>Scroll ke bawah untuk melihat animasi flip</h1>
<motion.div
style={{
width: "500px",
height: "500px",
backgroundColor: "blue",
borderRadius: "10px",
}}
>
<Image src={"https://awsimages.detik.net.id/community/media/visual/2023/04/14/gambar-pemandangan-6_169.jpeg?w=1200"} alt="a" />
</motion.div>
</div>
);
};
export default FlipOnScroll;

View File

@@ -0,0 +1,21 @@
"use client";
import { Center, Container, Stack, Text } from "@mantine/core";
import { useRef } from "react";
import FlipOnScroll from "./FlipScroll";
function Content5() {
const ref = useRef(null);
return (
<Stack ref={ref}>
<Container w={{ base: "100%", md: "80%" }} p={"xl"} h={720}>
<Center>
<Text fz={"3.4rem"}>CONTENT 5</Text>
</Center>
<FlipOnScroll />
</Container>
</Stack>
);
}
export default Content5;

View File

@@ -0,0 +1,30 @@
"use client";
import { Stack } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import { animate, motion, useMotionValue, useTransform } from "motion/react";
export default function Count() {
const count = useMotionValue(0);
const rounded = useTransform(() => Math.round(count.get()));
useShallowEffect(() => {
const controls = animate(count, 100, { duration: 5 });
return () => controls.stop();
}, []);
return (
<Stack>
<motion.pre style={text}>{rounded}</motion.pre>
</Stack>
);
}
/**
* ============== Styles ================
*/
const text = {
fontSize: 64,
color: "#4ff0b7",
};

View File

@@ -0,0 +1,18 @@
"use client";
import { Stack, Container, Center, Text } from "@mantine/core";
import Count from "./Count";
function Content6() {
return (
<Stack>
<Container w={{ base: "100%", md: "80%" }} p={"xl"} h={720}>
<Center>
<Text fz={"3.4rem"}>CONTENT 6</Text>
</Center>
<Count />
</Container>
</Stack>
);
}
export default Content6;

20
src/con/colors.ts Normal file
View File

@@ -0,0 +1,20 @@
const colors = {
"blue-button": "#0A4E78",
"white-1": "#FBFBFC",
"white-trans-1": "rgba(255, 255, 255, 0.5)",
"white-trans-2": "rgba(255, 255, 255, 0.7)",
"white-trans-3": "rgba(255, 255, 255, 0.9)",
"trans": {
"dark": {
"1": "rgba(0, 0, 0, 0.5)",
"2": "rgba(0, 0, 0, 0.7)",
"3": "rgba(0, 0, 0, 0.9)"
}
},
"grey": {
"1": "#F4F5F6"
}
}
export default colors
export type ColorsType = typeof colors

32
src/con/images.ts Normal file
View File

@@ -0,0 +1,32 @@
const images = {
"pudak-icon": "/assets/images/pudak-icon.png",
"module": {
"daves": "/assets/images/module/daves.png",
"mangan": "/assets/images/module/mangan.png",
"bicara-darma": "/assets/images/module/bicara-darma.png",
"bares": "/assets/images/module/bares.png",
"sajjana-dharma-raksaka": "/assets/images/module/sajjana-dharma-raksaka.png",
"pdkt": "/assets/images/module/pdkt.png",
"gelah-melah": "/assets/images/module/gelah-melah.png",
"inovasi-desa-darmasaba": "/assets/images/module/inovasi-desa-darmasaba.png"
},
"bg-slide": "/assets/images/bg-slide.png",
"sosmed": {
"telegram": "/assets/images/sosmed/telegram.png",
"instagram": "/assets/images/sosmed/instagram.png",
"gmail": "/assets/images/sosmed/gmail.png",
"x-twitter": "/assets/images/sosmed/x-twitter.png",
"tiktok": "/assets/images/sosmed/tiktok.png",
"youtube": "/assets/images/sosmed/youtube.png",
"whatsapp": "/assets/images/sosmed/whatsapp.png",
"facebook": "/assets/images/sosmed/facebook.png"
},
"bg-blur": "/assets/images/bg-blur.png",
"darmasaba-icon": "/assets/images/darmasaba-icon.png",
"bg-slide3": "/assets/images/bg-slide3.png",
"bg-slide2": "/assets/images/bg-slide2.png",
"perbekel": "/assets/images/perbekel.png",
"tps": "/assets/images/tps.png",
"bg": "/assets/images/bg.png"
};
export default images;

282
src/con/navbar-list-menu.ts Normal file
View File

@@ -0,0 +1,282 @@
const navbarListMenu = [
{
id: "1",
name: "Desa",
href: "/desa",
children: [
{
id: "1.1",
name: "profile",
href: "/desa/profile"
},
{
id: "1.2",
name: "potensi",
href: "/desa/potensi"
},
{
id: "1.3",
name: "berita",
href: "/desa/berita"
},
{
id: "1.4",
name: "pengumuman",
href: "/desa/pengumuman"
},
{
id: "1.5",
name: "galery",
href: "/desa/galery"
},
{
id: "1.6",
name: "layanan",
href: "/desa/layanan"
},
]
},
{
id: "2",
name: "Kesehatan",
href: "/kesehatan",
children: [
{
id: "2.1",
name: "Posyandu",
href: "/kesehatan/posyandu"
},
{
id: "2.2",
name: "Data Kesehatan Warga",
href: "/kesehatan/data-kesehatan-warga"
},
{
id: "2.3",
name: "Puskesmas",
href: "/kesehatan/puskesmas"
},
{
id: "2.4",
name: "Program Kesehatan",
href: "/kesehatan/program-kesehatan"
},
{
id: "2.5",
name: "Penanganan Darurat",
href: "/kesehatan/penanganan-darurat"
},
{
id: "2.6",
name: "Kontak Darurat",
href: "/kesehatan/kontak-darurat"
},
{
id: "2.7",
name: "Info Wabah/Penyakit",
href: "/kesehatan/info-wabah-penyakit"
}
]
},
{
id: "3",
name: "Keamanan",
href: "/keamanan",
children: [
{
id: "3.1",
name: "Keamanan Lingkungan (Pecalang/Patwal)",
href: "/keamanan/keamanan-lingkungan"
},
{
id: "3.2",
name: "Polsek Terdekat",
href: "/keamanan/polsek-terdekat"
},
{
id: "3.3",
name: "Kontak Darurat",
href: "/keamanan/kontak-darurat"
},
{
id: "3.4",
name: "Pencegahan Kriminalitas",
href: "/keamanan/pencegahan-kriminalitas"
},
{
id: "3.5",
name: "Laporan Publik",
href: "/keamanan/laporan-publik"
},
{
id: "3.6",
name: "Tips Keamanan",
href: "/keamanan/tips-keamanan"
}
]
},
{
id: "4",
name: "Ekonomi",
href: "/ekonomi",
children: [
{
id: "4.1",
name: "Pasar Desa",
href: "/ekonomi/pasar-desa"
},
{
id: "4.2",
name: "Koperasi",
href: "/ekonomi/koperasi"
},
{
id: "4.3",
name: "UMKM",
href: "/ekonomi/umkm"
},
{
id: "4.4",
name: "Data Ekonomi Desa",
href: "/ekonomi/data-ekonomi-desa"
},
{
id: "4.5",
name: "Pelatihan Wirausaha",
href: "/ekonomi/pelatihan-wirausaha"
},
{
id: "4.6",
name: "Bantuan & Pendanaan",
href: "/ekonomi/bantuan-pendanaan"
},
{
id: "4.7",
name: "Investasi Desa",
href: "/ekonomi/investasi-desa"
},
{
id: "4.8",
name: "Produk Unggulan",
href: "/ekonomi/produk-unggulan"
},
{
id: "4.9",
name: "Lowongan Kerja Lokal",
href: "/ekonomi/lowongan-kerja-lokal"
}
]
}, {
id: "5",
name: "Inovasi",
href: "/inovasi",
children: [
{
id: "5.1",
name: "Desa Digital/Smart Village",
href: "/inovasi/desa-digital-smart-village"
},
{
id: "5.2",
name: "Layanan Online Desa",
href: "/inovasi/layanan-online-desa"
},
{
id: "5.3",
name: "Program Kreatif Desa",
href: "/inovasi/program-kreatif-desa"
},
{
id: "5.4",
name: "Kolaborasi Inovasi",
href: "/inovasi/kolaborasi-inovasi"
},
{
id: "5.5",
name: "Info Teknologi Tepat Guna",
href: "/inovasi/info-teknologi-tepat-guna"
},
{
id: "5.6",
name: "Ajukan Ide Inovatif",
href: "/inovasi/ajukan-ide-inovatif"
}
]
}, {
id: "6",
name: "Lingkungan",
href: "/lingkungan",
children: [
{
id: "6.1",
name: "Pengelolaan Sampah (Bank Sampah)",
href: "/lingkungan/pengelolaan-sampah-bank-sampah"
},
{
id: "6.2",
name: "Program Penghijauan",
href: "/lingkungan/program-penghijauan"
},
{
id: "6.3",
name: "Data Lingkungan Desa",
href: "/lingkungan/data-lingkungan-desa"
},
{
id: "6.4",
name: "Gotong Royong",
href: "/lingkungan/gotong-royong"
},
{
id: "6.5",
name: "Edukasi Lingkungan",
href: "/lingkungan/edukasi-lingkungan"
},
{
id: "6.6",
name: "Konservasi Adat Bali",
href: "/lingkungan/konservasi-adat-bali"
}
]
}, {
id: "7",
name: "Pendidikan",
href: "/pendidikan",
children: [
{
id: "7.1",
name: "Info Sekolah & PAUD",
href: "/pendidikan/info-sekolah-paud"
},
{
id: "7.2",
name: "Beasiswa Desa",
href: "/pendidikan/beasiswa-desa"
},
{
id: "7.3",
name: "Program Pendidikan Anak",
href: "/pendidikan/program-pendidikan-anak"
},
{
id: "7.4",
name: "Bimbingan Belajar Desa",
href: "/pendidikan/bimbingan-belajar-desa"
},
{
id: "7.5",
name: "Pendidikan Non Formal",
href: "/pendidikan/pendidikan-non-formal"
},
{
id: "7.6",
name: "Perpustakaan Desa",
href: "/pendidikan/perpustakaan-desa"
}
]
}
]
export default navbarListMenu

26
src/con/sosmed.ts Normal file
View File

@@ -0,0 +1,26 @@
const sosmed = [
{
id: "1",
name: "Facebook",
href: "https://facebook.com",
},
{
id: "2",
name: "Instagram",
href: "https://instagram.com",
},
{
id: "3",
name: "Twitter",
href: "https://twitter.com",
},
{
id: "4",
name: "Youtube",
href: "https://youtube.com",
}
]
export default sosmed

6
src/lib/api-fetch.ts Normal file
View File

@@ -0,0 +1,6 @@
import { AppServer } from '@/app/api/[[...slugs]]/route'
import { treaty } from '@elysiajs/eden'
const ApiFetch = treaty<AppServer>(process.env.NEXT_PUBLIC_HOST || 'localhost:3000')
export default ApiFetch

44
src/lib/prisma.ts Normal file
View File

@@ -0,0 +1,44 @@
import { PrismaClient } from '@prisma/client';
let prisma: PrismaClient;
if (process.env.NODE_ENV === 'production') {
prisma = new PrismaClient();
} else {
const globalWithPrisma = global as typeof globalThis & {
prisma: PrismaClient;
};
if (!globalWithPrisma.prisma) {
globalWithPrisma.prisma = new PrismaClient();
}
prisma = globalWithPrisma.prisma;
}
// Handle uncaught errors
process.on('uncaughtException', async (error) => {
console.error('Uncaught Exception:', error);
await prisma.$disconnect();
process.exit(1);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', async (error) => {
console.error('Unhandled Rejection:', error);
await prisma.$disconnect();
process.exit(1);
});
// Handle graceful shutdown
process.on('SIGINT', async () => {
console.log('Received SIGINT signal. Closing database connections...');
await prisma.$disconnect();
process.exit(0);
});
process.on('SIGTERM', async () => {
console.log('Received SIGTERM signal. Closing database connections...');
await prisma.$disconnect();
process.exit(0);
});
export default prisma;

46
src/middleware.ts Normal file
View File

@@ -0,0 +1,46 @@
// app/middleware.js
import { NextResponse, NextRequest } from 'next/server';
// Daftar route yang diizinkan tanpa login (public routes)
const publicRoutes = [
'/*', // Home page
'/about', // About page
'/public/*', // Wildcard untuk semua route di bawah /public
'/login', // Halaman login
];
// Fungsi untuk memeriksa apakah route saat ini adalah route publik
function isPublicRoute(pathname: string) {
return publicRoutes.some((route) => {
// Jika route mengandung wildcard (*), gunakan regex untuk mencocokkan
if (route.endsWith('*')) {
const baseRoute = route.replace('*', ''); // Hapus wildcard
return pathname.startsWith(baseRoute); // Cocokkan dengan pathname
}
return pathname === route; // Cocokkan exact path
});
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Jika route adalah public, izinkan akses
if (isPublicRoute(pathname)) {
return NextResponse.next();
}
// Jika bukan public route, periksa apakah pengguna sudah login
const isLoggedIn = request.cookies.get('darmasaba-auth-token'); // Contoh: cek cookie auth-token
if (!isLoggedIn) {
// Redirect ke halaman login jika belum login
return NextResponse.redirect(new URL('/login', request.url));
}
// Jika sudah login, izinkan akses
return NextResponse.next();
}
// Konfigurasi untuk menentukan path mana yang akan dijalankan middleware
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], // Jalankan middleware untuk semua route kecuali file statis
};

View File

@@ -0,0 +1,19 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { proxy } from "valtio";
import useSwr from "swr";
type Layanan = {
layanan: string | null
useLoad: any
}
const stateLayanan = proxy<Layanan>({
layanan: null,
useLoad: () => {
}
})
export default stateLayanan

22
src/state/state-nav.ts Normal file
View File

@@ -0,0 +1,22 @@
import { proxy } from "valtio"
import { MenuItem } from "../../types/menu-item"
const stateNav = proxy<{
hover: boolean,
item: MenuItem[] | null
isSearch: boolean,
clear: () => void,
module: string | null
}>({
hover: false,
item: null,
isSearch: false,
clear: () => {
stateNav.hover = false
stateNav.item = null
stateNav.isSearch = false
},
module: null
})
export default stateNav

View File

@@ -22,6 +22,6 @@
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app/page.wibu", "src/app/api/[[...slugs]]/builder/handle.js", "src/app/destack/page.js"],
"exclude": ["node_modules"]
}

6
types/menu-item.ts Normal file
View File

@@ -0,0 +1,6 @@
export type MenuItem = {
id: string,
name: string,
href: string,
children?: MenuItem[]
}

11
xcoba.ts Normal file
View File

@@ -0,0 +1,11 @@
import path from 'path';
import fs from 'fs';
import { compress } from 'compress-pdf';
(async () => {
const pdf = path.resolve("/Users/bip/Downloads", 'komoditas.pdf');
const buffer = await compress(pdf);
const compressedPdf = path.resolve(__dirname, 'compressed_pdf.pdf');
await fs.promises.writeFile(compressedPdf, buffer);
})();

70
zgen/image.ts Normal file
View File

@@ -0,0 +1,70 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import fs from "fs";
import path from "path";
// Fungsi untuk membaca direktori secara rekursif
function readDirectoryRecursive(dir: string, callback: (filePath: string) => void) {
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
const fullPath = path.join(dir, item.name);
if (item.isDirectory()) {
// Jika item adalah direktori, baca secara rekursif
readDirectoryRecursive(fullPath, callback);
} else if (item.isFile()) {
// Jika item adalah file, panggil callback
callback(fullPath);
}
}
}
// Fungsi untuk generate object image
function generateImageObject() {
const publicFolderPath = path.join(process.cwd(), "public"); // Path ke folder public
const imagesFolderPath = path.join(publicFolderPath, "assets", "images"); // Path ke folder images
// Log path untuk debugging
console.log("Images folder path:", imagesFolderPath);
// Objek untuk menyimpan hasil
const images: Record<string, any> = {};
try {
// Baca direktori secara rekursif
readDirectoryRecursive(imagesFolderPath, (filePath) => {
const ext = path.extname(filePath).toLowerCase(); // Ekstensi file
const allowedExtensions = [".png", ".jpg", ".jpeg"];
// Filter hanya file gambar
if (allowedExtensions.includes(ext)) {
const relativePath = path.relative(imagesFolderPath, filePath); // Path relatif terhadap folder images
const dirName = path.dirname(relativePath); // Nama folder induk
const fileNameWithoutExt = path.basename(filePath, ext); // Nama file tanpa ekstensi
const relativeUrl = `/assets/images/${relativePath.replace(/\\/g, "/")}`; // Ganti \ dengan / untuk URL
// Jika file berada langsung di folder images, tambahkan ke root objek
if (dirName === ".") {
images[fileNameWithoutExt] = relativeUrl;
} else {
// Jika file berada di subfolder, kelompokkan berdasarkan nama folder
if (!images[dirName]) {
images[dirName] = {};
}
images[dirName][fileNameWithoutExt] = relativeUrl;
}
console.log(`Processed file: ${dirName}/${fileNameWithoutExt} -> ${relativeUrl}`); // Log setiap file yang diproses
}
});
// Simpan hasil ke file setelah semua file diproses
const outputPath = path.join(process.cwd(), "src", "con", "images.ts");
const content = `const images = ${JSON.stringify(images, null, 2)};\nexport default images;`;
fs.writeFileSync(outputPath, content);
console.log(`File generated successfully at: ${outputPath}`);
console.log("Generated images object:", images); // Log hasil akhir
} catch (error) {
console.error("Error generating image object:", error);
}
}
// Jalankan fungsi
generateImageObject();