From a6d4670f0616ea0bb1587e66c20bee369849d49b Mon Sep 17 00:00:00 2001 From: panggilsajarey <150323912+raynard05@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:53:16 +0700 Subject: [PATCH] initial commit --- app/actions.ts | 75 +++++ app/dashboard/manajemen-akun/page.tsx | 59 ++++ app/dashboard/page.tsx | 164 +++++++++ app/layout.tsx | 8 +- app/page.tsx | 238 +++++++++---- app/test-supabase/page.tsx | 35 ++ components/dashboard-footer.tsx | 33 ++ components/feature-card.tsx | 51 +++ components/logout-button.tsx | 18 + components/realtime-clock.tsx | 35 ++ components/ui/button.tsx | 55 +++ components/ui/checkbox.tsx | 30 ++ components/ui/input.tsx | 25 ++ components/ui/label.tsx | 26 ++ lib/supabase.ts | 7 + lib/utils.ts | 6 + package-lock.json | 463 +++++++++++++++++++++++++- package.json | 10 +- 18 files changed, 1270 insertions(+), 68 deletions(-) create mode 100644 app/actions.ts create mode 100644 app/dashboard/manajemen-akun/page.tsx create mode 100644 app/dashboard/page.tsx create mode 100644 app/test-supabase/page.tsx create mode 100644 components/dashboard-footer.tsx create mode 100644 components/feature-card.tsx create mode 100644 components/logout-button.tsx create mode 100644 components/realtime-clock.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 lib/supabase.ts create mode 100644 lib/utils.ts diff --git a/app/actions.ts b/app/actions.ts new file mode 100644 index 0000000..bfb7f7e --- /dev/null +++ b/app/actions.ts @@ -0,0 +1,75 @@ +'use server' + +import { supabase } from '@/lib/supabase' +import { redirect } from 'next/navigation' +import { cookies } from 'next/headers' + +export async function login(prevState: any, formData: FormData) { + const username = formData.get('username') as string + const password = formData.get('password') as string + const remember = formData.get('remember') === 'on' + + if (!username || !password) { + return { message: 'Username and password are required' } + } + + try { + // 1. Check Petugas (Admin - Role 1/Admin) + const { data: petugas, error: petugasError } = await supabase + .from('petugas_posyandu') + .select('*') + .eq('username', username) + .eq('password', password) // Plain text password check as requested + .single() + + if (petugas) { + // Set session/cookie for Admin + // In a real app, use a secure session library. For this demo, simple cookies. + const cookieStore = await cookies() + cookieStore.set('user_session', JSON.stringify({ + id: petugas.id, + role: 'admin', + username: petugas.username, + name: petugas.nama + }), { secure: true, httpOnly: true, maxAge: remember ? 60 * 60 * 24 * 7 : 60 * 60 * 24 }) + + redirect('/dashboard') // Redirect to dashboard or appropriate page + } + + // 2. Check Akun Balita (User - Role 2/User) + const { data: user, error: userError } = await supabase + .from('akun_balita') + .select('*') + .eq('username', username) + .eq('password', password) // Plain text password check as requested + .single() + + if (user) { + // Set session/cookie for User + const cookieStore = await cookies() + cookieStore.set('user_session', JSON.stringify({ + id: user.id, + role: 'user', + username: user.username, + name: user.nama_orang_tua // Or nama_anak depending on preference + }), { secure: true, httpOnly: true, maxAge: remember ? 60 * 60 * 24 * 7 : 60 * 60 * 24 }) + + redirect('/user-dashboard') // Redirect to user dashboard + } + + return { message: 'Invalid username or password' } + + } catch (error: any) { + if (error.message === 'NEXT_REDIRECT') { + throw error + } + console.error('Login error:', error) + return { message: 'An error occurred during login' } + } +} + +export async function logout() { + const cookieStore = await cookies() + cookieStore.delete('user_session') + redirect('/') +} diff --git a/app/dashboard/manajemen-akun/page.tsx b/app/dashboard/manajemen-akun/page.tsx new file mode 100644 index 0000000..6d22ee8 --- /dev/null +++ b/app/dashboard/manajemen-akun/page.tsx @@ -0,0 +1,59 @@ +import { FeatureCard } from '@/components/feature-card' +import { UserCog, Users, ArrowLeft } from 'lucide-react' +import { Activity } from 'lucide-react' +import { LogoutButton } from '@/components/logout-button' +import Link from 'next/link' + +export default function ManajemenAkunPage() { + return ( +
+ {/* Header */} +
+
+ +
+ +
+ Kembali ke Dashboard + +
+
+

Manajemen Akun

+

PILIH OPSI PENGELOLAAN

+
+
+ +
+ +
+
+ + {/* Kelola Akun Petugas */} +
+ +
+ + {/* Kelola Akun Pengguna */} +
+ +
+ +
+
+
+ ) +} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx new file mode 100644 index 0000000..fcac135 --- /dev/null +++ b/app/dashboard/page.tsx @@ -0,0 +1,164 @@ +import { cookies } from 'next/headers' +import { redirect } from 'next/navigation' +import { supabase } from '@/lib/supabase' +import { LogoutButton } from '@/components/logout-button' +import { RealtimeClock } from '@/components/realtime-clock' +import { FeatureCard } from '@/components/feature-card' +import { Activity, User, CreditCard, Phone, Users, TrendingUp, ClipboardList, Building2, Calendar } from 'lucide-react' + +export default async function DashboardPage() { + const cookieStore = await cookies() + const sessionCookie = cookieStore.get('user_session') + + if (!sessionCookie) { + redirect('/') + } + + const session = JSON.parse(sessionCookie.value) + + if (session.role !== 'admin') { + redirect('/') + } + + const { data: petugas, error } = await supabase + .from('petugas_posyandu') + .select('*') + .eq('id', session.id) + .single() + + if (error || !petugas) { + return
Error loading profile.
+ } + + return ( +
+ {/* Header */} +
+
+
+ +
+
+

HealthPortal

+

DASHBOARD

+
+
+ +
+ +
+ {/* Main Single Frame */} +
+ + {/* Top Section - Welcome & Clock */} +
+
+

+ + Selamat Datang! +

+

+ Anda log in sebagai {petugas.nama} +

+ +
+
+ +
+
+ + {/* Bottom Section - Details Grid */} +
+ + {/* Nama */} +
+
+ + Nama +
+
{petugas.nama}
+
+ + {/* Username */} +
+
+ + Username +
+
{petugas.username}
+
+ + {/* Nomor Petugas - Full Width */} +
+
+ + Nomor Petugas +
+
{petugas.nomor_petugas}
+
+ + {/* No Telp - Full Width */} +
+
+ + Nomor Telepon +
+
{petugas.no_telp || '-'}
+
+ +
+
+ + {/* Feature Menu Grid */} +
+ {/* 1. Manajemen Akun */} + + + {/* 2. Trend Stunting Daerah */} + + + {/* 3. Kelola Data */} + + + {/* 4. Manajemen Posyandu */} + + + {/* 5. Kelola Jadwal Posyandu */} + +
+
+ +
+ ) +} diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..329ba42 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import { DashboardFooter } from "@/components/dashboard-footer"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -25,9 +26,12 @@ export default function RootLayout({ return ( - {children} +
+ {children} +
+ ); diff --git a/app/page.tsx b/app/page.tsx index 295f8fd..bdf1950 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,65 +1,185 @@ -import Image from "next/image"; +'use client' + +import { useActionState } from 'react' +import { login } from './actions' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Checkbox } from '@/components/ui/checkbox' +import { Loader2, Eye, EyeOff, Activity } from 'lucide-react' +import { useState } from 'react' + +export default function LoginPage() { + const [state, formAction, isPending] = useActionState(login, null) + const [showPassword, setShowPassword] = useState(false) -export default function Home() { return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

+
+ {/* Left Side - Guide */} +
+ {/* Background Accents */} +
+
+ +
+
+
+ +
+

HealthPortal

+
+

SISTEM INFORMASI KESEHATAN

-
- - Vercel logomark - Deploy Now - - - Documentation - + +

Panduan Login

+ +
+
+ +
+
1
+
+

Masukkan Username

+

Gunakan username yang telah diberikan oleh administrator sistem.

+
+
+ +
+
2
+
+

Masukkan Password

+

Ketik password Anda dengan benar. Password bersifat case-sensitive.

+
+
+ +
+
3
+
+

Klik Tombol Masuk

+

Setelah mengisi data, klik tombol "Masuk" untuk mengakses sistem.

+
+
-
+ +
+
+
?
+
+
+

Lupa Password?

+

Hubungi administrator untuk reset password atau gunakan fitur "Lupa Password" di form login.

+
+
+ +
+

Bantuan: (021) 555-1234

+
+
+ +
+ Sistem ini dilindungi dan diawasi. Akses tidak sah akan ditindak sesuai hukum yang berlaku. +
+
+ +
+ + {/* Right Side - Login Form */} +
+
+ {/* Decorative Elements for Card style */} +
+ +
+

Login

+

Masukkan Akun Anda untuk melanjutkan

+
+ +
+
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+
+ + +
+ Lupa Kata Sandi? +
+ + {state?.message && ( +
+ + {state.message} +
+ )} + + + +
+

+ + KONEKSI AMAN & TERENKRIPSI +

+
+ +
+ +
+
+ Privasi + + Ketentuan + + Bantuan +
+
+ © 2024 HealthPortal. Hak Cipta Dilindungi. +
+
- ); + ) } diff --git a/app/test-supabase/page.tsx b/app/test-supabase/page.tsx new file mode 100644 index 0000000..39eade8 --- /dev/null +++ b/app/test-supabase/page.tsx @@ -0,0 +1,35 @@ +'use client' +import { useEffect, useState } from 'react' +import { supabase } from '@/lib/supabase' + +export default function TestPage() { + const [status, setStatus] = useState('Checking connection...') + + useEffect(() => { + async function checkConnection() { + try { + const { data, error } = await supabase.auth.getSession() + if (error) { + setStatus('Error connecting to Supabase: ' + error.message) + } else { + setStatus('Success! Connected to Supabase. Session data available.') + } + } catch (err: any) { + setStatus('Unexpected error: ' + err.message) + } + } + checkConnection() + }, []) + + return ( +
+

Supabase Connection Test

+
+ {status} +
+

+ If you see a success message, the Supabase client is correctly initialized with the provided URL and Anon Key. +

+
+ ) +} diff --git a/components/dashboard-footer.tsx b/components/dashboard-footer.tsx new file mode 100644 index 0000000..fe24624 --- /dev/null +++ b/components/dashboard-footer.tsx @@ -0,0 +1,33 @@ +'use client' + +import { usePathname } from 'next/navigation' + +export function DashboardFooter() { + const pathname = usePathname() + + // Don't show footer on login page (/) + if (pathname === '/') { + return null + } + + return ( + + ) +} diff --git a/components/feature-card.tsx b/components/feature-card.tsx new file mode 100644 index 0000000..d50566c --- /dev/null +++ b/components/feature-card.tsx @@ -0,0 +1,51 @@ +import { LucideIcon } from 'lucide-react' +import Link from 'next/link' +import { cn } from '@/lib/utils' + +interface FeatureCardProps { + title: string + description?: string + icon: LucideIcon + href: string + color: 'green' | 'blue' | 'orange' | 'purple' | 'red' + className?: string +} + +export function FeatureCard({ title, description, icon: Icon, href, color, className }: FeatureCardProps) { + const colorStyles = { + green: 'border-green-500 text-green-600', + blue: 'border-blue-500 text-blue-600', + orange: 'border-orange-500 text-orange-600', + purple: 'border-purple-500 text-purple-600', + red: 'border-red-500 text-red-600', + } + + const iconBgStyles = { + green: 'bg-green-50 text-green-600 border-green-200', + blue: 'bg-blue-50 text-blue-600 border-blue-200', + orange: 'bg-orange-50 text-orange-600 border-orange-200', + purple: 'bg-purple-50 text-purple-600 border-purple-200', + red: 'bg-red-50 text-red-600 border-red-200', + } + + return ( + +
+
+
+ +
+ +

{title}

+ {description && ( +

{description}

+ )} +
+
+ + ) +} diff --git a/components/logout-button.tsx b/components/logout-button.tsx new file mode 100644 index 0000000..80b6345 --- /dev/null +++ b/components/logout-button.tsx @@ -0,0 +1,18 @@ +'use client' + +import { logout } from '@/app/actions' +import { Button } from '@/components/ui/button' +import { LogOut } from 'lucide-react' + +export function LogoutButton() { + return ( + + ) +} diff --git a/components/realtime-clock.tsx b/components/realtime-clock.tsx new file mode 100644 index 0000000..4f01105 --- /dev/null +++ b/components/realtime-clock.tsx @@ -0,0 +1,35 @@ +'use client' + +import { useEffect, useState } from 'react' + +export function RealtimeClock() { + const [date, setDate] = useState(null) // Start null to avoid hydration mismatch + + useEffect(() => { + setDate(new Date()) // Set initial date on client + const timer = setInterval(() => { + setDate(new Date()) + }, 1000) + return () => clearInterval(timer) + }, []) + + if (!date) { + // Render a placeholder or nothing during SSR/initial mount to prevent flicker + return ( +
+
00:00:00 WIB
+
LOADING...
+
+ ) + } + + const formattedTime = date.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit', second: '2-digit' }).replace(/\./g, ':') + const formattedDate = date.toLocaleDateString('id-ID', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }).toUpperCase() + + return ( +
+
{formattedTime} WIB
+
{formattedDate}
+
+ ) +} diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 0000000..639bea2 --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,55 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx new file mode 100644 index 0000000..ab9df48 --- /dev/null +++ b/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 0000000..d191cb8 --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes { } + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/components/ui/label.tsx b/components/ui/label.tsx new file mode 100644 index 0000000..fa5143e --- /dev/null +++ b/components/ui/label.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/lib/supabase.ts b/lib/supabase.ts new file mode 100644 index 0000000..7d43064 --- /dev/null +++ b/lib/supabase.ts @@ -0,0 +1,7 @@ + +import { createClient } from '@supabase/supabase-js' + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL! +const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + +export const supabase = createClient(supabaseUrl, supabaseAnonKey) diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..03aaa4b --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/package-lock.json b/package-lock.json index 7aa2837..509ee95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,17 @@ "name": "web-cloud", "version": "0.1.0", "dependencies": { + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@supabase/supabase-js": "^2.95.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.563.0", "next": "16.1.6", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "tailwind-merge": "^3.4.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -1226,6 +1234,286 @@ "node": ">=12.4.0" } }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1233,6 +1521,86 @@ "dev": true, "license": "MIT" }, + "node_modules/@supabase/auth-js": { + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.95.3.tgz", + "integrity": "sha512-vD2YoS8E2iKIX0F7EwXTmqhUpaNsmbU6X2R0/NdFcs02oEfnHyNP/3M716f3wVJ2E5XHGiTFXki6lRckhJ0Thg==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.95.3.tgz", + "integrity": "sha512-uTuOAKzs9R/IovW1krO0ZbUHSJnsnyJElTXIRhjJTqymIVGcHzkAYnBCJqd7468Fs/Foz1BQ7Dv6DCl05lr7ig==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.95.3.tgz", + "integrity": "sha512-LTrRBqU1gOovxRm1vRXPItSMPBmEFqrfTqdPTRtzOILV4jPSueFz6pES5hpb4LRlkFwCPRmv3nQJ5N625V2Xrg==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.95.3.tgz", + "integrity": "sha512-D7EAtfU3w6BEUxDACjowWNJo/ZRo7sDIuhuOGKHIm9FHieGeoJV5R6GKTLtga/5l/6fDr2u+WcW/m8I9SYmaIw==", + "license": "MIT", + "dependencies": { + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.95.3.tgz", + "integrity": "sha512-4GxkJiXI3HHWjxpC3sDx1BVrV87O0hfX+wvJdqGv67KeCu+g44SPnII8y0LL/Wr677jB7tpjAxKdtVWf+xhc9A==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.95.3.tgz", + "integrity": "sha512-Fukw1cUTQ6xdLiHDJhKKPu6svEPaCEDvThqCne3OaQyZvuq2qjhJAd91kJu3PXLG18aooCgYBaB6qQz35hhABg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.95.3", + "@supabase/functions-js": "2.95.3", + "@supabase/postgrest-js": "2.95.3", + "@supabase/realtime-js": "2.95.3", + "@supabase/storage-js": "2.95.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1549,17 +1917,22 @@ "version": "20.19.33", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/phoenix": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", + "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.13", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1569,12 +1942,21 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.55.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", @@ -2570,12 +2952,33 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2629,7 +3032,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -3890,6 +4293,15 @@ "hermes-estree": "0.25.1" } }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4833,6 +5245,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.563.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz", + "integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -6018,6 +6439,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", @@ -6297,7 +6728,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -6491,6 +6921,27 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index a1a753e..5e526b4 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,17 @@ "lint": "eslint" }, "dependencies": { + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@supabase/supabase-js": "^2.95.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.563.0", "next": "16.1.6", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "tailwind-merge": "^3.4.0" }, "devDependencies": { "@tailwindcss/postcss": "^4",