Fix Revisi Kak Inno 22 Oktober && Fix Revisi Kak Ayu 22 Oktober
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.1.5",
|
"version": "0.1.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun --bun next dev --hostname 0.0.0.0",
|
"dev": "bun --bun next dev",
|
||||||
"build": "bun --bun next build",
|
"build": "bun --bun next build",
|
||||||
"start": "bun --bun next start"
|
"start": "bun --bun next start"
|
||||||
},
|
},
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
"@types/bun": "^1.2.2",
|
"@types/bun": "^1.2.2",
|
||||||
"@types/leaflet": "^1.9.20",
|
"@types/leaflet": "^1.9.20",
|
||||||
"@types/lodash": "^4.17.16",
|
"@types/lodash": "^4.17.16",
|
||||||
|
"@types/nodemailer": "^7.0.2",
|
||||||
"add": "^2.0.6",
|
"add": "^2.0.6",
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
"elysia": "^1.3.5",
|
"elysia": "^1.3.5",
|
||||||
"embla-carousel-autoplay": "^8.5.2",
|
"embla-carousel-autoplay": "^8.5.2",
|
||||||
"embla-carousel-react": "^7.1.0",
|
"embla-carousel-react": "^7.1.0",
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
"next": "^15.5.2",
|
"next": "^15.5.2",
|
||||||
"next-view-transitions": "^0.3.4",
|
"next-view-transitions": "^0.3.4",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
|
"nodemailer": "^7.0.10",
|
||||||
"p-limit": "^6.2.0",
|
"p-limit": "^6.2.0",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primereact": "^10.9.6",
|
"primereact": "^10.9.6",
|
||||||
|
|||||||
54
src/app/api/subscribe/route.ts
Normal file
54
src/app/api/subscribe/route.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
try {
|
||||||
|
const { email } = await request.json();
|
||||||
|
|
||||||
|
// Input validation
|
||||||
|
if (!email) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, message: 'Email is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email regex validation
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(email)) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, message: 'Invalid email format' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure nodemailer
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
service: 'gmail',
|
||||||
|
auth: {
|
||||||
|
user: process.env.EMAIL_USER,
|
||||||
|
pass: process.env.EMAIL_PASS,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send email
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: `"Tim Info" <${process.env.EMAIL_USER}>`,
|
||||||
|
to: email,
|
||||||
|
subject: '✅ Berhasil Berlangganan!',
|
||||||
|
html: `<p>Terima kasih telah berlangganan info terbaru dari kami!</p>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Subscription successful! Please check your email.',
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in subscribe API:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, message: 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,25 +7,39 @@ import { useEffect, useState } from 'react';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function PelayananPerizinanBerusaha() {
|
function PelayananPerizinanBerusaha() {
|
||||||
const state = useProxy(stateLayananDesa)
|
const state = useProxy(stateLayananDesa);
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false);
|
||||||
const [active, setActive] = useState(1);
|
const [active, setActive] = useState(0);
|
||||||
const nextStep = () => setActive((current) => (current < 6 ? current + 1 : current));
|
|
||||||
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
|
const totalSteps = 6;
|
||||||
|
|
||||||
|
const nextStep = () => {
|
||||||
|
if (active < totalSteps - 1) {
|
||||||
|
setActive(active + 1);
|
||||||
|
} else if (active === totalSteps - 1) {
|
||||||
|
setActive(totalSteps); // Mark as completed
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevStep = () => {
|
||||||
|
if (active > 0) {
|
||||||
|
setActive(active - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await state.pelayananPerizinanBerusaha.findById.load('edit')
|
await state.pelayananPerizinanBerusaha.findById.load('edit');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Gagal memuat data:', error);
|
console.error('Gagal memuat data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
loadData()
|
loadData();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const data = state.pelayananPerizinanBerusaha.findById.data;
|
const data = state.pelayananPerizinanBerusaha.findById.data;
|
||||||
|
|
||||||
@@ -33,8 +47,12 @@ function PelayananPerizinanBerusaha() {
|
|||||||
return (
|
return (
|
||||||
<Center mih={300}>
|
<Center mih={300}>
|
||||||
<Stack align="center" gap="sm">
|
<Stack align="center" gap="sm">
|
||||||
<Text fz="lg" fw={500} c="dimmed">Belum ada informasi layanan yang tersedia</Text>
|
<Text fz="lg" fw={500} c="dimmed">
|
||||||
<Button component="a" href="https://oss.go.id" target="_blank" radius="xl">Kunjungi OSS</Button>
|
Belum ada informasi layanan yang tersedia
|
||||||
|
</Text>
|
||||||
|
<Button component="a" href="https://oss.go.id" target="_blank" radius="xl">
|
||||||
|
Kunjungi OSS
|
||||||
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
@@ -57,11 +75,27 @@ function PelayananPerizinanBerusaha() {
|
|||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Text fz={{ base: 'sm', md: 'md' }} ta="justify" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: data?.deskripsi || '' }} />
|
<Text
|
||||||
|
fz={{ base: 'sm', md: 'md' }}
|
||||||
|
ta="justify"
|
||||||
|
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: data?.deskripsi || '' }}
|
||||||
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={600} mb="sm" fz={{ base: 'sm', md: 'lg' }}>Alur pendaftaran NIB:</Text>
|
<Text fw={600} mb="sm" fz={{ base: 'sm', md: 'lg' }}>
|
||||||
<Stepper active={active} onStepClick={setActive} orientation="vertical" color="blue" radius="md"
|
Alur pendaftaran NIB:
|
||||||
|
</Text>
|
||||||
|
<Stepper
|
||||||
|
active={active}
|
||||||
|
onStepClick={(step) => {
|
||||||
|
if (step <= active) { // Only allow clicking on previous or current steps
|
||||||
|
setActive(step);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
orientation="vertical"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
styles={{
|
styles={{
|
||||||
step: { padding: '14px 0' },
|
step: { padding: '14px 0' },
|
||||||
stepBody: { marginLeft: 8 }
|
stepBody: { marginLeft: 8 }
|
||||||
@@ -95,19 +129,42 @@ function PelayananPerizinanBerusaha() {
|
|||||||
</StepperCompleted>
|
</StepperCompleted>
|
||||||
</Stepper>
|
</Stepper>
|
||||||
|
|
||||||
|
{active < totalSteps && (
|
||||||
<Group justify="center" mt="lg">
|
<Group justify="center" mt="lg">
|
||||||
<Button variant="light" leftSection={<IconArrowLeft size={18} />} onClick={prevStep} disabled={active === 0}>
|
<Button
|
||||||
|
variant="light"
|
||||||
|
leftSection={<IconArrowLeft size={18} />}
|
||||||
|
onClick={prevStep}
|
||||||
|
disabled={active === 0}
|
||||||
|
>
|
||||||
Kembali
|
Kembali
|
||||||
</Button>
|
</Button>
|
||||||
<Button rightSection={<IconArrowRight size={18} />} onClick={nextStep}>
|
|
||||||
Lanjut
|
{active < totalSteps ? (
|
||||||
|
<Button
|
||||||
|
rightSection={active < totalSteps - 1 ? <IconArrowRight size={18} /> : null}
|
||||||
|
onClick={nextStep}
|
||||||
|
>
|
||||||
|
{active === totalSteps - 1 ? 'Selesai' : 'Lanjut'}
|
||||||
</Button>
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
onClick={() => setActive(0)}
|
||||||
|
>
|
||||||
|
Mulai Lagi
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Text fz="sm" ta="justify" c="dimmed" mt="md">
|
<Text fz="sm" ta="justify" c="dimmed" mt="md">
|
||||||
Catatan: Persyaratan dan prosedur dapat berubah sewaktu-waktu. Untuk informasi resmi terbaru, silakan kunjungi situs{" "}
|
Catatan: Persyaratan dan prosedur dapat berubah sewaktu-waktu. Untuk informasi resmi terbaru, silakan kunjungi situs{' '}
|
||||||
<a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">oss.go.id</a> atau hubungi instansi pemerintah terkait.
|
<a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">
|
||||||
|
oss.go.id
|
||||||
|
</a>{' '}
|
||||||
|
atau hubungi instansi pemerintah terkait.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
import laporanPublikState from '@/app/admin/(dashboard)/_state/keamanan/laporan-publik';
|
import laporanPublikState from '@/app/admin/(dashboard)/_state/keamanan/laporan-publik';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, ColorSwatch, Flex, Group, Modal, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Center, ColorSwatch, Flex, Group, Modal, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||||
import { DateTimePicker } from '@mantine/dates';
|
import { DateTimePicker } from '@mantine/dates';
|
||||||
import { useDebouncedValue, useDisclosure, useShallowEffect } from '@mantine/hooks';
|
import { useDebouncedValue, useDisclosure, useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowRight, IconPlus, IconSearch } from '@tabler/icons-react';
|
import { IconArrowRight, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useTransitionRouter } from 'next-view-transitions';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
import { useTransitionRouter } from 'next-view-transitions';
|
|
||||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@@ -53,7 +53,7 @@ function Page() {
|
|||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Flex justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<BackButton />
|
<BackButton />
|
||||||
<TextInput
|
<TextInput
|
||||||
radius={"lg"}
|
radius={"lg"}
|
||||||
@@ -61,9 +61,9 @@ function Page() {
|
|||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
leftSection={<IconSearch size={20} />}
|
leftSection={<IconSearch size={20} />}
|
||||||
w={{ base: "50%", md: "100%" }}
|
w={{ base: "100%", md: "30%" }}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
@@ -118,7 +118,7 @@ function Page() {
|
|||||||
return (
|
return (
|
||||||
<Paper radius={'lg'} key={k} bg={colors['white-trans-1']} p={'xl'}>
|
<Paper radius={'lg'} key={k} bg={colors['white-trans-1']} p={'xl'}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Title c={colors['blue-button']} order={1}>{v.judul}</Title>
|
<Text c={colors['blue-button']} lineClamp={3} truncate="end" fz="h4" fw="bold">{v.judul}</Text>
|
||||||
<Text fs={'italic'} fz={'xl'}>
|
<Text fs={'italic'} fz={'xl'}>
|
||||||
{v.tanggalWaktu
|
{v.tanggalWaktu
|
||||||
? new Date(v.tanggalWaktu).toLocaleString('id-ID')
|
? new Date(v.tanggalWaktu).toLocaleString('id-ID')
|
||||||
|
|||||||
@@ -84,9 +84,8 @@ function Page() {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="h4" fw="bold">Kenali Gejala DBD</Text>
|
<Text fz="h4" fw="bold">{state.findUnique.data.symptom?.title}</Text>
|
||||||
<Divider my="xs" />
|
<Divider my="xs" />
|
||||||
<Text fz="md" fw="semibold">{state.findUnique.data.symptom?.title}</Text>
|
|
||||||
<Text fz="md" lh={1.6} ta="justify" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
|
<Text fz="md" lh={1.6} ta="justify" style={{wordBreak: "break-word", whiteSpace: "normal"}} dangerouslySetInnerHTML={{ __html: state.findUnique.data.symptom?.content }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -126,18 +126,37 @@ export default function Page() {
|
|||||||
className="hover-scale"
|
className="hover-scale"
|
||||||
>
|
>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Box h={180} w="100%">
|
<Center>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 180, // 🔥 tinggi fix biar semua seragam
|
||||||
|
borderRadius: 12,
|
||||||
|
overflow: 'hidden',
|
||||||
|
position: 'relative',
|
||||||
|
backgroundColor: '#f0f2f5', // fallback kalau gambar loading
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Image
|
<Image
|
||||||
src={v.image?.link}
|
src={v.image?.link || '/img/default.png'}
|
||||||
alt={v.name}
|
alt={v.name}
|
||||||
radius="xl"
|
|
||||||
w="100%"
|
|
||||||
h="100%"
|
|
||||||
fit="cover"
|
fit="cover"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
style={{
|
||||||
|
objectFit: 'cover',
|
||||||
|
objectPosition: 'center',
|
||||||
|
transition: 'transform 0.4s ease',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.05)')}
|
||||||
|
onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
</Center>
|
||||||
|
|
||||||
<Box px="lg" pb="lg">
|
<Box px="lg" pb="lg">
|
||||||
<Text
|
<Text
|
||||||
fw="bold"
|
fw="bold"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ function Page() {
|
|||||||
>
|
>
|
||||||
Dasar Hukum
|
Dasar Hukum
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta="center" fz="sm" c="dimmed">
|
<Text ta="center" fz="md" >
|
||||||
Informasi regulasi dan kebijakan resmi yang menjadi dasar hukum
|
Informasi regulasi dan kebijakan resmi yang menjadi dasar hukum
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -63,19 +63,25 @@ function Page() {
|
|||||||
<Box px={{ base: 0, md: 50 }} pb={40}>
|
<Box px={{ base: 0, md: 50 }} pb={40}>
|
||||||
<SimpleGrid cols={{ base: 1, xl: 2 }} spacing="xl">
|
<SimpleGrid cols={{ base: 1, xl: 2 }} spacing="xl">
|
||||||
<Box px={{ base: 0, md: 50 }}>
|
<Box px={{ base: 0, md: 50 }}>
|
||||||
<Paper bg={colors['white-trans-1']} radius="lg" shadow="sm">
|
<Paper bg={colors['white-trans-1']} radius="xl" shadow="md" withBorder>
|
||||||
<Stack gap="md">
|
<Stack gap={0}>
|
||||||
<Center>
|
|
||||||
<Image
|
<Image
|
||||||
loading='lazy'
|
pt={{ base: 0, md: 100 }}
|
||||||
|
px="lg"
|
||||||
src={item.image?.link ? `${item.image.link}?t=${Date.now()}` : "/perbekel.png"}
|
src={item.image?.link ? `${item.image.link}?t=${Date.now()}` : "/perbekel.png"}
|
||||||
w={{ base: 220, md: 330 }}
|
|
||||||
alt="Foto Pimpinan"
|
alt="Foto Pimpinan"
|
||||||
radius="md"
|
radius="lg"
|
||||||
|
onError={(e) => e.currentTarget.src = "/perbekel.png"}
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</Center>
|
<Paper
|
||||||
<Paper bg={colors['blue-button']} py={25} radius="lg" className="glass3">
|
bg={colors['blue-button']}
|
||||||
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "1.5rem", md: "2rem" }}>
|
px="lg"
|
||||||
|
radius="0 0 var(--mantine-radius-xl) var(--mantine-radius-xl)"
|
||||||
|
className="glass3"
|
||||||
|
py={{ base: 20, md: 50 }}
|
||||||
|
>
|
||||||
|
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "xl", md: "h2" }}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -54,12 +54,11 @@ function Page() {
|
|||||||
ta="center"
|
ta="center"
|
||||||
fz={{ base: 28, md: 36 }}
|
fz={{ base: 28, md: 36 }}
|
||||||
fw={800}
|
fw={800}
|
||||||
variant="gradient"
|
c={colors['blue-button']}
|
||||||
gradient={{ from: colors['blue-button'], to: 'cyan', deg: 45 }}
|
|
||||||
>
|
>
|
||||||
Moto PPID Desa Darmasaba
|
Moto PPID Desa Darmasaba
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta="center" fz={{ base: 16, md: 20 }} mt="xs" c="dimmed">
|
<Text ta="center" fz={{ base: 16, md: 20 }} mt="xs">
|
||||||
Memberikan informasi yang cepat, mudah, tepat, dan transparan
|
Memberikan informasi yang cepat, mudah, tepat, dan transparan
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { ActionIcon, Anchor, Box, Button, Center, Container, Divider, Flex, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { ActionIcon, Anchor, Box, Button, Center, Container, Divider, Flex, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconAt, IconBrandFacebook, IconBrandInstagram, IconBrandTiktok, IconBrandYoutube } from '@tabler/icons-react';
|
import { IconAt, IconBrandFacebook, IconBrandInstagram, IconBrandTiktok, IconBrandYoutube } from '@tabler/icons-react';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
const sosialMedia = [
|
const sosialMedia = [
|
||||||
{
|
{
|
||||||
@@ -60,6 +62,39 @@ const tautanPenting = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
function Footer() {
|
function Footer() {
|
||||||
|
|
||||||
|
const emailRef = useRef<HTMLInputElement>(null)
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const email = emailRef.current?.value.trim();
|
||||||
|
if (!email) return toast.error('Email wajib diisi!');
|
||||||
|
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(email)) return toast.error('Format email tidak valid!');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/subscribe', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ email }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (res.ok && data.success) {
|
||||||
|
toast.success('Berhasil! Cek email Anda untuk konfirmasi.');
|
||||||
|
emailRef.current!.value = '';
|
||||||
|
} else {
|
||||||
|
toast.error(data.message || 'Gagal berlangganan.');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error('Gagal menghubungi server. Coba lagi nanti.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack bg="linear-gradient(180deg, #1C6EA4, #124170)" c="white">
|
<Stack bg="linear-gradient(180deg, #1C6EA4, #124170)" c="white">
|
||||||
<Box w="100%" p="xl">
|
<Box w="100%" p="xl">
|
||||||
@@ -166,8 +201,9 @@ function Footer() {
|
|||||||
w="70%"
|
w="70%"
|
||||||
placeholder="Masukkan email Anda"
|
placeholder="Masukkan email Anda"
|
||||||
rightSection={<IconAt size={16} />}
|
rightSection={<IconAt size={16} />}
|
||||||
|
ref={emailRef} // ini aja cukup
|
||||||
/>
|
/>
|
||||||
<Button variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} radius="md">Daftar</Button>
|
<Button onClick={handleSubmit} variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} radius="md">Daftar</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ function Apbdes() {
|
|||||||
const data = (state.findMany.data || []).slice(0, 3)
|
const data = (state.findMany.data || []).slice(0, 3)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack p="lg" gap="4rem" bg={colors.Bg}>
|
<Stack p="sm" gap="xl" bg={colors.Bg}>
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text ta={"center"} fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
<Text c={colors["blue-button"]} ta={"center"} fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||||
{textHeading.title}
|
{textHeading.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||||
@@ -117,7 +117,7 @@ function Apbdes() {
|
|||||||
)}
|
)}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|
||||||
<Group pb={80} justify="center">
|
<Group justify="center" pb={10}>
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
href="/darmasaba/apbdes"
|
href="/darmasaba/apbdes"
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ function DesaAntiKorupsi() {
|
|||||||
const data = (state.desaAntikorupsi.findMany.data || []).slice(0, 6);
|
const data = (state.desaAntikorupsi.findMany.data || []).slice(0, 6);
|
||||||
return (
|
return (
|
||||||
<Stack gap={"0"} bg={colors.Bg} p={"sm"}>
|
<Stack gap={"0"} bg={colors.Bg} p={"sm"}>
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
<Container w={{ base: "100%", md: "80%" }} p={"md"} >
|
||||||
<Center>
|
<Center>
|
||||||
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>Desa Anti Korupsi</Text>
|
<Text fw={"bold"} c={colors["blue-button"]} fz={{ base: "1.8rem", md: "3.4rem" }}>Desa Anti Korupsi</Text>
|
||||||
</Center>
|
</Center>
|
||||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola terbuka dengan melibatkan warga mengawasi anggaran, sehingga digunakan tepat sasaran sesuai kebutuhan.</Text>
|
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola terbuka dengan melibatkan warga mengawasi anggaran, sehingga digunakan tepat sasaran sesuai kebutuhan.</Text>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ function Kepuasan() {
|
|||||||
|
|
||||||
if ((loading && !data) || !data) {
|
if ((loading && !data) || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10} px="xl">
|
<Stack py={10} px="sm">
|
||||||
<Skeleton height={300} mb="md" />
|
<Skeleton height={300} mb="md" />
|
||||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||||
<Skeleton height={300} />
|
<Skeleton height={300} />
|
||||||
@@ -154,9 +154,9 @@ function Kepuasan() {
|
|||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Stack p="sm">
|
<Stack p="sm">
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"}>
|
<Container w={{ base: "100%", md: "80%" }} p={"sm"}>
|
||||||
<Center>
|
<Center>
|
||||||
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
<Text fw={"bold"} c={colors["blue-button"]} fz={{ base: "1.8rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
||||||
</Center>
|
</Center>
|
||||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
||||||
<Center mt={10}>
|
<Center mt={10}>
|
||||||
@@ -168,7 +168,7 @@ function Kepuasan() {
|
|||||||
>Ajukan Responden</Button>
|
>Ajukan Responden</Button>
|
||||||
</Center>
|
</Center>
|
||||||
</Container>
|
</Container>
|
||||||
<Box px={"xl"}>
|
<Box px={"sm"}>
|
||||||
<Paper p={"lg"} bg={colors.Bg}>
|
<Paper p={"lg"} bg={colors.Bg}>
|
||||||
<Paper p={"lg"}>
|
<Paper p={"lg"}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
@@ -416,16 +416,16 @@ function Kepuasan() {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Stack p={"sm"}>
|
<Stack p={"sm"}>
|
||||||
<Container size="lg" px="md">
|
<Container size="lg" px="sm">
|
||||||
<Center>
|
<Center>
|
||||||
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }}>Indeks Kepuasan Masyarakat</Text>
|
<Text ta={"center"} fz={{ base: "2.4rem", md: "3.4rem" }} c={colors["blue-button"]}>Indeks Kepuasan Masyarakat</Text>
|
||||||
</Center>
|
</Center>
|
||||||
<Text fz={{ base: "1.2rem", md: "1.4rem" }} ta={"center"}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
<Text fz={{ base: "1.2rem", md: "1.4rem" }} ta={"center"}>Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!</Text>
|
||||||
<Center mt={10}>
|
<Center mt={10}>
|
||||||
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
|
<Button radius={"lg"} bg={colors["blue-button"]} onClick={open}>Ajukan Responden</Button>
|
||||||
</Center>
|
</Center>
|
||||||
</Container>
|
</Container>
|
||||||
<Box px={"xl"}>
|
<Box px={"md"}>
|
||||||
<Paper p={"lg"} bg={colors.Bg}>
|
<Paper p={"lg"} bg={colors.Bg}>
|
||||||
<Paper p={"lg"}>
|
<Paper p={"lg"}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ function LandingPage() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack bg={colors.Bg} p="md" gap="xl">
|
<Stack bg={colors.Bg} p="md" gap="lg">
|
||||||
<Flex gap="lg" wrap={{ base: "wrap", md: "nowrap" }}>
|
<Flex gap="lg" wrap={{ base: "wrap", md: "nowrap" }}>
|
||||||
<Stack w={{ base: "100%", md: "65%" }} gap="lg">
|
<Stack w={{ base: "100%", md: "65%" }} gap="lg">
|
||||||
<Card radius="xl" bg={colors.grey[1]} p="lg" shadow="xl">
|
<Card radius="xl" bg={colors.grey[1]} p="lg" shadow="xl">
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ const textHeading = {
|
|||||||
function Layanan() {
|
function Layanan() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.grey[1]} gap={"42"} py={"xl"}>
|
<Stack pos={"relative"} bg={colors.grey[1]} gap={"xl"} py={"md"}>
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
<Container w={{ base: "100%", md: "80%" }} p={"md"} >
|
||||||
<Stack align="center" gap={"0"}>
|
<Stack align="center" gap={"0"}>
|
||||||
<Text fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
<Text fw={"bold"} c={colors["blue-button"]} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||||
{textHeading.title}
|
{textHeading.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ function Potensi() {
|
|||||||
const data = (state.findMany.data || []).slice(0, 4);
|
const data = (state.findMany.data || []).slice(0, 4);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack p="sm" gap="4rem">
|
<Stack p="sm" gap="xl">
|
||||||
<Container w={{ base: "100%", md: "80%" }} p={"xl"} >
|
<Container w={{ base: "100%", md: "80%" }} p={"md"} >
|
||||||
<Text ta={"center"} fw={"bold"} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
<Text ta={"center"} fw={"bold"} c={colors["blue-button"]} fz={{ base: "1.8rem", md: "3.4rem" }}>
|
||||||
{textHeading.title}
|
{textHeading.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function Prestasi() {
|
|||||||
<Stack align="center" gap="sm">
|
<Stack align="center" gap="sm">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconTrophy size={36} color={colors["blue-button"]} />
|
<IconTrophy size={36} color={colors["blue-button"]} />
|
||||||
<Text ta="center" fz={{ base: "2rem", md: "3.4rem" }} fw={700}>
|
<Text c={colors["blue-button"]} ta="center" fz={{ base: "2rem", md: "3.4rem" }} fw={700}>
|
||||||
Prestasi Desa
|
Prestasi Desa
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -55,7 +55,7 @@ function Prestasi() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<Box py={50}>
|
<Box py="lg">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Center mih={200}>
|
<Center mih={200}>
|
||||||
<Loader color={colors["blue-button"]} size="xl" />
|
<Loader color={colors["blue-button"]} size="xl" />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useMediaQuery } from "@mantine/hooks"
|
|||||||
import { Prisma } from "@prisma/client"
|
import { Prisma } from "@prisma/client"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { IconMoodSad } from "@tabler/icons-react"
|
import { IconMoodSad } from "@tabler/icons-react"
|
||||||
|
import colors from "@/con/colors"
|
||||||
|
|
||||||
export default function SDGS() {
|
export default function SDGS() {
|
||||||
const theme = useMantineTheme()
|
const theme = useMantineTheme()
|
||||||
@@ -41,11 +42,7 @@ export default function SDGS() {
|
|||||||
order={1}
|
order={1}
|
||||||
fz={{ base: "2.4rem", md: "3.6rem" }}
|
fz={{ base: "2.4rem", md: "3.6rem" }}
|
||||||
fw={900}
|
fw={900}
|
||||||
style={{
|
c={colors["blue-button"]}
|
||||||
background: "linear-gradient(90deg, #1A5F7A, #159895)",
|
|
||||||
WebkitBackgroundClip: "text",
|
|
||||||
WebkitTextFillColor: "transparent",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
SDGs Desa
|
SDGs Desa
|
||||||
</Title>
|
</Title>
|
||||||
@@ -54,7 +51,7 @@ export default function SDGS() {
|
|||||||
SDGs Desa merupakan langkah nyata untuk mewujudkan desa yang maju, inklusif, dan berkelanjutan melalui 17 tujuan pembangunan dari pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, hingga pelestarian lingkungan.
|
SDGs Desa merupakan langkah nyata untuk mewujudkan desa yang maju, inklusif, dan berkelanjutan melalui 17 tujuan pembangunan dari pengentasan kemiskinan, pendidikan, kesehatan, kesetaraan gender, hingga pelestarian lingkungan.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Box py={50}>
|
<Box py="lg">
|
||||||
<Paper
|
<Paper
|
||||||
p={{ base: "md", md: "xl" }}
|
p={{ base: "md", md: "xl" }}
|
||||||
radius="2xl"
|
radius="2xl"
|
||||||
@@ -156,7 +153,7 @@ export default function SDGS() {
|
|||||||
href="/darmasaba/sdgs-desa"
|
href="/darmasaba/sdgs-desa"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
size="lg"
|
size="lg"
|
||||||
mt={40}
|
mt="md"
|
||||||
variant="gradient"
|
variant="gradient"
|
||||||
gradient={{ from: "#26667F", to: "#124170" }}
|
gradient={{ from: "#26667F", to: "#124170" }}
|
||||||
style={{ boxShadow: "0 6px 14px rgba(18,65,112,0.25)"}}
|
style={{ boxShadow: "0 6px 14px rgba(18,65,112,0.25)"}}
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ import ScrollToTopButton from "./_com/scrollToTopButton";
|
|||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack bg={colors.grey[1]} gap={"4rem"}>
|
<Stack
|
||||||
|
bg={colors.grey[1]}
|
||||||
|
gap={"1.5rem"}
|
||||||
|
>
|
||||||
<LandingPage />
|
<LandingPage />
|
||||||
<Penghargaan />
|
<Penghargaan />
|
||||||
<Layanan />
|
<Layanan />
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
const colors = {
|
const colors = {
|
||||||
"orange": "#FCAE00",
|
"orange": "#FCAE00",
|
||||||
"blue-button": "#0A4E78",
|
"blue-button": "#0A4E78",
|
||||||
|
"blue-button-1": "#E5F2FA",
|
||||||
|
"blue-button-2": "#B8DAEF",
|
||||||
|
"blue-button-3": "#8AC1E3",
|
||||||
|
"blue-button-4": "#5DA9D8",
|
||||||
|
"blue-button-5": "#2F91CC",
|
||||||
|
"blue-button-6": "#083F61",
|
||||||
|
"blue-button-7": "#062F49",
|
||||||
|
"blue-button-8": "#041F32",
|
||||||
"blue-button-trans": "#628EC6",
|
"blue-button-trans": "#628EC6",
|
||||||
"white-1": "#FBFBFC",
|
"white-1": "#FBFBFC",
|
||||||
"white-trans-1": "rgba(255, 255, 255, 0.5)",
|
"white-trans-1": "rgba(255, 255, 255, 0.5)",
|
||||||
|
|||||||
Reference in New Issue
Block a user