diff --git a/app/auth/layout.tsx b/app/auth/layout.tsx new file mode 100644 index 0000000..03ceabe --- /dev/null +++ b/app/auth/layout.tsx @@ -0,0 +1,32 @@ +import Footer from "@/components/layout/footer" +import Image from "next/image" + +export default function AuthLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( +
+
+
+ auth-image +
+ +
+
+
{children}
+
+ +
+
+
+ ) +} diff --git a/app/auth/sign-in/layout.tsx b/app/auth/sign-in/layout.tsx new file mode 100644 index 0000000..c58bee9 --- /dev/null +++ b/app/auth/sign-in/layout.tsx @@ -0,0 +1,7 @@ +export default function SignInLayout({ + children, +}: { + children: React.ReactNode +}) { + return <>{children} +} diff --git a/app/auth/sign-in/page.tsx b/app/auth/sign-in/page.tsx new file mode 100644 index 0000000..f0442f7 --- /dev/null +++ b/app/auth/sign-in/page.tsx @@ -0,0 +1,121 @@ +"use client" + +import { useForm, Controller } from "react-hook-form" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import Link from "next/link" +import { useRouter } from "next/navigation" + +type FormValues = { + nrp: string + password: string +} + +export default function SignInPage() { + const router = useRouter() + + const { + control, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + nrp: "", + password: "", + }, + }) + + const onSubmit = async (data: FormValues) => { + console.log("DATA LOGIN:", data) + + await new Promise((res) => setTimeout(res, 1000)) + + router.push("/dashboard") + } + + return ( +
+

+ Masuk +

+ +
+
+ + + ( + + )} + /> + + {errors.nrp && ( +

{errors.nrp.message}

+ )} +
+ +
+ + + ( + + )} + /> + + {errors.password && ( +

+ {errors.password.message} +

+ )} +
+ + + +

+ Belum punya akun?{" "} + + Daftar Sekarang + +

+
+
+ ) +} diff --git a/app/auth/sign-up/layout.tsx b/app/auth/sign-up/layout.tsx new file mode 100644 index 0000000..e762700 --- /dev/null +++ b/app/auth/sign-up/layout.tsx @@ -0,0 +1,7 @@ +export default function SignUpLayout({ + children, +}: { + children: React.ReactNode +}) { + return <>{children} +} diff --git a/app/auth/sign-up/page.tsx b/app/auth/sign-up/page.tsx new file mode 100644 index 0000000..bb3526b --- /dev/null +++ b/app/auth/sign-up/page.tsx @@ -0,0 +1,115 @@ +"use client" + +import { useForm, Controller } from "react-hook-form" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import Link from "next/link" + +type FormValues = { + nrp: string + email: string +} + +export default function SignUpPage() { + const { + control, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + nrp: "", + email: "", + }, + }) + + const onSubmit = async (data: FormValues) => { + console.log("DATA LOGIN:", data) + + await new Promise((res) => setTimeout(res, 1000)) + } + + return ( +
+

+ Daftar +

+ +
+
+ + + ( + + )} + /> + + {errors.email && ( +

{errors.email.message}

+ )} +
+ +
+ + + ( + + )} + /> + + {errors.nrp && ( +

{errors.nrp.message}

+ )} +
+ + + +

+ Sudah memiliki akun?{" "} + + Masuk Sekarang + +

+
+
+ ) +} diff --git a/app/dashboard/etle/layout.tsx b/app/dashboard/etle/layout.tsx new file mode 100644 index 0000000..41aa4dc --- /dev/null +++ b/app/dashboard/etle/layout.tsx @@ -0,0 +1,7 @@ +export default function EtleLayout({ + children, +}: { + children: React.ReactNode +}) { + return <>{children} +} diff --git a/app/dashboard/etle/page.tsx b/app/dashboard/etle/page.tsx new file mode 100644 index 0000000..df26d5e --- /dev/null +++ b/app/dashboard/etle/page.tsx @@ -0,0 +1,115 @@ +"use client" + +import { ArrowLeftIcon, ChevronLeft } from "lucide-react" +import Link from "next/link" +import { useRouter } from "next/navigation" +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer" +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet" +import { Button } from "@/components/ui/button" +import Image from "next/image" + +const dummy = [ + { url: "/sample-1.jpg" }, + { url: "/sample-2.jpg" }, + { url: "/sample-3.jpg" }, + { url: "/sample-4.jpg" }, + { url: "/sample-1.jpg" }, + { url: "/sample-2.jpg" }, + { url: "/sample-3.jpg" }, + { url: "/sample-4.jpg" }, + { url: "/sample-1.jpg" }, +] + +export default function Etle() { + const router = useRouter() + return ( +
+
+ + + +

ETLE Toll

+
+
+

TRAFFIC VIOLATIONS

+ {"main-image"} + +

VIOLATIONS

+

+ Plat yang terdeteksi berangka D1234PZ di 2026-01-08 jam 17:55:26 + berada di jalur 2 +

+
+ + + + + +
+
+ Total Image: 10 +
{" "} + {dummy.map((item, index) => ( +
+ {"image" +
+ ))} +
+
+
+ {/* + + + + +
+
+
+ Total Image: 10 +
{" "} +
+
+
+
*/} +
+ ) +} diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx new file mode 100644 index 0000000..275053a --- /dev/null +++ b/app/dashboard/layout.tsx @@ -0,0 +1,7 @@ +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode +}) { + return <>{children} +} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx new file mode 100644 index 0000000..a98bc5c --- /dev/null +++ b/app/dashboard/page.tsx @@ -0,0 +1,54 @@ +import DashboardSideMenu from "@/components/layout/dashboard-side-menu" +import Footer from "@/components/layout/footer" +import { PinIcon } from "lucide-react" +import Image from "next/image" +import Link from "next/link" + +const dummy = { name: "Andri2 Ferinata", plat: "B 14 QU" } + +const menu = [ + { name: "Tugas", href: "/assignment", icon: "icon-tugas.svg" }, + { name: "Patroli Rutin", href: "/assignment", icon: "icon-patrol.svg" }, + { name: "Pengawalan Polisi", href: "/assignment", icon: "icon-patrol.svg" }, + { name: "Komunikasi", href: "/assignment", icon: "icon-komunikasi.svg" }, + { name: "SOS", href: "/assignment", icon: "icon-sos.svg" }, + { name: "Peta", href: "/assignment", icon: "icon-map.svg" }, + { name: "SPKLU", href: "/assignment", icon: "icon-spklu.svg" }, + { name: "Kata Ahli", href: "/assignment", icon: "icon-ahli.svg" }, + { name: "ETLE Toll", href: "/dashboard/etle", icon: "icon-etle.svg" }, +] + +export default function Dashboard() { + return ( +
+
+

+ No Kendaraan : {dummy.plat} +

+

Petugas : {dummy.name}

+ +
+ {menu.map((item) => ( + + {item.icon} +

{item.name}

+ + ))} +
+
+
+
+ +
+
+ ) +} diff --git a/app/globals.css b/app/globals.css index 14e5686..8e54883 100644 --- a/app/globals.css +++ b/app/globals.css @@ -5,124 +5,124 @@ @custom-variant dark (&:is(.dark *)); :root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.809 0.105 251.813); - --chart-2: oklch(0.623 0.214 259.815); - --chart-3: oklch(0.546 0.245 262.881); - --chart-4: oklch(0.488 0.243 264.376); - --chart-5: oklch(0.424 0.199 265.638); - --radius: 0.625rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.809 0.105 251.813); + --chart-2: oklch(0.623 0.214 259.815); + --chart-3: oklch(0.546 0.245 262.881); + --chart-4: oklch(0.488 0.243 264.376); + --chart-5: oklch(0.424 0.199 265.638); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); } .dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.809 0.105 251.813); - --chart-2: oklch(0.623 0.214 259.815); - --chart-3: oklch(0.546 0.245 262.881); - --chart-4: oklch(0.488 0.243 264.376); - --chart-5: oklch(0.424 0.199 265.638); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.809 0.105 251.813); + --chart-2: oklch(0.623 0.214 259.815); + --chart-3: oklch(0.546 0.245 262.881); + --chart-4: oklch(0.488 0.243 264.376); + --chart-5: oklch(0.424 0.199 265.638); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); } @theme inline { - --font-sans: var(--font-sans); - --color-sidebar-ring: var(--sidebar-ring); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar: var(--sidebar); - --color-chart-5: var(--chart-5); - --color-chart-4: var(--chart-4); - --color-chart-3: var(--chart-3); - --color-chart-2: var(--chart-2); - --color-chart-1: var(--chart-1); - --color-ring: var(--ring); - --color-input: var(--input); - --color-border: var(--border); - --color-destructive: var(--destructive); - --color-accent-foreground: var(--accent-foreground); - --color-accent: var(--accent); - --color-muted-foreground: var(--muted-foreground); - --color-muted: var(--muted); - --color-secondary-foreground: var(--secondary-foreground); - --color-secondary: var(--secondary); - --color-primary-foreground: var(--primary-foreground); - --color-primary: var(--primary); - --color-popover-foreground: var(--popover-foreground); - --color-popover: var(--popover); - --color-card-foreground: var(--card-foreground); - --color-card: var(--card); - --color-foreground: var(--foreground); - --color-background: var(--background); - --radius-sm: calc(var(--radius) * 0.6); - --radius-md: calc(var(--radius) * 0.8); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) * 1.4); - --radius-2xl: calc(var(--radius) * 1.8); - --radius-3xl: calc(var(--radius) * 2.2); - --radius-4xl: calc(var(--radius) * 2.6); + --font-sans: var(--font-sans); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-foreground: var(--foreground); + --color-background: var(--background); + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); } @layer base { * { @apply border-border outline-ring/50; - } + } body { @apply bg-background text-foreground; - } + } html { @apply font-sans; - } -} \ No newline at end of file + } +} diff --git a/app/layout.tsx b/app/layout.tsx index a4c7321..f6e8f18 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,16 +1,22 @@ -import { Geist, Geist_Mono, Inter } from "next/font/google" +import { Geist_Mono, Inter, Roboto } from "next/font/google" import "./globals.css" import { ThemeProvider } from "@/components/theme-provider" -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils" -const inter = Inter({subsets:['latin'],variable:'--font-sans'}) +const inter = Inter({ subsets: ["latin"], variable: "--font-sans" }) const fontMono = Geist_Mono({ subsets: ["latin"], variable: "--font-mono", }) +const roboto = Roboto({ + subsets: ["latin"], + weight: ["400", "700", "900"], + variable: "--font-roboto", +}) + export default function RootLayout({ children, }: Readonly<{ @@ -20,9 +26,15 @@ export default function RootLayout({ - + {children} diff --git a/app/page.tsx b/app/page.tsx index 98ff035..1165a1c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,19 +1,35 @@ +"use client" +import Footer from "@/components/layout/footer" import { Button } from "@/components/ui/button" +import Image from "next/image" +import { useRouter } from "next/navigation" export default function Page() { + const router = useRouter() return ( -
-
-
-

Project ready!

-

You may now add components and start building.

-

We've already added the button component for you.

- -
-
- (Press d to toggle dark mode) -
+
+
+ main-icon + +

+ Selamat Datang
di
Silancar! +

+ +
+ +
) } diff --git a/components/layout/dashboard-side-menu.tsx b/components/layout/dashboard-side-menu.tsx new file mode 100644 index 0000000..aca519c --- /dev/null +++ b/components/layout/dashboard-side-menu.tsx @@ -0,0 +1,69 @@ +import { RefreshCwIcon } from "lucide-react" +import { Button } from "../ui/button" +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel" + +const dummy = [ + { + name: "Ipda Iwan", + message: "Ndan, lokasi di Bundaran HI lalu lintas lancar", + time: "16 Oct 2024, 13:44", + }, + { + name: "Ipda Iwan", + message: "Ndan, lokasi di Bundaran HI lalu lintas lancar", + time: "16 Oct 2024, 13:44", + }, +] + +export default function DashboardSideMenu() { + return ( +
+
+
+ Pesan Pribadi Terakhir + +
+ + + {dummy.map((list, index) => ( + +
+

{list.name}

+

{list.message}

+

{list.time}

+
+
+ ))} +
+
+
+ +
+
+ Panggilan Pribadi Terakhir +
+ + + {dummy.map((list, index) => ( + +
+

{list.name}

+

{list.message}

+

{list.time}

+
+
+ ))} +
+
+
+
+ ) +} diff --git a/components/layout/footer.tsx b/components/layout/footer.tsx new file mode 100644 index 0000000..aba0de4 --- /dev/null +++ b/components/layout/footer.tsx @@ -0,0 +1,8 @@ +export default function Footer() { + return ( +
+

ver 1.0.0

+

@2024 - Korlantas Hak Cipta Dilindungi Undang-Undang.

+
+ ) +} diff --git a/components/ui/carousel.tsx b/components/ui/carousel.tsx new file mode 100644 index 0000000..9deee76 --- /dev/null +++ b/components/ui/carousel.tsx @@ -0,0 +1,242 @@ +"use client" + +import * as React from "react" +import useEmblaCarousel, { + type UseEmblaCarouselType, +} from "embla-carousel-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react" + +type CarouselApi = UseEmblaCarouselType[1] +type UseCarouselParameters = Parameters +type CarouselOptions = UseCarouselParameters[0] +type CarouselPlugin = UseCarouselParameters[1] + +type CarouselProps = { + opts?: CarouselOptions + plugins?: CarouselPlugin + orientation?: "horizontal" | "vertical" + setApi?: (api: CarouselApi) => void +} + +type CarouselContextProps = { + carouselRef: ReturnType[0] + api: ReturnType[1] + scrollPrev: () => void + scrollNext: () => void + canScrollPrev: boolean + canScrollNext: boolean +} & CarouselProps + +const CarouselContext = React.createContext(null) + +function useCarousel() { + const context = React.useContext(CarouselContext) + + if (!context) { + throw new Error("useCarousel must be used within a ") + } + + return context +} + +function Carousel({ + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + ...props +}: React.ComponentProps<"div"> & CarouselProps) { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins + ) + const [canScrollPrev, setCanScrollPrev] = React.useState(false) + const [canScrollNext, setCanScrollNext] = React.useState(false) + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) return + setCanScrollPrev(api.canScrollPrev()) + setCanScrollNext(api.canScrollNext()) + }, []) + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev() + }, [api]) + + const scrollNext = React.useCallback(() => { + api?.scrollNext() + }, [api]) + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "ArrowLeft") { + event.preventDefault() + scrollPrev() + } else if (event.key === "ArrowRight") { + event.preventDefault() + scrollNext() + } + }, + [scrollPrev, scrollNext] + ) + + React.useEffect(() => { + if (!api || !setApi) return + setApi(api) + }, [api, setApi]) + + React.useEffect(() => { + if (!api) return + onSelect(api) + api.on("reInit", onSelect) + api.on("select", onSelect) + + return () => { + api?.off("select", onSelect) + } + }, [api, onSelect]) + + return ( + +
+ {children} +
+
+ ) +} + +function CarouselContent({ className, ...props }: React.ComponentProps<"div">) { + const { carouselRef, orientation } = useCarousel() + + return ( +
+
+
+ ) +} + +function CarouselItem({ className, ...props }: React.ComponentProps<"div">) { + const { orientation } = useCarousel() + + return ( +
+ ) +} + +function CarouselPrevious({ + className, + variant = "outline", + size = "icon-sm", + ...props +}: React.ComponentProps) { + const { orientation, scrollPrev, canScrollPrev } = useCarousel() + + return ( + + ) +} + +function CarouselNext({ + className, + variant = "outline", + size = "icon-sm", + ...props +}: React.ComponentProps) { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) +} + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, + useCarousel, +} diff --git a/components/ui/drawer.tsx b/components/ui/drawer.tsx new file mode 100644 index 0000000..ea930ee --- /dev/null +++ b/components/ui/drawer.tsx @@ -0,0 +1,131 @@ +"use client" + +import * as React from "react" +import { Drawer as DrawerPrimitive } from "vaul" + +import { cn } from "@/lib/utils" + +function Drawer({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerClose({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DrawerContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + +
+ {children} + + + ) +} + +function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DrawerTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DrawerDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 0000000..d763cd9 --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,19 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx new file mode 100644 index 0000000..7039402 --- /dev/null +++ b/components/ui/sheet.tsx @@ -0,0 +1,144 @@ +"use client" + +import * as React from "react" +import { Dialog as SheetPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { XIcon } from "lucide-react" + +function Sheet({ ...props }: React.ComponentProps) { + return +} + +function SheetTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function SheetClose({ + ...props +}: React.ComponentProps) { + return +} + +function SheetPortal({ + ...props +}: React.ComponentProps) { + return +} + +function SheetOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SheetContent({ + className, + children, + side = "right", + showCloseButton = true, + ...props +}: React.ComponentProps & { + side?: "top" | "right" | "bottom" | "left" + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + + )} + + + ) +} + +function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SheetDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Sheet, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/package-lock.json b/package-lock.json index cbc7b3e..7d4c879 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,15 +10,18 @@ "dependencies": { "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "embla-carousel-react": "^8.6.0", "lucide-react": "^0.577.0", "next": "16.1.7", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-hook-form": "^7.71.2", "shadcn": "^4.0.8", "tailwind-merge": "^3.5.0", - "tw-animate-css": "^1.4.0" + "tw-animate-css": "^1.4.0", + "vaul": "^1.1.2" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -5224,6 +5227,31 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==" }, + "node_modules/embla-carousel": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==" + }, + "node_modules/embla-carousel-react": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz", + "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==", + "dependencies": { + "embla-carousel": "8.6.0", + "embla-carousel-reactive-utils": "8.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz", + "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==", + "peerDependencies": { + "embla-carousel": "8.6.0" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -8923,6 +8951,21 @@ "react": "^19.2.4" } }, + "node_modules/react-hook-form": { + "version": "7.71.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz", + "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -10442,6 +10485,18 @@ "node": ">= 0.8" } }, + "node_modules/vaul": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", + "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", diff --git a/package.json b/package.json index ac35922..272fd69 100644 --- a/package.json +++ b/package.json @@ -14,15 +14,18 @@ "dependencies": { "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "embla-carousel-react": "^8.6.0", "lucide-react": "^0.577.0", "next": "16.1.7", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-hook-form": "^7.71.2", "shadcn": "^4.0.8", "tailwind-merge": "^3.5.0", - "tw-animate-css": "^1.4.0" + "tw-animate-css": "^1.4.0", + "vaul": "^1.1.2" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/public/icon-ahli.svg b/public/icon-ahli.svg new file mode 100644 index 0000000..1239a0d --- /dev/null +++ b/public/icon-ahli.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon-etle.svg b/public/icon-etle.svg new file mode 100644 index 0000000..970c74e --- /dev/null +++ b/public/icon-etle.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon-komunikasi.svg b/public/icon-komunikasi.svg new file mode 100644 index 0000000..e71b8b2 --- /dev/null +++ b/public/icon-komunikasi.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon-map.svg b/public/icon-map.svg new file mode 100644 index 0000000..f2fd366 --- /dev/null +++ b/public/icon-map.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/icon-patrol.svg b/public/icon-patrol.svg new file mode 100644 index 0000000..d83944a --- /dev/null +++ b/public/icon-patrol.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/icon-sos.svg b/public/icon-sos.svg new file mode 100644 index 0000000..61cf23d --- /dev/null +++ b/public/icon-sos.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icon-spklu.svg b/public/icon-spklu.svg new file mode 100644 index 0000000..faf2b08 --- /dev/null +++ b/public/icon-spklu.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icon-tugas.svg b/public/icon-tugas.svg new file mode 100644 index 0000000..0906cfb --- /dev/null +++ b/public/icon-tugas.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/main-background.png b/public/main-background.png new file mode 100644 index 0000000..cd6efec Binary files /dev/null and b/public/main-background.png differ diff --git a/public/main-icon.png b/public/main-icon.png new file mode 100644 index 0000000..9ee1ed6 Binary files /dev/null and b/public/main-icon.png differ diff --git a/public/sample-1.jpg b/public/sample-1.jpg new file mode 100644 index 0000000..0b4e9a5 Binary files /dev/null and b/public/sample-1.jpg differ diff --git a/public/sample-2.jpg b/public/sample-2.jpg new file mode 100644 index 0000000..465b85f Binary files /dev/null and b/public/sample-2.jpg differ diff --git a/public/sample-3.jpg b/public/sample-3.jpg new file mode 100644 index 0000000..b226c78 Binary files /dev/null and b/public/sample-3.jpg differ diff --git a/public/sample-4.jpg b/public/sample-4.jpg new file mode 100644 index 0000000..201c9de Binary files /dev/null and b/public/sample-4.jpg differ diff --git a/public/silancar.jpg b/public/silancar.jpg new file mode 100644 index 0000000..0955343 Binary files /dev/null and b/public/silancar.jpg differ diff --git a/tailwind.config.tsx b/tailwind.config.tsx new file mode 100644 index 0000000..589643d --- /dev/null +++ b/tailwind.config.tsx @@ -0,0 +1,17 @@ +import type { Config } from "tailwindcss" + +const config: Config = { + content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"], + theme: { + extend: { + fontFamily: { + sans: ["var(--font-inter)"], + mono: ["var(--font-mono)"], + heading: ["var(--font-roboto)"], + }, + }, + }, + plugins: [], +} + +export default config