+>(({ className, ...props }, ref) => (
+ [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+))
+TableCell.displayName = "TableCell"
+
+const TableCaption = React.forwardRef<
+ HTMLTableCaptionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableCaption.displayName = "TableCaption"
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/app/components/ui/tabs.tsx b/app/components/ui/tabs.tsx
new file mode 100644
index 0000000..93f2914
--- /dev/null
+++ b/app/components/ui/tabs.tsx
@@ -0,0 +1,53 @@
+import * as React from "react"
+import * as TabsPrimitive from "@radix-ui/react-tabs"
+
+import { cn } from "~/lib/utils"
+
+const Tabs = TabsPrimitive.Root
+
+const TabsList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsList.displayName = TabsPrimitive.List.displayName
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
+
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsContent.displayName = TabsPrimitive.Content.displayName
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/app/components/ui/textarea.tsx b/app/components/ui/textarea.tsx
new file mode 100644
index 0000000..c480928
--- /dev/null
+++ b/app/components/ui/textarea.tsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+
+import { cn } from "~/lib/utils"
+
+const Textarea = React.forwardRef<
+ HTMLTextAreaElement,
+ React.ComponentProps<"textarea">
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+Textarea.displayName = "Textarea"
+
+export { Textarea }
diff --git a/app/components/ui/tooltip.tsx b/app/components/ui/tooltip.tsx
new file mode 100644
index 0000000..4cec520
--- /dev/null
+++ b/app/components/ui/tooltip.tsx
@@ -0,0 +1,30 @@
+import * as React from "react"
+import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+
+import { cn } from "~/lib/utils"
+
+const TooltipProvider = TooltipPrimitive.Provider
+
+const Tooltip = TooltipPrimitive.Root
+
+const TooltipTrigger = TooltipPrimitive.Trigger
+
+const TooltipContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+TooltipContent.displayName = TooltipPrimitive.Content.displayName
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/app/lib/utils.ts b/app/lib/utils.ts
new file mode 100644
index 0000000..bd0c391
--- /dev/null
+++ b/app/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/app/root.tsx b/app/root.tsx
index 61c8b98..f004345 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -1,11 +1,21 @@
import {
Links,
+ LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
+ useLoaderData
} from "@remix-run/react";
-import type { LinksFunction } from "@remix-run/node";
+import clsx from "clsx";
+import {
+ PreventFlashOnWrongTheme,
+ ThemeProvider,
+ useTheme
+} from "remix-themes";
+import { themeSessionResolver } from "./sessions.server";
+import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node";
+import { ProgressProvider } from "@bprogress/remix";
import "./tailwind.css";
@@ -14,32 +24,54 @@ export const links: LinksFunction = () => [
{
rel: "preconnect",
href: "https://fonts.gstatic.com",
- crossOrigin: "anonymous",
+ crossOrigin: "anonymous"
},
{
rel: "stylesheet",
- href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
+ href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
},
+ {
+ rel: "stylesheet",
+ href: "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
+ }
];
-export function Layout({ children }: { children: React.ReactNode }) {
+export async function loader({ request }: LoaderFunctionArgs) {
+ const { getTheme } = await themeSessionResolver(request);
+ return {
+ theme: getTheme()
+ };
+}
+
+export default function AppWithProviders() {
+ const data = useLoaderData();
return (
-
+
+
+
+ );
+}
+
+export function App() {
+ const data = useLoaderData();
+ const [theme] = useTheme();
+ return (
+
+
- {children}
-
-
+
+
+
+
+
+
);
}
-
-export default function App() {
- return ;
-}
diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx
deleted file mode 100644
index 13a5c00..0000000
--- a/app/routes/_index.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import type { MetaFunction } from "@remix-run/node";
-
-export const meta: MetaFunction = () => {
- return [
- { title: "New Remix App" },
- { name: "description", content: "Welcome to Remix!" },
- ];
-};
-
-export default function Index() {
- return (
-
-
-
-
- Welcome to Remix
-
-
-
-
-
-
-
-
- What's next?
-
-
-
-
-
- );
-}
-
-const resources = [
- {
- href: "https://remix.run/start/quickstart",
- text: "Quick Start (5 min)",
- icon: (
-
-
-
- ),
- },
- {
- href: "https://remix.run/start/tutorial",
- text: "Tutorial (30 min)",
- icon: (
-
-
-
- ),
- },
- {
- href: "https://remix.run/docs",
- text: "Remix Docs",
- icon: (
-
-
-
- ),
- },
- {
- href: "https://rmx.as/discord",
- text: "Join Discord",
- icon: (
-
-
-
- ),
- },
-];
diff --git a/app/routes/_landing._index.tsx b/app/routes/_landing._index.tsx
new file mode 100644
index 0000000..1456e11
--- /dev/null
+++ b/app/routes/_landing._index.tsx
@@ -0,0 +1,1104 @@
+import { Button } from "~/components/ui/button";
+import { Badge } from "~/components/ui/badge";
+import { Card, CardContent } from "~/components/ui/card";
+import { Input } from "~/components/ui/input";
+import { Textarea } from "~/components/ui/textarea";
+import {
+ Play,
+ Download,
+ Apple,
+ Check,
+ ChevronDown,
+ Star,
+ Users,
+ Calendar,
+ MapPin,
+ Truck,
+ BarChart3,
+ Recycle,
+ ArrowLeft,
+ ArrowRight
+} from "lucide-react";
+import { CardBody, CardContainer, CardItem } from "~/components/ui/3d-card";
+import { MacbookScroll } from "~/components/ui/macbook-scroll";
+
+interface Feature {
+ icon: React.ReactNode | string;
+ title: string;
+ description: string;
+}
+
+interface StakeholderFeature {
+ title: string;
+ icon: string;
+ features: string[];
+}
+
+interface WorkStep {
+ icon: React.ReactNode | string;
+ title: string;
+ description: string;
+}
+
+interface Testimonial {
+ name: string;
+ role: string;
+ avatar: string;
+ rating: number;
+ comment: string;
+}
+
+interface Blog {
+ title: string;
+ excerpt: string;
+ author: string;
+ date: string;
+ image: string;
+}
+
+interface FAQ {
+ question: string;
+ answer: string;
+}
+
+// Data constants
+const features: Feature[] = [
+ {
+ icon: ,
+ title: "Cari Pengepul Terdekat",
+ description:
+ "Temukan pengepul sampah terdekat dari lokasi Anda dengan mudah dan cepat menggunakan fitur pencarian berbasis GPS."
+ },
+ {
+ icon: ,
+ title: "Request Penjemputan",
+ description:
+ "Minta penjemputan sampah langsung dari rumah Anda dengan sistem booking yang mudah dan terpercaya."
+ },
+ {
+ icon: ,
+ title: "Tracking Progress",
+ description:
+ "Pantau status penjemputan sampah Anda secara real-time dari permintaan hingga transaksi selesai."
+ },
+ {
+ icon: "📊",
+ title: "Pencatatan Penjualan",
+ description:
+ "Catat semua transaksi penjualan sampah dan monitor pendapatan yang diperoleh dari aktivitas daur ulang."
+ },
+ {
+ icon: "💰",
+ title: "Manajemen Keuangan",
+ description:
+ "Kelola dan pantau uang yang didapat dari penjualan sampah dengan sistem pencatatan yang rapi dan akurat."
+ },
+ {
+ icon: ,
+ title: "Visualisasi Data",
+ description:
+ "Lihat data penjualan, tren harga, dan dampak lingkungan melalui dashboard yang informatif dan mudah dipahami."
+ }
+];
+
+const stakeholderFeatures: StakeholderFeature[] = [
+ {
+ title: "Fitur untuk Masyarakat",
+ icon: "🏠",
+ features: [
+ "Temukan pengepul terdekat dengan GPS",
+ "Request penjemputan sampah dari rumah",
+ "Tracking real-time status pickup",
+ "Pencatatan lengkap penjualan sampah",
+ "Monitor pendapatan dari sampah",
+ "Visualisasi data dan statistik personal"
+ ]
+ },
+ {
+ title: "Fitur untuk Pengepul",
+ icon: "🚛",
+ features: [
+ "Terima dan kelola request pickup",
+ "Pencatatan transaksi dengan masyarakat",
+ "Manajemen stok sampah yang terkumpul",
+ "Dashboard analisis bisnis lengkap",
+ "Sistem notifikasi otomatis",
+ "Laporan keuangan dan operasional"
+ ]
+ },
+ {
+ title: "Fitur untuk Pengelola",
+ icon: "🏭",
+ features: [
+ "Monitor pengepul dan stok tersedia",
+ "Identifikasi supplier potensial",
+ "Pencatatan supply chain management",
+ "Quality control system terintegrasi",
+ "Production planning tools",
+ "Environmental impact reports"
+ ]
+ }
+];
+
+const workSteps: WorkStep[] = [
+ {
+ icon: ,
+ title: "Download Aplikasi Rijig",
+ description:
+ "Unduh aplikasi Rijig dari Play Store atau App Store dan daftar sesuai dengan peran Anda sebagai masyarakat, pengepul, atau pengelola."
+ },
+ {
+ icon: ,
+ title: "Lengkapi Profil Anda",
+ description:
+ "Isi data diri lengkap dan verifikasi akun untuk keamanan transaksi dan membangun kepercayaan antar pengguna dalam ekosistem."
+ },
+ {
+ icon: "🌱",
+ title: "Mulai Kelola Sampah",
+ description:
+ "Mulai jual sampah, kelola bisnis pengepulan, atau operasikan fasilitas daur ulang dengan sistem yang terintegrasi dan mudah digunakan."
+ }
+];
+
+const testimonials: Testimonial[] = [
+ {
+ name: "Budi Santoso",
+ role: "Pengepul Sampah - Jakarta Timur",
+ avatar: "👨💼",
+ rating: 5,
+ comment:
+ "Dengan Rijig, bisnis pengepulan saya jadi lebih terorganisir. Bisa tracking semua transaksi dan jaringan supplier dari masyarakat makin luas. Revenue meningkat 40% sejak pakai aplikasi ini."
+ },
+ {
+ name: "Sari Wijaya",
+ role: "Ibu Rumah Tangga - Bekasi",
+ avatar: "👩🦰",
+ rating: 5,
+ comment:
+ "Sangat memudahkan untuk jual sampah rumah tangga. Harga transparan, pengepul bisa dijemput ke rumah, dan prosesnya cepat. Sekarang sampah rumah tangga bisa jadi tambahan penghasilan yang lumayan!"
+ },
+ {
+ name: "PT. Green Recycle Indonesia",
+ role: "Pengelola Fasilitas Daur Ulang",
+ avatar: "🏭",
+ rating: 5,
+ comment:
+ "Supply chain management jadi jauh lebih efisien dengan Rijig. Bisa monitor kualitas bahan baku dari berbagai pengepul dan planning produksi berdasarkan data yang akurat dan real-time."
+ },
+ {
+ name: "Ahmad Rahman",
+ role: "Ketua RT 05 - Depok",
+ avatar: "👨🔬",
+ rating: 5,
+ comment:
+ "Lingkungan RT kami jadi lebih bersih sejak menggunakan Rijig. Warga antusias karena sampah bisa jadi uang, dan sistem pickup memudahkan mereka yang sibuk bekerja!"
+ }
+];
+
+const blogs: Blog[] = [
+ {
+ title: "5 Jenis Sampah yang Paling Menguntungkan untuk Dijual",
+ excerpt:
+ "Pelajari jenis-jenis sampah yang memiliki nilai ekonomi tinggi dan cara memilahnya dengan benar untuk mendapatkan harga terbaik dari pengepul.",
+ author: "Tim Rijig",
+ date: "15 Juni 2025",
+ image: "♻️"
+ },
+ {
+ title: "Tips Memulai Bisnis Pengepul Sampah untuk Pemula",
+ excerpt:
+ "Panduan lengkap untuk memulai bisnis pengepulan sampah, mulai dari modal awal hingga strategi pemasaran yang efektif menggunakan platform digital.",
+ author: "Tim Rijig",
+ date: "10 Juni 2025",
+ image: "💼"
+ },
+ {
+ title: "Dampak Positif Daur Ulang Terhadap Lingkungan Indonesia",
+ excerpt:
+ "Mengenal lebih dalam bagaimana industri daur ulang berkontribusi dalam mengurangi pencemaran lingkungan dan ekonomi sirkular di Indonesia.",
+ author: "Tim Rijig",
+ date: "5 Juni 2025",
+ image: "🌱"
+ }
+];
+
+const faqs: FAQ[] = [
+ {
+ question: "Apakah aplikasi Rijig gratis untuk digunakan?",
+ answer:
+ "Ya, aplikasi Rijig gratis untuk diunduh dan digunakan oleh masyarakat umum. Untuk pengepul dan pengelola, tersedia fitur premium dengan biaya berlangganan yang terjangkau sesuai kebutuhan bisnis."
+ },
+ {
+ question: "Bagaimana cara kerja sistem penjemputan sampah?",
+ answer:
+ "Masyarakat dapat membuat request pickup melalui aplikasi, memilih pengepul terdekat, menentukan jadwal penjemputan. Pengepul akan mendapat notifikasi dan dapat menerima atau menolak request berdasarkan kapasitas dan lokasi."
+ },
+ {
+ question: "Bagaimana Rijig memastikan harga sampah yang fair?",
+ answer:
+ "Kami menggunakan sistem harga transparan berdasarkan harga pasar real-time, jenis dan kualitas sampah, serta volume. Sistem rating dan review membantu menjaga kualitas transaksi untuk semua pihak."
+ },
+ {
+ question: "Apakah data dan transaksi di Rijig aman?",
+ answer:
+ "Sangat aman. Kami menggunakan enkripsi data tingkat bank, sistem pembayaran terintegrasi yang aman, dan verifikasi identitas pengguna untuk memastikan keamanan dan kepercayaan dalam ekosistem."
+ }
+];
+
+// Component definitions
+const FeatureCard = ({
+ feature,
+ index
+}: {
+ feature: Feature;
+ index: number;
+}) => (
+
+
+ {typeof feature.icon === "string" ? (
+ {feature.icon}
+ ) : (
+ feature.icon
+ )}
+
+
+ {feature.title}
+
+
{feature.description}
+
+);
+
+const StakeholderCard = ({
+ stakeholder,
+ index
+}: {
+ stakeholder: StakeholderFeature;
+ index: number;
+}) => (
+
+
+
+
+ {stakeholder.icon}
+
+
+ {stakeholder.title}
+
+
+
+
+ {stakeholder.features.map((feature, fIndex) => (
+
+
+
+ {feature}
+
+
+ ))}
+
+
+
+
+ Pelajari Lebih Lanjut
+
+
+
+
+);
+
+const WorkStepCard = ({ step, index }: { step: WorkStep; index: number }) => (
+
+
+ {typeof step.icon === "string" ? (
+ {step.icon}
+ ) : (
+ step.icon
+ )}
+
+
+ {step.title}
+
+
{step.description}
+
+);
+
+const TestimonialCard = ({
+ testimonial,
+ index
+}: {
+ testimonial: Testimonial;
+ index: number;
+}) => (
+
+
+
+ "{testimonial.comment}"
+
+
+
+
+
+
+ {testimonial.avatar}
+
+
+
+ {testimonial.name}
+
+
+ {testimonial.role}
+
+
+
+
+
+
+ {testimonial.rating}
+
+
+ {[...Array(testimonial.rating)].map((_, i) => (
+
+ ))}
+
+
+
+
+);
+
+const BlogCard = ({ blog, index }: { blog: Blog; index: number }) => (
+
+
+
+ {blog.image}
+
+
+
+
+
+ {blog.author}
+
+
+
+ {blog.date}
+
+
+
+
+
+
+
+ {blog.title}
+
+
+ {blog.excerpt}
+
+
+ Baca Selengkapnya
+
+
+
+);
+
+const FAQItem = ({ faq, index }: { faq: FAQ; index: number }) => (
+
+
+
+ {faq.question}
+
+
+
+
+
+);
+
+const DashboardFeatureCard = ({
+ icon,
+ title,
+ description,
+ features,
+ iconBg
+}: {
+ icon: React.ReactNode;
+ title: string;
+ description: string;
+ features: string[];
+ iconBg: string;
+}) => (
+
+
+ {icon}
+
+
+ {title}
+
+ {description}
+
+ {features.map((feature, index) => (
+
+
+ {feature}
+
+ ))}
+
+
+);
+
+export default function LandingIndex() {
+ return (
+
+
+
+
+
+
+
+
+ Solusi Digital untuk Ekonomi Sirkular
+
+
+
+ Rijig - Platform{" "}
+
+ Pengelolaan Sampah
+ {" "}
+ Terpadu Indonesia
+
+
+
+ Menghubungkan masyarakat, pengepul, dan pengelola daur ulang
+ dalam satu ekosistem digital yang memudahkan pengelolaan sampah
+ dan menciptakan nilai ekonomi berkelanjutan.
+
+
+
+
+
+ Download Rijig
+
+
+
+
+
+
+
Lihat Demo
+
+ Cara kerja aplikasi
+
+
+
+
+
+
+
+
+
+
+
+
♻️
+
+ Rijig
+
+
+ Kelola sampah dengan bijak
+
+
+
+
+
+
+
+
+
+
+
+
+ Scroll untuk melihat lebih
+
+
+
+
+
+
+ {/* Features Section */}
+
+
+
+
+ Fitur Lengkap untuk Semua Pengguna
+
+
+ Rijig menyediakan solusi terintegrasi yang menghubungkan
+ masyarakat, pengepul, dan pengelola daur ulang dalam satu platform
+ yang mudah digunakan dan efisien.
+
+
+
+
+
+ {features.map((feature, index) => (
+
+ ))}
+
+
+
+
+
+ {/* Stakeholder Features Section */}
+
+
+
+
+ Fitur Khusus untuk Setiap Stakeholder
+
+
+ Rijig dirancang khusus untuk memenuhi kebutuhan berbeda dari
+ setiap pemangku kepentingan dalam ekosistem pengelolaan sampah.
+
+
+
+
+ {stakeholderFeatures.map((stakeholder, index) => (
+
+ ))}
+
+
+
+
+ {/* About Section */}
+
+
+
+
+
+
+
+ Pantau Aktivitas Sampah Real-time
+
+
+ Tracking Lengkap dari Pickup hingga Daur Ulang
+
+
+ Monitor seluruh proses pengelolaan sampah mulai dari permintaan
+ penjemputan, transaksi, hingga proses daur ulang dengan
+ dashboard yang informatif dan update real-time.
+
+
+
+
+
+ 01
+
+
+
+ Status Pickup Real-time
+
+
+ Pantau status penjemputan sampah dari request hingga
+ selesai dengan notifikasi langsung.
+
+
+
+
+
+
+ 02
+
+
+
+ Pencatatan & Analisis Otomatis
+
+
+ Sistem pencatatan otomatis dan analisis data untuk
+ membantu optimalisasi pengelolaan sampah.
+
+
+
+
+
+
+
+
+
+
+ Dampak Lingkungan Terukur
+
+
+ Visualisasi Kontribusi Anda untuk Lingkungan
+
+
+ Lihat secara visual dampak positif yang Anda berikan terhadap
+ lingkungan melalui aktivitas pengelolaan sampah dan daur ulang
+ dalam dashboard yang mudah dipahami.
+
+
+ Pelajari Lebih Lanjut
+
+
+
+
+
+
+
+
+ {/* Work Process Section */}
+
+
+
+
+ Bagaimana Cara Kerjanya?
+
+
+ Bergabung dengan ekosistem pengelolaan sampah yang berkelanjutan
+ hanya dalam 3 langkah mudah dan mulai berkontribusi untuk
+ lingkungan yang lebih bersih.
+
+
+
+
+
+ {workSteps.map((step, index) => (
+
+ ))}
+
+
+
+
+
+ {/* Screenshots Section */}
+
+
+
+
+ Screenshot Aplikasi
+
+
+ Lihat tampilan antarmuka aplikasi Rijig yang user-friendly dan
+ mudah digunakan untuk semua kalangan pengguna.
+
+
+
+
+
+ {[
+ { icon: "🏠", title: "Dashboard Masyarakat" },
+ { icon: "📊", title: "Panel Pengepul" },
+ { icon: "🏭", title: "Sistem Daur Ulang" }
+ ].map((screen, index) => (
+
+
+
+
+ {screen.icon}
+
+
+ {screen.title}
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ {/* MacbookScroll Section */}
+
+
+ Aplikasi Rijig dibangun dengan teknologi modern.
+
+ Pengalaman pengguna yang luar biasa.
+
+
+ }
+ landingTargetId="product-showcase"
+ landingOffset={-150}
+ showGradient={true}
+ badge={
+
+ Live Demo
+
+ }
+ />
+
+
+ {/* Product Showcase Section */}
+
+
+
+
+ Dashboard Analytics
+
+
+ Dashboard Powerful untuk Monitoring Real-time
+
+
+ Pantau seluruh aktivitas pengelolaan sampah dengan dashboard yang
+ informatif, real-time, dan mudah dipahami untuk semua stakeholder.
+
+
+
+
+
+
+
+
+
📊
+
+ Dashboard Preview
+
+
+
+
+
+
+
+
+ }
+ title="Analytics & Insights"
+ description="Visualisasi data penjualan, tren harga sampah, dan analisis performa bisnis dalam bentuk grafik yang mudah dipahami."
+ features={[
+ "Grafik pendapatan harian/bulanan",
+ "Analisis jenis sampah terlaris",
+ "Perbandingan harga pasar"
+ ]}
+ iconBg="bg-green-100 dark:bg-green-900"
+ />
+
+ }
+ title="Live Tracking Map"
+ description="Peta interaktif untuk monitoring lokasi pengepul, status pickup, dan optimasi rute penjemputan secara real-time."
+ features={[
+ "Lokasi pengepul terdekat",
+ "Status pickup real-time",
+ "Estimasi waktu kedatangan"
+ ]}
+ iconBg="bg-blue-100 dark:bg-blue-900"
+ />
+
+ }
+ title="Network Management"
+ description="Kelola jaringan masyarakat, pengepul, dan pengelola dalam satu dashboard terintegrasi dengan fitur komunikasi."
+ features={[
+ "Database supplier & customer",
+ "Rating & review system",
+ "In-app messaging"
+ ]}
+ iconBg="bg-purple-100 dark:bg-purple-900"
+ />
+
+
+
+
+
+
+ 2.5 Ton
+
+
+ Sampah Terkumpul Hari Ini
+
+
+
+
+ Rp 3.2 Juta
+
+
+ Total Transaksi
+
+
+
+
+ 127
+
+
+ Pickup Selesai
+
+
+
+
+ 98%
+
+
+ Kepuasan Pengguna
+
+
+
+
+
+
+
+ {/* Download CTA Section */}
+
+
+
+
+
+ Download Rijig Sekarang & Mulai Kelola Sampah dengan Bijak
+
+
+ Bergabunglah dengan ekosistem pengelolaan sampah yang
+ berkelanjutan. Dapatkan pendapatan dari sampah, kelola bisnis
+ pengepulan, atau optimalisasi supply chain fasilitas daur ulang
+ Anda.
+
+
+
+
+ 📱
+
+
Download di
+
Google Play
+
+
+
+
+
+
Download dari
+
App Store
+
+
+
+
+
+
+
50K+
+
Pengguna Aktif
+
+
+
+ 1000+
+
+
+ Pengepul Terdaftar
+
+
+
+
250+
+
Kota di Indonesia
+
+
+
+
+
+
+
+ {/* Testimonials Section */}
+
+
+
+
+ Apa Kata Pengguna Rijig
+
+
+ Testimoni dari berbagai stakeholder yang telah merasakan manfaat
+ nyata dari penggunaan platform Rijig dalam pengelolaan sampah
+ sehari-hari.
+
+
+
+
+ {testimonials.map((testimonial, index) => (
+
+ ))}
+
+
+
+
+ {/* FAQ Section */}
+
+
+
+
+ Pertanyaan yang Sering Diajukan
+
+
+ Temukan jawaban atas pertanyaan umum tentang Rijig dan cara kerja
+ platform pengelolaan sampah terintegrasi kami.
+
+
+
+
+ {faqs.map((faq, index) => (
+
+ ))}
+
+
+
+
+ {/* Blog Section */}
+
+
+
+
+ Artikel & Tips Pengelolaan Sampah
+
+
+ Pelajari lebih lanjut tentang pengelolaan sampah, tips bisnis
+ pengepulan, dan informasi terkini seputar industri daur ulang di
+ Indonesia.
+
+
+
+
+ {blogs.map((blog, index) => (
+
+ ))}
+
+
+
+
+ {/* Support Section */}
+
+
+
+
+ Mari Terhubung dengan Tim Rijig
+
+
+ Punya pertanyaan atau ingin bermitra dengan Rijig? Hubungi tim
+ kami dan mari bersama-sama membangun ekosistem pengelolaan sampah
+ yang berkelanjutan di Indonesia.
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/routes/_landing.tsx b/app/routes/_landing.tsx
new file mode 100644
index 0000000..ef08956
--- /dev/null
+++ b/app/routes/_landing.tsx
@@ -0,0 +1,405 @@
+import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
+import { json } from "@remix-run/node";
+import { Outlet, useLoaderData, useNavigate } from "@remix-run/react";
+import { useState, useEffect } from "react";
+import { Button } from "~/components/ui/button";
+import { Separator } from "~/components/ui/separator";
+import {
+ Menu,
+ X,
+ Facebook,
+ Twitter,
+ Linkedin,
+ Instagram,
+ Recycle,
+ ArrowUp
+} from "lucide-react";
+import { ModeToggle } from "~/components/ui/dark-mode-toggle";
+
+export const meta: MetaFunction = () => {
+ return [
+ { title: "Rijig - Platform Pengelolaan Sampah Terpadu" },
+ {
+ name: "description",
+ content:
+ "Platform pengelolaan sampah terpadu yang menghubungkan masyarakat, pengepul, dan pengelola untuk ekonomi sirkular berkelanjutan"
+ },
+ { name: "viewport", content: "width=device-width, initial-scale=1" }
+ ];
+};
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ const navigationItems = [
+ { name: "Fitur", href: "#features" },
+ { name: "Tentang", href: "#about" },
+ { name: "Cara Kerja", href: "#work-process" },
+ { name: "Testimoni", href: "#testimonials" },
+ { name: "Kontak", href: "#support" }
+ ];
+
+ const socialLinks = [
+ { name: "Facebook", icon: "facebook", href: "#" },
+ { name: "Twitter", icon: "twitter", href: "#" },
+ { name: "LinkedIn", icon: "linkedin", href: "#" },
+ { name: "Instagram", icon: "instagram", href: "#" }
+ ];
+
+ const authData = {
+ isAuthenticated: false,
+ isRegistrationComplete: false,
+ userRole: null as string | null
+ };
+
+ return json({
+ navigationItems,
+ socialLinks,
+ authData
+ });
+}
+
+export default function LandingLayout() {
+ const { navigationItems, socialLinks, authData } =
+ useLoaderData();
+ const navigate = useNavigate();
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+ const [isScrolled, setIsScrolled] = useState(false);
+ const [showBackToTop, setShowBackToTop] = useState(false);
+
+ const { isAuthenticated, isRegistrationComplete, userRole } = authData;
+
+ useEffect(() => {
+ const handleScroll = () => {
+ const scrollTop = window.scrollY;
+
+ setIsScrolled(scrollTop > 20);
+ setShowBackToTop(scrollTop > 300);
+ };
+
+ handleScroll();
+
+ window.addEventListener("scroll", handleScroll);
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, []);
+
+ const scrollToTop = () => {
+ window.scrollTo({
+ top: 0,
+ behavior: "smooth"
+ });
+ };
+
+ const toggleMenu = () => setIsMenuOpen(!isMenuOpen);
+
+ const handleNavClick = (href: string) => {
+ setIsMenuOpen(false);
+ if (href.startsWith("#")) {
+ const element = document.querySelector(href);
+ if (element) {
+ element.scrollIntoView({ behavior: "smooth" });
+ }
+ }
+ };
+
+ const getRedirectPath = () => {
+ if (userRole === "administrator") {
+ return "/sys-rijig-adminpanel/dashboard";
+ }
+ return "/pengelola/dashboard";
+ };
+
+ const handleGetStarted = () => {
+ if (isAuthenticated && isRegistrationComplete) {
+ const dashboardPath =
+ userRole === "administrator"
+ ? "/sys-rijig-adminpanel/dashboard"
+ : "/pengelola/dashboard";
+ navigate(dashboardPath);
+ } else if (isAuthenticated && !isRegistrationComplete) {
+ const redirectPath = getRedirectPath();
+ navigate(redirectPath);
+ } else {
+ navigate("/pengelola/register");
+ }
+ };
+
+ const handleAdminLogin = () => {
+ navigate("/sys-rijig-adminpanel/login");
+ };
+
+ const getButtonText = () => {
+ if (isAuthenticated && isRegistrationComplete) {
+ return "Go to Dashboard";
+ }
+ return "Get Started";
+ };
+
+ return (
+
+ {/* Header dengan glassmorphism effect */}
+
+
+ {/* Main Content */}
+
+
+
+
+ {/* Back to Top Button */}
+
+
+
+
+ {/* Footer */}
+
+
+
+
+
+
+
+ Platform pengelolaan sampah terpadu yang menghubungkan
+ masyarakat, pengepul, dan pengelola untuk ekonomi sirkular
+ berkelanjutan.
+
+
+
+
+
+
+
+
+
+
+ © 2025 Rijig - layout inspired by appline
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Kebijakan Privasi
+
+
+ Syarat & Ketentuan
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/routes/action.set-theme.ts b/app/routes/action.set-theme.ts
new file mode 100644
index 0000000..f2d8ad8
--- /dev/null
+++ b/app/routes/action.set-theme.ts
@@ -0,0 +1,4 @@
+import { createThemeAction } from "remix-themes"
+import { themeSessionResolver } from "../sessions.server"
+
+export const action = createThemeAction(themeSessionResolver)
\ No newline at end of file
diff --git a/app/routes/pengelola._index.tsx b/app/routes/pengelola._index.tsx
new file mode 100644
index 0000000..f952225
--- /dev/null
+++ b/app/routes/pengelola._index.tsx
@@ -0,0 +1,6 @@
+import { redirect } from "@remix-run/node";
+
+export const loader = async () => {
+ return redirect("/pengelola/dashboard");
+};
+
diff --git a/app/routes/pengelola.dashboard._index.tsx b/app/routes/pengelola.dashboard._index.tsx
new file mode 100644
index 0000000..d616f53
--- /dev/null
+++ b/app/routes/pengelola.dashboard._index.tsx
@@ -0,0 +1,509 @@
+import { json } from "@remix-run/node";
+import { useLoaderData } from "@remix-run/react";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle
+} from "~/components/ui/card";
+import { Badge } from "~/components/ui/badge";
+import { Button } from "~/components/ui/button";
+import { Progress } from "~/components/ui/progress";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
+import {
+ Truck,
+ Users,
+ MapPin,
+ TrendingUp,
+ TrendingDown,
+ Clock,
+ CheckCircle,
+ AlertTriangle,
+ Calendar,
+ BarChart3,
+ Recycle,
+ Package
+} from "lucide-react";
+
+// Interface untuk activity
+interface Activity {
+ id: number;
+ type: string;
+ title: string;
+ time: string;
+ status: string;
+ driver?: string;
+ volume?: string;
+ estimatedComplete?: string;
+ estimatedVolume?: string;
+ reason?: string;
+}
+
+interface Truck {
+ id: string;
+ driver: string;
+ status: string;
+ location: string;
+ capacity: string;
+ lastUpdate: string;
+}
+
+interface WeeklyProgress {
+ day: string;
+ target: number;
+ actual: number;
+}
+
+interface DashboardData {
+ stats: {
+ totalSampahHariIni: number;
+ targetHarian: number;
+ trukAktif: number;
+ totalTruk: number;
+ pegawaiAktif: number;
+ totalPegawai: number;
+ wilayahTerlayani: number;
+ totalWilayah: number;
+ };
+ recentActivities: Activity[];
+ trucks: Truck[];
+ weeklyProgress: WeeklyProgress[];
+}
+
+// Loader untuk mengambil data dashboard
+export const loader = async (): Promise => {
+ // Mock data - dalam implementasi nyata, ambil dari database
+ const dashboardData: DashboardData = {
+ stats: {
+ totalSampahHariIni: 2450, // kg
+ targetHarian: 3000, // kg
+ trukAktif: 8,
+ totalTruk: 12,
+ pegawaiAktif: 24,
+ totalPegawai: 30,
+ wilayahTerlayani: 15,
+ totalWilayah: 18
+ },
+ recentActivities: [
+ {
+ id: 1,
+ type: "pickup",
+ title: "Pengangkutan selesai di Kelurahan Merdeka",
+ time: "10 menit yang lalu",
+ status: "completed",
+ driver: "Budi Santoso",
+ volume: "245 kg"
+ },
+ {
+ id: 2,
+ type: "maintenance",
+ title: "Truk B-002 menjalani maintenance rutin",
+ time: "1 jam yang lalu",
+ status: "in-progress",
+ estimatedComplete: "14:00"
+ },
+ {
+ id: 3,
+ type: "pickup",
+ title: "Pengangkutan dimulai di Komplek Permata",
+ time: "2 jam yang lalu",
+ status: "in-progress",
+ driver: "Andi Wijaya",
+ estimatedVolume: "180 kg"
+ },
+ {
+ id: 4,
+ type: "alert",
+ title: "Jadwal pengangkutan tertunda di Jl. Sudirman",
+ time: "3 jam yang lalu",
+ status: "warning",
+ reason: "Kemacetan lalu lintas"
+ }
+ ],
+ trucks: [
+ {
+ id: "B-001",
+ driver: "Budi Santoso",
+ status: "active",
+ location: "Kelurahan Merdeka",
+ capacity: "85%",
+ lastUpdate: "5 menit yang lalu"
+ },
+ {
+ id: "B-002",
+ driver: "Andi Wijaya",
+ status: "maintenance",
+ location: "Workshop",
+ capacity: "0%",
+ lastUpdate: "1 jam yang lalu"
+ },
+ {
+ id: "B-003",
+ driver: "Sari Dewi",
+ status: "active",
+ location: "Komplek Permata",
+ capacity: "60%",
+ lastUpdate: "15 menit yang lalu"
+ },
+ {
+ id: "B-004",
+ driver: "Dedi Kurniawan",
+ status: "idle",
+ location: "Pool Kendaraan",
+ capacity: "0%",
+ lastUpdate: "2 jam yang lalu"
+ }
+ ],
+ weeklyProgress: [
+ { day: "Sen", target: 3000, actual: 2800 },
+ { day: "Sel", target: 3000, actual: 3200 },
+ { day: "Rab", target: 3000, actual: 2950 },
+ { day: "Kam", target: 3000, actual: 3100 },
+ { day: "Jum", target: 3000, actual: 2750 },
+ { day: "Sab", target: 2500, actual: 2450 },
+ { day: "Min", target: 2000, actual: 0 } // hari ini
+ ]
+ };
+
+ return json(dashboardData);
+};
+
+export default function PengelolaDashboard() {
+ const data = useLoaderData();
+
+ const getStatusBadge = (status: string) => {
+ switch (status) {
+ case "completed":
+ return (
+
+ Selesai
+
+ );
+ case "in-progress":
+ return (
+
+ Berlangsung
+
+ );
+ case "warning":
+ return Peringatan ;
+ case "active":
+ return (
+
+ Aktif
+
+ );
+ case "maintenance":
+ return (
+
+ Maintenance
+
+ );
+ case "idle":
+ return Standby ;
+ default:
+ return {status} ;
+ }
+ };
+
+ const getActivityIcon = (type: string) => {
+ switch (type) {
+ case "pickup":
+ return ;
+ case "maintenance":
+ return ;
+ case "alert":
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const progressPercentage = Math.round(
+ (data.stats.totalSampahHariIni / data.stats.targetHarian) * 100
+ );
+
+ return (
+
+ {/* Header Dashboard */}
+
+
+ Dashboard Pengelola
+
+
+
+
+ Jadwal Hari Ini
+
+
+
+ Laporan
+
+
+
+
+ {/* Cards Statistik Utama */}
+
+
+
+
+ Sampah Terkumpul Hari Ini
+
+
+
+
+
+ {data.stats.totalSampahHariIni.toLocaleString()} kg
+
+
+
+ {progressPercentage}% dari target (
+ {data.stats.targetHarian.toLocaleString()} kg)
+
+
+
+
+
+
+
+ Truk Operasional
+
+
+
+
+
+ {data.stats.trukAktif}/{data.stats.totalTruk}
+
+
+
+ {Math.round((data.stats.trukAktif / data.stats.totalTruk) * 100)}%
+ truk sedang beroperasi
+
+
+
+
+
+
+ Pegawai Aktif
+
+
+
+
+ {data.stats.pegawaiAktif}/{data.stats.totalPegawai}
+
+
+
+ {Math.round(
+ (data.stats.pegawaiAktif / data.stats.totalPegawai) * 100
+ )}
+ % pegawai sedang bertugas
+
+
+
+
+
+
+
+ Wilayah Terlayani
+
+
+
+
+
+ {data.stats.wilayahTerlayani}/{data.stats.totalWilayah}
+
+
+
+ {Math.round(
+ (data.stats.wilayahTerlayani / data.stats.totalWilayah) * 100
+ )}
+ % wilayah tercover hari ini
+
+
+
+
+
+ {/* Tabs untuk konten detail */}
+
+
+ Aktivitas Terbaru
+ Status Truk
+ Performa Mingguan
+
+
+
+
+
+ Aktivitas Terbaru
+
+ Pantau semua aktivitas operasional secara real-time
+
+
+
+
+ {data.recentActivities.map((activity) => (
+
+
+ {getActivityIcon(activity.type)}
+
+
+
+ {activity.title}
+
+
+
{activity.time}
+ {activity.driver && (
+
+ • Driver: {activity.driver}
+
+ )}
+ {activity.volume && (
+
+ • {activity.volume}
+
+ )}
+ {activity.estimatedVolume && (
+
+ • Est: {activity.estimatedVolume}
+
+ )}
+ {activity.estimatedComplete && (
+
+ • Selesai: {activity.estimatedComplete}
+
+ )}
+ {activity.reason && (
+
+ • {activity.reason}
+
+ )}
+
+
+
+ {getStatusBadge(activity.status)}
+
+
+ ))}
+
+
+
+
+
+
+
+
+ Status Truk Real-time
+
+ Monitor kondisi dan lokasi semua armada truk
+
+
+
+
+ {data.trucks.map((truck) => (
+
+
+
+
+
+
+
{truck.id}
+
+ Driver: {truck.driver}
+
+
+ Update: {truck.lastUpdate}
+
+
+
+
+
+ {getStatusBadge(truck.status)}
+
+
{truck.location}
+
+ Kapasitas: {truck.capacity}
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ Performa Mingguan
+
+ Pencapaian target pengumpulan sampah minggu ini
+
+
+
+
+ {data.weeklyProgress.map((day, index) => (
+
+
+
{day.day}
+
+
+
+ Target: {day.target.toLocaleString()} kg
+
+
+ Actual: {day.actual.toLocaleString()} kg
+
+
+
0 ? (day.actual / day.target) * 100 : 0
+ }
+ className="mt-1 h-2"
+ />
+
+
+
+ {day.actual >= day.target ? (
+
+ ) : day.actual > 0 ? (
+
+ ) : (
+
+ )}
+
+ {day.actual > 0
+ ? `${Math.round((day.actual / day.target) * 100)}%`
+ : "-"}
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/app/routes/pengelola.dashboard.chat._index.tsx b/app/routes/pengelola.dashboard.chat._index.tsx
new file mode 100644
index 0000000..4691006
--- /dev/null
+++ b/app/routes/pengelola.dashboard.chat._index.tsx
@@ -0,0 +1,596 @@
+import { json } from "@remix-run/node";
+import { useLoaderData } from "@remix-run/react";
+import { useState, useRef, useEffect } from "react";
+import { Card, CardContent, CardHeader } from "~/components/ui/card";
+import { Button } from "~/components/ui/button";
+import { Input } from "~/components/ui/input";
+import { Badge } from "~/components/ui/badge";
+import { ScrollArea } from "~/components/ui/scroll-area";
+import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";
+import { Separator } from "~/components/ui/separator";
+import { Textarea } from "~/components/ui/textarea";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger
+} from "~/components/ui/dropdown-menu";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger
+} from "~/components/ui/dialog";
+import {
+ Search,
+ Send,
+ Phone,
+ Video,
+ MoreVertical,
+ Paperclip,
+ Image,
+ MapPin,
+ Clock,
+ Check,
+ CheckCheck,
+ User,
+ MessageCircle,
+ Truck,
+ Package,
+ AlertCircle
+} from "lucide-react";
+
+// Interfaces
+interface Message {
+ id: string;
+ senderId: string;
+ senderName: string;
+ content: string;
+ timestamp: string;
+ type: "text" | "image" | "location" | "document";
+ status: "sent" | "delivered" | "read";
+ attachmentUrl?: string;
+}
+
+interface Contact {
+ id: string;
+ name: string;
+ role: string;
+ avatar?: string;
+ isOnline: boolean;
+ lastSeen: string;
+ lastMessage: string;
+ unreadCount: number;
+ location?: string;
+ phone: string;
+}
+
+interface ChatData {
+ contacts: Contact[];
+ messages: { [contactId: string]: Message[] };
+ currentUser: {
+ id: string;
+ name: string;
+ };
+}
+
+export const loader = async (): Promise => {
+ // Mock data - dalam implementasi nyata, ambil dari database
+ const chatData: ChatData = {
+ currentUser: {
+ id: "pengelola-001",
+ name: "Ahmad Pengelola"
+ },
+ contacts: [
+ {
+ id: "pengepul-001",
+ name: "Budi Santoso",
+ role: "Driver Truk B-001",
+ avatar: "",
+ isOnline: true,
+ lastSeen: "Online",
+ lastMessage: "Sampah sudah diangkut semua pak",
+ unreadCount: 2,
+ location: "Kelurahan Merdeka",
+ phone: "081234567890"
+ },
+ {
+ id: "pengepul-002",
+ name: "Sari Dewi",
+ role: "Driver Truk B-003",
+ avatar: "",
+ isOnline: false,
+ lastSeen: "15 menit yang lalu",
+ lastMessage: "Baik pak, segera ke lokasi",
+ unreadCount: 0,
+ location: "Komplek Permata",
+ phone: "081234567891"
+ },
+ {
+ id: "pengepul-003",
+ name: "Dedi Kurniawan",
+ role: "Driver Truk B-004",
+ avatar: "",
+ isOnline: true,
+ lastSeen: "Online",
+ lastMessage: "Truk sedang dalam perjalanan",
+ unreadCount: 1,
+ location: "Jl. Sudirman",
+ phone: "081234567892"
+ },
+ {
+ id: "pengepul-004",
+ name: "Andi Wijaya",
+ role: "Driver Truk B-002",
+ avatar: "",
+ isOnline: false,
+ lastSeen: "2 jam yang lalu",
+ lastMessage: "Maintenance selesai besok pagi",
+ unreadCount: 0,
+ location: "Workshop",
+ phone: "081234567893"
+ },
+ {
+ id: "pengepul-005",
+ name: "Rini Astuti",
+ role: "Supervisor Lapangan",
+ avatar: "",
+ isOnline: true,
+ lastSeen: "Online",
+ lastMessage: "Laporan harian sudah dikirim",
+ unreadCount: 0,
+ location: "Pool Kendaraan",
+ phone: "081234567894"
+ }
+ ],
+ messages: {
+ "pengepul-001": [
+ {
+ id: "msg-001",
+ senderId: "pengepul-001",
+ senderName: "Budi Santoso",
+ content:
+ "Selamat pagi pak, saya sudah sampai di lokasi Kelurahan Merdeka",
+ timestamp: "08:00",
+ type: "text",
+ status: "read"
+ },
+ {
+ id: "msg-002",
+ senderId: "pengelola-001",
+ senderName: "Ahmad Pengelola",
+ content: "Pagi Bud, bagus. Berapa estimasi waktu pengangkutan?",
+ timestamp: "08:02",
+ type: "text",
+ status: "read"
+ },
+ {
+ id: "msg-003",
+ senderId: "pengepul-001",
+ senderName: "Budi Santoso",
+ content: "Sekitar 2 jam pak, volume sampah cukup banyak hari ini",
+ timestamp: "08:05",
+ type: "text",
+ status: "read"
+ },
+ {
+ id: "msg-004",
+ senderId: "pengepul-001",
+ senderName: "Budi Santoso",
+ content: "Sampah sudah diangkut semua pak",
+ timestamp: "10:30",
+ type: "text",
+ status: "delivered"
+ },
+ {
+ id: "msg-005",
+ senderId: "pengepul-001",
+ senderName: "Budi Santoso",
+ content: "Total 245 kg, lanjut ke lokasi berikutnya?",
+ timestamp: "10:31",
+ type: "text",
+ status: "sent"
+ }
+ ],
+ "pengepul-002": [
+ {
+ id: "msg-006",
+ senderId: "pengelola-001",
+ senderName: "Ahmad Pengelola",
+ content: "Sari, bisa ke Komplek Permata sekarang?",
+ timestamp: "09:15",
+ type: "text",
+ status: "read"
+ },
+ {
+ id: "msg-007",
+ senderId: "pengepul-002",
+ senderName: "Sari Dewi",
+ content: "Baik pak, segera ke lokasi",
+ timestamp: "09:17",
+ type: "text",
+ status: "read"
+ }
+ ],
+ "pengepul-003": [
+ {
+ id: "msg-008",
+ senderId: "pengepul-003",
+ senderName: "Dedi Kurniawan",
+ content:
+ "Pak, ada kemacetan di Jl. Sudirman, mungkin terlambat 30 menit",
+ timestamp: "11:00",
+ type: "text",
+ status: "read"
+ },
+ {
+ id: "msg-009",
+ senderId: "pengelola-001",
+ senderName: "Ahmad Pengelola",
+ content: "OK Ded, hati-hati di jalan. Update terus ya",
+ timestamp: "11:05",
+ type: "text",
+ status: "read"
+ },
+ {
+ id: "msg-010",
+ senderId: "pengepul-003",
+ senderName: "Dedi Kurniawan",
+ content: "Truk sedang dalam perjalanan",
+ timestamp: "11:45",
+ type: "text",
+ status: "delivered"
+ }
+ ]
+ }
+ };
+
+ return json(chatData);
+};
+
+export default function ChatPengepul() {
+ const data = useLoaderData();
+ const [selectedContact, setSelectedContact] = useState(
+ data.contacts[0]
+ );
+ const [messageInput, setMessageInput] = useState("");
+ const [searchQuery, setSearchQuery] = useState("");
+ const scrollAreaRef = useRef(null);
+
+ // Filter contacts berdasarkan search
+ const filteredContacts = data.contacts.filter(
+ (contact) =>
+ contact.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ contact.role.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ // Get messages untuk contact yang dipilih
+ const currentMessages = selectedContact
+ ? data.messages[selectedContact.id] || []
+ : [];
+
+ // Auto scroll ke pesan terbaru
+ useEffect(() => {
+ if (scrollAreaRef.current) {
+ scrollAreaRef.current.scrollTop = scrollAreaRef.current.scrollHeight;
+ }
+ }, [currentMessages]);
+
+ const handleSendMessage = () => {
+ if (!messageInput.trim() || !selectedContact) return;
+
+ // Implementasi kirim pesan (dalam real app, hit API)
+ console.log("Sending message:", messageInput, "to:", selectedContact.name);
+ setMessageInput("");
+ };
+
+ const getStatusIcon = (status: string) => {
+ switch (status) {
+ case "sent":
+ return ;
+ case "delivered":
+ return ;
+ case "read":
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getContactStatusBadge = (contact: Contact) => {
+ if (contact.isOnline) {
+ return (
+
+ Online
+
+ );
+ }
+ return (
+
+ {contact.lastSeen}
+
+ );
+ };
+
+ return (
+
+ {/* Header */}
+
+
Chat Pengepul
+
+
+
+ Panggil Semua
+
+
+
+ Broadcast
+
+
+
+
+ {/* Chat Container */}
+
+ {/* Sidebar Contact List */}
+
+
+
+
+ setSearchQuery(e.target.value)}
+ className="border-none shadow-none focus-visible:ring-0"
+ />
+
+
+
+
+
+ {filteredContacts.map((contact) => (
+
setSelectedContact(contact)}
+ className={`p-3 rounded-lg cursor-pointer transition-colors ${
+ selectedContact?.id === contact.id
+ ? "bg-primary/10 border border-primary/20"
+ : "hover:bg-gray-50"
+ }`}
+ >
+
+
+
+
+
+ {contact.name
+ .split(" ")
+ .map((n) => n[0])
+ .join("")}
+
+
+ {contact.isOnline && (
+
+ )}
+
+
+
+
+ {contact.name}
+
+ {contact.unreadCount > 0 && (
+
+ {contact.unreadCount}
+
+ )}
+
+
+
+ {contact.lastMessage}
+
+
+
+
+
+ {contact.location}
+
+
+ {getContactStatusBadge(contact)}
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* Chat Area */}
+
+ {selectedContact ? (
+ <>
+ {/* Chat Header */}
+
+
+
+
+
+ {selectedContact.name
+ .split(" ")
+ .map((n) => n[0])
+ .join("")}
+
+
+
+
{selectedContact.name}
+
+
+ {selectedContact.role}
+
+ {getContactStatusBadge(selectedContact)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Lihat Profil
+
+
+
+ Lacak Lokasi
+
+
+
+ Riwayat Tugas
+
+
+
+
+
+
+
+
+ {/* Messages Area */}
+
+
+
+ {currentMessages.map((message) => {
+ const isOwnMessage =
+ message.senderId === data.currentUser.id;
+ return (
+
+
+
{message.content}
+
+
+ {message.timestamp}
+
+ {isOwnMessage && (
+
+ {getStatusIcon(message.status)}
+
+ )}
+
+
+
+ );
+ })}
+
+
+
+
+ {/* Message Input */}
+
+
+
+
+
+
+
+
+
+
+
+ Kirim Foto
+
+
+
+ Bagikan Lokasi
+
+
+
+ Kirim Dokumen
+
+
+
+
+
+
+
+
+ >
+ ) : (
+ // Empty State
+
+
+
+
+ Pilih Chat
+
+
+ Pilih pengepul untuk mulai berkomunikasi
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/app/routes/pengelola.dashboard.collection._index.tsx b/app/routes/pengelola.dashboard.collection._index.tsx
new file mode 100644
index 0000000..e204a86
--- /dev/null
+++ b/app/routes/pengelola.dashboard.collection._index.tsx
@@ -0,0 +1,984 @@
+import { json } from "@remix-run/node";
+import { useLoaderData } from "@remix-run/react";
+import { useState, useEffect } from "react";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle
+} from "~/components/ui/card";
+import { Badge } from "~/components/ui/badge";
+import { Button } from "~/components/ui/button";
+import { Progress } from "~/components/ui/progress";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
+import { Input } from "~/components/ui/input";
+import { Label } from "~/components/ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue
+} from "~/components/ui/select";
+import { ScrollArea } from "~/components/ui/scroll-area";
+import { Separator } from "~/components/ui/separator";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger
+} from "~/components/ui/dialog";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger
+} from "~/components/ui/alert-dialog";
+import {
+ Truck,
+ MapPin,
+ Clock,
+ CheckCircle,
+ AlertTriangle,
+ XCircle,
+ RotateCcw,
+ Users,
+ Target,
+ TrendingUp,
+ Calendar,
+ RefreshCw,
+ Phone,
+ MessageSquare,
+ Plus,
+ Edit,
+ Trash2,
+ Navigation,
+ Package,
+ Timer,
+ Zap
+} from "lucide-react";
+
+// Interfaces
+interface CollectionArea {
+ id: string;
+ name: string;
+ zone: string;
+ priority: "high" | "medium" | "low";
+ status: "pending" | "in-progress" | "completed" | "overdue" | "cancelled";
+ scheduledTime: string;
+ estimatedDuration: number; // minutes
+ actualStartTime?: string;
+ completedTime?: string;
+ assignedTruck: string;
+ assignedDriver: string;
+ driverContact: string;
+ estimatedVolume: number; // kg
+ actualVolume?: number; // kg
+ households: number;
+ lastCollection: string;
+ notes?: string;
+ coordinates: { lat: number; lng: number };
+ urgentIssues?: string[];
+}
+
+interface DailyStats {
+ totalAreas: number;
+ completedAreas: number;
+ inProgressAreas: number;
+ overdueAreas: number;
+ totalTargetVolume: number;
+ collectedVolume: number;
+ activeTrucks: number;
+ availableTrucks: number;
+ estimatedCompletion: string;
+}
+
+interface TruckStatus {
+ id: string;
+ driver: string;
+ contact: string;
+ status: "active" | "available" | "maintenance" | "break";
+ currentLocation?: string;
+ currentCapacity: number; // percentage
+ assignedAreas: string[];
+ lastUpdate: string;
+}
+
+interface CollectionData {
+ date: string;
+ stats: DailyStats;
+ areas: CollectionArea[];
+ trucks: TruckStatus[];
+ emergencyContacts: {
+ supervisor: string;
+ dispatcher: string;
+ maintenance: string;
+ };
+}
+
+export const loader = async (): Promise => {
+ // Mock data - dalam implementasi nyata, ambil dari database
+ const today = new Date().toISOString().split("T")[0];
+
+ const collectionData: CollectionData = {
+ date: today,
+ stats: {
+ totalAreas: 18,
+ completedAreas: 12,
+ inProgressAreas: 4,
+ overdueAreas: 2,
+ totalTargetVolume: 4500,
+ collectedVolume: 3200,
+ activeTrucks: 6,
+ availableTrucks: 2,
+ estimatedCompletion: "16:30"
+ },
+ areas: [
+ {
+ id: "area-001",
+ name: "Kelurahan Merdeka Blok A",
+ zone: "Zona Utara",
+ priority: "high",
+ status: "overdue",
+ scheduledTime: "07:00",
+ estimatedDuration: 90,
+ assignedTruck: "B-001",
+ assignedDriver: "Budi Santoso",
+ driverContact: "081234567890",
+ estimatedVolume: 280,
+ actualVolume: 245,
+ households: 120,
+ lastCollection: "2025-07-05",
+ coordinates: { lat: -6.2088, lng: 106.8456 },
+ urgentIssues: ["Jalan rusak", "Akses terbatas"],
+ actualStartTime: "07:15",
+ completedTime: "09:00"
+ },
+ {
+ id: "area-002",
+ name: "Komplek Permata Indah",
+ zone: "Zona Selatan",
+ priority: "medium",
+ status: "in-progress",
+ scheduledTime: "08:30",
+ estimatedDuration: 60,
+ assignedTruck: "B-003",
+ assignedDriver: "Sari Dewi",
+ driverContact: "081234567891",
+ estimatedVolume: 180,
+ households: 85,
+ lastCollection: "2025-07-05",
+ coordinates: { lat: -6.22, lng: 106.83 },
+ actualStartTime: "08:45"
+ },
+ {
+ id: "area-003",
+ name: "Jl. Sudirman Raya",
+ zone: "Zona Tengah",
+ priority: "high",
+ status: "overdue",
+ scheduledTime: "09:00",
+ estimatedDuration: 120,
+ assignedTruck: "B-004",
+ assignedDriver: "Dedi Kurniawan",
+ driverContact: "081234567892",
+ estimatedVolume: 350,
+ households: 200,
+ lastCollection: "2025-07-04",
+ coordinates: { lat: -6.215, lng: 106.84 },
+ urgentIssues: ["Volume sangat tinggi", "Kemacetan akses"],
+ notes: "Perlu 2 trip untuk mengangkut semua"
+ },
+ {
+ id: "area-004",
+ name: "Perumahan Indah Permai",
+ zone: "Zona Timur",
+ priority: "medium",
+ status: "completed",
+ scheduledTime: "10:00",
+ estimatedDuration: 75,
+ assignedTruck: "B-002",
+ assignedDriver: "Andi Wijaya",
+ driverContact: "081234567893",
+ estimatedVolume: 200,
+ actualVolume: 195,
+ households: 95,
+ lastCollection: "2025-07-05",
+ coordinates: { lat: -6.195, lng: 106.86 },
+ actualStartTime: "10:15",
+ completedTime: "11:30"
+ },
+ {
+ id: "area-005",
+ name: "Pasar Tradisional Sentral",
+ zone: "Zona Tengah",
+ priority: "high",
+ status: "in-progress",
+ scheduledTime: "11:30",
+ estimatedDuration: 45,
+ assignedTruck: "B-005",
+ assignedDriver: "Rini Astuti",
+ driverContact: "081234567894",
+ estimatedVolume: 420,
+ households: 50,
+ lastCollection: "2025-07-05",
+ coordinates: { lat: -6.21, lng: 106.835 },
+ actualStartTime: "11:45",
+ notes: "Sampah organik tinggi dari pasar"
+ },
+ {
+ id: "area-006",
+ name: "Cluster Villa Harmoni",
+ zone: "Zona Barat",
+ priority: "low",
+ status: "pending",
+ scheduledTime: "13:00",
+ estimatedDuration: 60,
+ assignedTruck: "B-006",
+ assignedDriver: "Toni Setiawan",
+ driverContact: "081234567895",
+ estimatedVolume: 150,
+ households: 75,
+ lastCollection: "2025-07-05",
+ coordinates: { lat: -6.205, lng: 106.82 }
+ }
+ ],
+ trucks: [
+ {
+ id: "B-001",
+ driver: "Budi Santoso",
+ contact: "081234567890",
+ status: "active",
+ currentLocation: "Kelurahan Merdeka",
+ currentCapacity: 85,
+ assignedAreas: ["area-001"],
+ lastUpdate: "10 menit yang lalu"
+ },
+ {
+ id: "B-002",
+ driver: "Andi Wijaya",
+ contact: "081234567893",
+ status: "available",
+ currentLocation: "Pool Kendaraan",
+ currentCapacity: 0,
+ assignedAreas: ["area-004"],
+ lastUpdate: "5 menit yang lalu"
+ },
+ {
+ id: "B-003",
+ driver: "Sari Dewi",
+ contact: "081234567891",
+ status: "active",
+ currentLocation: "Komplek Permata",
+ currentCapacity: 60,
+ assignedAreas: ["area-002"],
+ lastUpdate: "2 menit yang lalu"
+ },
+ {
+ id: "B-004",
+ driver: "Dedi Kurniawan",
+ contact: "081234567892",
+ status: "active",
+ currentLocation: "Dalam perjalanan",
+ currentCapacity: 0,
+ assignedAreas: ["area-003"],
+ lastUpdate: "15 menit yang lalu"
+ },
+ {
+ id: "B-005",
+ driver: "Rini Astuti",
+ contact: "081234567894",
+ status: "active",
+ currentLocation: "Pasar Sentral",
+ currentCapacity: 40,
+ assignedAreas: ["area-005"],
+ lastUpdate: "1 menit yang lalu"
+ },
+ {
+ id: "B-006",
+ driver: "Toni Setiawan",
+ contact: "081234567895",
+ status: "available",
+ currentLocation: "Pool Kendaraan",
+ currentCapacity: 0,
+ assignedAreas: ["area-006"],
+ lastUpdate: "30 menit yang lalu"
+ }
+ ],
+ emergencyContacts: {
+ supervisor: "081234560001",
+ dispatcher: "081234560002",
+ maintenance: "081234560003"
+ }
+ };
+
+ return json(collectionData);
+};
+
+export default function PengumpulanHarian() {
+ const data = useLoaderData();
+ const [selectedArea, setSelectedArea] = useState(null);
+ const [viewMode, setViewMode] = useState<"list" | "map">("list");
+ const [filterStatus, setFilterStatus] = useState("all");
+ const [filterPriority, setFilterPriority] = useState("all");
+
+ // Filter areas
+ const filteredAreas = data.areas.filter((area) => {
+ const statusMatch = filterStatus === "all" || area.status === filterStatus;
+ const priorityMatch =
+ filterPriority === "all" || area.priority === filterPriority;
+ return statusMatch && priorityMatch;
+ });
+
+ // Calculate progress percentage
+ const progressPercentage = Math.round(
+ (data.stats.completedAreas / data.stats.totalAreas) * 100
+ );
+ const volumePercentage = Math.round(
+ (data.stats.collectedVolume / data.stats.totalTargetVolume) * 100
+ );
+
+ const getStatusBadge = (status: string) => {
+ switch (status) {
+ case "completed":
+ return (
+
+ Selesai
+
+ );
+ case "in-progress":
+ return (
+
+ Berlangsung
+
+ );
+ case "pending":
+ return Menunggu ;
+ case "overdue":
+ return Terlambat ;
+ case "cancelled":
+ return (
+
+ Dibatalkan
+
+ );
+ default:
+ return {status} ;
+ }
+ };
+
+ const getPriorityBadge = (priority: string) => {
+ switch (priority) {
+ case "high":
+ return Tinggi ;
+ case "medium":
+ return (
+
+ Sedang
+
+ );
+ case "low":
+ return Rendah ;
+ default:
+ return {priority} ;
+ }
+ };
+
+ const getStatusIcon = (status: string) => {
+ switch (status) {
+ case "completed":
+ return ;
+ case "in-progress":
+ return ;
+ case "pending":
+ return ;
+ case "overdue":
+ return ;
+ case "cancelled":
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getTruckStatusBadge = (status: string) => {
+ switch (status) {
+ case "active":
+ return (
+
+ Aktif
+
+ );
+ case "available":
+ return Tersedia ;
+ case "maintenance":
+ return (
+
+ Maintenance
+
+ );
+ case "break":
+ return Istirahat ;
+ default:
+ return {status} ;
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+ Pengumpulan Harian
+
+
+ URGENT
+
+
+
+ Monitoring dan koordinasi pengumpulan sampah -{" "}
+ {new Date(data.date).toLocaleDateString("id-ID", {
+ weekday: "long",
+ year: "numeric",
+ month: "long",
+ day: "numeric"
+ })}
+
+
+
+
+
+ Refresh
+
+
+
+
+
+ Emergency
+
+
+
+
+ Emergency Response
+
+ Hubungi kontak darurat untuk situasi mendesak
+
+
+
+
+
+
Supervisor
+
+ {data.emergencyContacts.supervisor}
+
+
+
+
+
+
+
+
+
Dispatcher
+
+ {data.emergencyContacts.dispatcher}
+
+
+
+
+
+
+
+
+
Maintenance
+
+ {data.emergencyContacts.maintenance}
+
+
+
+
+
+
+
+
+ Tutup
+
+
+
+
+
+ Tambah Area
+
+
+
+
+ {/* Stats Cards */}
+
+
+
+
+ Progress Harian
+
+
+
+
+
+ {data.stats.completedAreas}/{data.stats.totalAreas}
+
+
+
+ {progressPercentage}% area selesai • Est. selesai{" "}
+ {data.stats.estimatedCompletion}
+
+
+
+
+
+
+
+ Volume Terkumpul
+
+
+
+
+
+ {data.stats.collectedVolume.toLocaleString()} kg
+
+
+
+ {volumePercentage}% dari target (
+ {data.stats.totalTargetVolume.toLocaleString()} kg)
+
+
+
+
+
+
+ Status Truk
+
+
+
+
+ {data.stats.activeTrucks}/
+ {data.stats.activeTrucks + data.stats.availableTrucks}
+
+
+
+
+
{data.stats.activeTrucks} Aktif
+
+
+
+
+ {data.stats.availableTrucks} Tersedia
+
+
+
+
+
+
+
+
+
+ Area Terlambat
+
+
+
+
+
+ {data.stats.overdueAreas}
+
+
+ Perlu penanganan segera
+
+
+ {data.stats.inProgressAreas} area sedang dikerjakan
+
+
+
+
+
+ {/* Filters and View Toggle */}
+
+
+
+ Status:
+
+
+
+
+
+ Semua
+ Menunggu
+ Berlangsung
+ Selesai
+ Terlambat
+
+
+
+
+
+ Prioritas:
+
+
+
+
+
+ Semua
+ Tinggi
+ Sedang
+ Rendah
+
+
+
+
+
+
+ setViewMode("list")}
+ >
+ List
+
+ setViewMode("map")}
+ >
+ Map
+
+
+
+
+ {/* Main Content */}
+
+
+ Area Pengumpulan
+ Status Truk
+ Timeline Hari Ini
+
+
+
+
+ {filteredAreas.map((area) => (
+
+
+
+
+ {getStatusIcon(area.status)}
+
+ {area.name}
+
+ {area.zone}
+ •
+ {area.households} rumah
+ •
+ Truk {area.assignedTruck}
+
+
+
+
+ {getPriorityBadge(area.priority)}
+ {getStatusBadge(area.status)}
+
+
+
+
+
+
+
+ Jadwal & Progress
+
+
+
+
+ Target: {area.scheduledTime}
+
+ {area.actualStartTime && (
+
+
+ Mulai: {area.actualStartTime}
+
+ )}
+ {area.completedTime && (
+
+
+ Selesai: {area.completedTime}
+
+ )}
+
+
+
+ {area.actualVolume
+ ? `${area.actualVolume} kg`
+ : `Est. ${area.estimatedVolume} kg`}
+
+
+
+
+
+
+
+ Driver & Kontak
+
+
+
+
+ {area.assignedDriver}
+
+
+
+
{area.driverContact}
+
+
+
+ Truk {area.assignedTruck}
+
+
+
+
+
+
Actions
+
+
+
+ Call
+
+
+
+ Chat
+
+
+
+ Track
+
+
+
+
+
+ Edit
+
+
+
+
+
+ Update Status - {area.name}
+
+
+ Update status pengumpulan dan informasi terkait
+
+
+
+
+ Status
+
+
+
+
+
+
+ Menunggu
+
+
+ Berlangsung
+
+
+ Selesai
+
+
+ Terlambat
+
+
+ Dibatalkan
+
+
+
+
+
+
+ Volume Aktual (kg)
+
+
+
+
+ Catatan
+
+
+
Update Status
+
+
+
+
+
+
+
+ {area.urgentIssues && area.urgentIssues.length > 0 && (
+
+
+
+ {area.urgentIssues.map((issue, index) => (
+ • {issue}
+ ))}
+
+
+ )}
+
+ {area.notes && (
+
+
+ Catatan: {area.notes}
+
+
+ )}
+
+
+ ))}
+
+
+
+
+
+ {data.trucks.map((truck) => (
+
+
+
+
+
+
+ Truk {truck.id}
+ {truck.driver}
+
+
+ {getTruckStatusBadge(truck.status)}
+
+
+
+
+
+ Kapasitas:
+
+ {truck.currentCapacity}%
+
+
+
+
+
+
+ {truck.currentLocation}
+
+
+
+
+
+ Update: {truck.lastUpdate}
+
+
+
+
+
+
+ Call
+
+
+
+ Track
+
+
+
+ Chat
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+ Timeline Pengumpulan Hari Ini
+
+ Jadwal dan progress pengumpulan sampah realtime
+
+
+
+
+
+ {data.areas
+ .sort((a, b) =>
+ a.scheduledTime.localeCompare(b.scheduledTime)
+ )
+ .map((area, index) => (
+
+
+ {getStatusIcon(area.status)}
+
+
+
+
{area.name}
+
+ {getPriorityBadge(area.priority)}
+ {getStatusBadge(area.status)}
+
+
+
+ {area.scheduledTime} • {area.assignedDriver} • Truk{" "}
+ {area.assignedTruck}
+
+ {area.actualStartTime && (
+
+ Dimulai: {area.actualStartTime}
+
+ )}
+ {area.completedTime && (
+
+ Selesai: {area.completedTime} • Volume:{" "}
+ {area.actualVolume} kg
+
+ )}
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/routes/pengelola.tsx b/app/routes/pengelola.tsx
new file mode 100644
index 0000000..36f6fee
--- /dev/null
+++ b/app/routes/pengelola.tsx
@@ -0,0 +1,25 @@
+import { json } from "@remix-run/node";
+import { Outlet, useLoaderData } from "@remix-run/react";
+import { PengelolaLayoutWrapper } from "~/components/layoutpengelola/layout-wrapper";
+
+export const loader = async () => {
+ // Data untuk layout bisa diambil di sini
+ return json({
+ user: {
+ name: "Fahmi Kurniawan",
+ email: "pengelola@example.com",
+ role: "Pengelola"
+ }
+ });
+};
+
+export default function PengelolaPanelLayout() {
+ const { user } = useLoaderData();
+
+ return (
+
+ {/* Outlet akan merender child routes */}
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/routes/sys-rijig-adminpanel._index.tsx b/app/routes/sys-rijig-adminpanel._index.tsx
new file mode 100644
index 0000000..50ee54b
--- /dev/null
+++ b/app/routes/sys-rijig-adminpanel._index.tsx
@@ -0,0 +1,6 @@
+import { redirect } from "@remix-run/node";
+
+export const loader = async () => {
+ return redirect("/sys-rijig-adminpanel/dashboard");
+};
+
diff --git a/app/routes/sys-rijig-adminpanel.dashboard._index.tsx b/app/routes/sys-rijig-adminpanel.dashboard._index.tsx
new file mode 100644
index 0000000..23a2ace
--- /dev/null
+++ b/app/routes/sys-rijig-adminpanel.dashboard._index.tsx
@@ -0,0 +1,352 @@
+import { json } from "@remix-run/node";
+import { useLoaderData } from "@remix-run/react";
+import {
+ BarChart3,
+ Users,
+ Recycle,
+ DollarSign,
+ TrendingUp,
+ TrendingDown,
+ Package,
+ MapPin,
+ Clock,
+ AlertCircle
+} from "lucide-react";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle
+} from "~/components/ui/card";
+import { Badge } from "~/components/ui/badge";
+import { Button } from "~/components/ui/button";
+
+export const loader = async () => {
+ const dashboardData = {
+ stats: {
+ totalUsers: 1234,
+ totalWaste: 5678,
+ totalRevenue: 98765,
+ activeCollectors: 45
+ },
+ recentTransactions: [
+ {
+ id: 1,
+ user: "Ahmad Rizki",
+ type: "Plastik",
+ amount: 15000,
+ time: "2 menit lalu"
+ },
+ {
+ id: 2,
+ user: "Siti Nurhaliza",
+ type: "Kertas",
+ amount: 8500,
+ time: "5 menit lalu"
+ },
+ {
+ id: 3,
+ user: "Budi Santoso",
+ type: "Logam",
+ amount: 25000,
+ time: "10 menit lalu"
+ },
+ {
+ id: 4,
+ user: "Diana Putri",
+ type: "Plastik",
+ amount: 12000,
+ time: "15 menit lalu"
+ }
+ ],
+ wasteStats: [
+ { type: "Plastik", percentage: 45, color: "bg-blue-500" },
+ { type: "Kertas", percentage: 30, color: "bg-green-500" },
+ { type: "Logam", percentage: 15, color: "bg-yellow-500" },
+ { type: "Organik", percentage: 10, color: "bg-red-500" }
+ ],
+ alerts: [
+ {
+ id: 1,
+ message: "Kapasitas gudang Pengepul A mencapai 85%",
+ type: "warning",
+ time: "1 jam lalu"
+ },
+ {
+ id: 2,
+ message: "Harga plastik naik 15% hari ini",
+ type: "info",
+ time: "2 jam lalu"
+ },
+ {
+ id: 3,
+ message: "5 pengepul baru menunggu verifikasi",
+ type: "urgent",
+ time: "3 jam lalu"
+ }
+ ]
+ };
+
+ return json({ dashboardData });
+};
+
+export default function AdminDashboard() {
+ const { dashboardData } = useLoaderData();
+
+ return (
+
+ {/* Header */}
+
+
+
+ Dashboard Overview
+
+
+ Kelola ekosistem pengelolaan sampah secara terpadu
+
+
+
+
+
+ Export Report
+
+
+
+ View Analytics
+
+
+
+
+ {/* Stats Cards */}
+
+
+
+
+ Total Pengguna
+
+
+
+
+
+ {dashboardData.stats.totalUsers.toLocaleString()}
+
+
+
+ +12% dari bulan lalu
+
+
+
+
+
+
+
+ Total Sampah (kg)
+
+
+
+
+
+ {dashboardData.stats.totalWaste.toLocaleString()}
+
+
+
+ +8% dari bulan lalu
+
+
+
+
+
+
+
+ Total Revenue
+
+
+
+
+
+ Rp {dashboardData.stats.totalRevenue.toLocaleString()}
+
+
+
+ +23% dari bulan lalu
+
+
+
+
+
+
+
+ Pengepul Aktif
+
+
+
+
+
+ {dashboardData.stats.activeCollectors}
+
+
+
+ -2% dari bulan lalu
+
+
+
+
+
+ {/* Content Grid */}
+
+ {/* Recent Transactions */}
+
+
+
+
+ Transaksi Terbaru
+
+
+ Aktivitas transaksi sampah dalam 24 jam terakhir
+
+
+
+
+ {dashboardData.recentTransactions.map((transaction) => (
+
+
+
+
+
+
+
+ {transaction.user}
+
+
+ {transaction.type}
+
+
+
+
+
+ Rp {transaction.amount.toLocaleString()}
+
+
+
+ {transaction.time}
+
+
+
+ ))}
+
+
+ Lihat Semua Transaksi
+
+
+
+
+ {/* Side Panel */}
+
+ {/* Waste Statistics */}
+
+
+ Distribusi Jenis Sampah
+ Persentase berdasarkan volume
+
+
+ {dashboardData.wasteStats.map((waste, index) => (
+
+
+ {waste.type}
+ {waste.percentage}%
+
+
+
+ ))}
+
+
+
+ {/* Alerts */}
+
+
+
+
+ Notifikasi Penting
+
+
+
+ {dashboardData.alerts.map((alert) => (
+
+
+
+ {alert.message}
+
+
+ {alert.type}
+
+
+
+
+ {alert.time}
+
+
+ ))}
+
+
+
+
+
+ {/* Quick Actions */}
+
+
+ Quick Actions
+ Aksi cepat untuk mengelola sistem
+
+
+
+
+
+ Kelola User
+
+
+
+ Data Sampah
+
+
+
+ Laporan
+
+
+
+ Peta Lokasi
+
+
+
+
+
+ );
+}
diff --git a/app/routes/sys-rijig-adminpanel.dashboard.areacoverage.tsx b/app/routes/sys-rijig-adminpanel.dashboard.areacoverage.tsx
new file mode 100644
index 0000000..27462cf
--- /dev/null
+++ b/app/routes/sys-rijig-adminpanel.dashboard.areacoverage.tsx
@@ -0,0 +1,709 @@
+import { ActionFunctionArgs, json, LoaderFunctionArgs } from "@remix-run/node";
+import { Form, useActionData, useLoaderData } from "@remix-run/react";
+import { useState } from "react";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle
+} from "~/components/ui/card";
+import { Button } from "~/components/ui/button";
+import { Input } from "~/components/ui/input";
+import { Label } from "~/components/ui/label";
+import { Badge } from "~/components/ui/badge";
+import { Separator } from "~/components/ui/separator";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue
+} from "~/components/ui/select";
+import { Textarea } from "~/components/ui/textarea";
+import { Alert, AlertDescription } from "~/components/ui/alert";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger
+} from "~/components/ui/dialog";
+import {
+ MapPin,
+ Plus,
+ Search,
+ Filter,
+ Edit,
+ Trash2,
+ Eye,
+ Layers,
+ Route,
+ Target,
+ Truck,
+ Recycle,
+ Building,
+ Users,
+ Navigation,
+ Download,
+ Info
+} from "lucide-react";
+import { LeafletMap } from "~/components/map/leaflet-map";
+
+// Mock data untuk lokasi
+const mockLocations = [
+ {
+ id: "1",
+ name: "TPS Kelurahan Menteng",
+ type: "tps",
+ address: "Jl. Menteng Raya No. 45, Jakarta Pusat",
+ coordinates: [-6.1944, 106.8229] as [number, number],
+ status: "active",
+ capacity: 500,
+ currentLoad: 320,
+ lastPickup: "2024-01-15 08:30",
+ coverage: "Menteng, Gondangdia",
+ population: 15000,
+ schedule: "Senin, Rabu, Jumat"
+ },
+ {
+ id: "2",
+ name: "TPS Kelurahan Cikini",
+ type: "tps",
+ address: "Jl. Cikini Raya No. 12, Jakarta Pusat",
+ coordinates: [-6.1889, 106.8317] as [number, number],
+ status: "active",
+ capacity: 300,
+ currentLoad: 180,
+ lastPickup: "2024-01-15 09:15",
+ coverage: "Cikini, Pegangsaan",
+ population: 12000,
+ schedule: "Selasa, Kamis, Sabtu"
+ },
+ {
+ id: "3",
+ name: "Rumah Kompos Kemayoran",
+ type: "composting",
+ address: "Jl. Kemayoran No. 88, Jakarta Pusat",
+ coordinates: [-6.1725, 106.8584] as [number, number],
+ status: "maintenance",
+ capacity: 1000,
+ currentLoad: 450,
+ lastPickup: "2024-01-14 16:00",
+ coverage: "Kemayoran, Senen",
+ population: 25000,
+ schedule: "Harian"
+ },
+ {
+ id: "4",
+ name: "Bank Sampah Tanah Abang",
+ type: "waste_bank",
+ address: "Jl. Tanah Abang II No. 67, Jakarta Pusat",
+ coordinates: [-6.1822, 106.8142] as [number, number],
+ status: "active",
+ capacity: 200,
+ currentLoad: 95,
+ lastPickup: "2024-01-15 14:20",
+ coverage: "Tanah Abang",
+ population: 8000,
+ schedule: "Senin, Kamis"
+ }
+];
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ // Mock statistics
+ const stats = {
+ totalLocations: mockLocations.length,
+ activeLocations: mockLocations.filter((l) => l.status === "active").length,
+ totalPopulation: mockLocations.reduce((sum, l) => sum + l.population, 0),
+ averageLoad: Math.round(
+ mockLocations.reduce(
+ (sum, l) => sum + (l.currentLoad / l.capacity) * 100,
+ 0
+ ) / mockLocations.length
+ ),
+ coverageArea: "45.2 km²"
+ };
+
+ return json({ locations: mockLocations, stats });
+}
+
+export async function action({ request }: ActionFunctionArgs) {
+ const formData = await request.formData();
+ const intent = formData.get("intent");
+
+ try {
+ switch (intent) {
+ case "add-location":
+ console.log("Adding new location...");
+ break;
+ case "edit-location":
+ console.log("Editing location...");
+ break;
+ case "delete-location":
+ console.log("Deleting location...");
+ break;
+ }
+
+ return json({ success: true, message: "Operasi berhasil!" });
+ } catch (error) {
+ return json(
+ { success: false, message: "Terjadi kesalahan." },
+ { status: 400 }
+ );
+ }
+}
+
+export default function AreaCoverage() {
+ const { locations, stats } = useLoaderData();
+ const actionData = useActionData();
+ const [selectedLocation, setSelectedLocation] = useState<
+ (typeof mockLocations)[0] | null
+ >(null);
+ const [filter, setFilter] = useState("all");
+ const [searchTerm, setSearchTerm] = useState("");
+ const [showAddDialog, setShowAddDialog] = useState(false);
+
+ const filteredLocations = locations.filter((location) => {
+ const matchesFilter =
+ filter === "all" ||
+ location.type === filter ||
+ location.status === filter;
+ const matchesSearch =
+ location.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ location.address.toLowerCase().includes(searchTerm.toLowerCase());
+ return matchesFilter && matchesSearch;
+ });
+
+ const getTypeIcon = (type: string) => {
+ switch (type) {
+ case "tps":
+ return ;
+ case "composting":
+ return ;
+ case "waste_bank":
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getTypeLabel = (type: string) => {
+ switch (type) {
+ case "tps":
+ return "TPS";
+ case "composting":
+ return "Kompos";
+ case "waste_bank":
+ return "Bank Sampah";
+ default:
+ return type;
+ }
+ };
+
+ return (
+
+
+
Area Coverage
+
+ Kelola dan pantau area cakupan layanan pengelolaan sampah
+
+
+
+ {actionData && (
+
+
+ {actionData.message}
+
+ )}
+
+ {/* Statistics Cards */}
+
+
+
+ Total Lokasi
+
+
+
+ {stats.totalLocations}
+
+
+
+
+
+ Lokasi Aktif
+
+
+
+
+ {stats.activeLocations}
+
+
+
+
+
+
+
+ Total Penduduk
+
+
+
+
+
+ {stats.totalPopulation.toLocaleString()}
+
+
+
+
+
+
+
+ Rata-rata Load
+
+
+
+
+ {stats.averageLoad}%
+
+
+
+
+
+ Area Cakupan
+
+
+
+ {stats.coverageArea}
+
+
+
+
+
+
+
+
+
+ Map View
+
+
+
+ List View
+
+
+
+
+
+
+
+ Tambah Lokasi
+
+
+
+
+ Tambah Lokasi Baru
+
+ Tambahkan lokasi pengelolaan sampah baru
+
+
+
+
+
+
+
+
+
+ {/* Map */}
+
+
+
+
+
+ Peta Area Coverage
+
+ Sebaran lokasi pengelolaan sampah di area Jakarta Pusat
+
+
+
+
+
+ Layers
+
+
+
+ Routes
+
+
+
+
+
+
+
+
+
+
+ {/* Location Details Sidebar */}
+
+
+
+ Detail Lokasi
+
+ {selectedLocation
+ ? "Informasi lokasi terpilih"
+ : "Pilih lokasi di peta untuk melihat detail"}
+
+
+
+ {selectedLocation ? (
+
+
+
+ {getTypeIcon(selectedLocation.type)}
+
+ {selectedLocation.name}
+
+
+
+ {selectedLocation.address}
+
+
+
+
+
+
+
+ Status
+
+ {selectedLocation.status}
+
+
+
+
+ Tipe
+
+ {getTypeLabel(selectedLocation.type)}
+
+
+
+
+ Kapasitas
+
+ {selectedLocation.capacity} kg
+
+
+
+
+
+ Load Saat Ini
+
+
+ {Math.round(
+ (selectedLocation.currentLoad /
+ selectedLocation.capacity) *
+ 100
+ )}
+ %
+
+
+
+
+
+
+
+ Populasi Dilayani
+
+
+ {selectedLocation.population.toLocaleString()}
+
+
+
+
+
+ Pickup Terakhir
+
+
+ {selectedLocation.lastPickup}
+
+
+
+
+ Jadwal
+
+ {selectedLocation.schedule}
+
+
+
+
+
+
+
+
+
+ Edit
+
+
+
+ Navigate
+
+
+
+ ) : (
+
+
+
+ Klik pada marker di peta untuk melihat detail lokasi
+
+
+ )}
+
+
+
+
+
+
+
+ {/* Filters */}
+
+
+ Filter & Search
+
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ className="pl-8"
+ />
+
+
+
+
+
+
+
+ Semua Lokasi
+ TPS
+ Rumah Kompos
+ Bank Sampah
+ Status: Aktif
+
+ Status: Maintenance
+
+
+
+
+
+ Export
+
+
+
+
+
+ {/* Location Cards */}
+
+ {filteredLocations.map((location) => (
+
+
+
+
+ {getTypeIcon(location.type)}
+ {location.name}
+
+
+ {location.status}
+
+
+
+ {location.address}
+
+
+
+
+
+ Tipe:
+ {getTypeLabel(location.type)}
+
+
+
+
+ Load:
+
+ {Math.round(
+ (location.currentLoad / location.capacity) * 100
+ )}
+ %
+
+
+
+
+
+
+ Populasi:
+ {location.population.toLocaleString()}
+
+
+
+ Pickup Terakhir:
+ {location.lastPickup}
+
+
+
+
+
+ Detail
+
+
+
+ Edit
+
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/app/routes/sys-rijig-adminpanel.dashboard.artikel-blog._index.tsx b/app/routes/sys-rijig-adminpanel.dashboard.artikel-blog._index.tsx
new file mode 100644
index 0000000..f35679e
--- /dev/null
+++ b/app/routes/sys-rijig-adminpanel.dashboard.artikel-blog._index.tsx
@@ -0,0 +1,208 @@
+import { LoaderFunctionArgs, json } from "@remix-run/node";
+import { useLoaderData, Link } from "@remix-run/react";
+import { Button } from "~/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle
+} from "~/components/ui/card";
+import { Badge } from "~/components/ui/badge";
+import { Input } from "~/components/ui/input";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow
+} from "~/components/ui/table";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger
+} from "~/components/ui/dropdown-menu";
+import {
+ Plus,
+ Search,
+ MoreHorizontal,
+ Edit,
+ Trash2,
+ Eye,
+ FileText
+} from "lucide-react";
+
+// Mock data - ganti dengan data dari database
+const mockArtikel = [
+ {
+ id: "1",
+ title: "Cara Memilah Sampah yang Benar",
+ category: "Edukasi",
+ status: "published",
+ author: "Admin",
+ createdAt: "2024-01-15",
+ views: 245
+ },
+ {
+ id: "2",
+ title: "Manfaat Daur Ulang untuk Lingkungan",
+ category: "Tips",
+ status: "draft",
+ author: "Admin",
+ createdAt: "2024-01-14",
+ views: 0
+ }
+];
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ // Di sini Anda akan fetch data dari database
+ return json({ artikel: mockArtikel });
+}
+
+export default function ArtikelBlogIndex() {
+ const { artikel } = useLoaderData();
+
+ return (
+
+ {/* Header Actions */}
+
+
+ }
+ />
+
+
+
+
+ Tambah Artikel
+
+
+
+
+ {/* Stats Cards */}
+
+
+
+ Total Artikel
+
+
+
+ {artikel.length}
+
+
+
+
+
+ Published
+
+
+
+
+ {artikel.filter((a) => a.status === "published").length}
+
+
+
+
+
+
+ Draft
+
+
+
+
+ {artikel.filter((a) => a.status === "draft").length}
+
+
+
+
+
+
+ Total Views
+
+
+
+
+ {artikel.reduce((sum, a) => sum + a.views, 0)}
+
+
+
+
+
+ {/* Artikel Table */}
+
+
+ Daftar Artikel & Blog
+
+ Kelola semua artikel dan blog post Anda
+
+
+
+
+
+
+ Judul
+ Kategori
+ Status
+ Author
+ Tanggal
+ Views
+ Actions
+
+
+
+ {artikel.map((item) => (
+
+ {item.title}
+
+ {item.category}
+
+
+
+ {item.status}
+
+
+ {item.author}
+ {item.createdAt}
+ {item.views}
+
+
+
+
+
+
+
+
+
+
+
+ Edit
+
+
+
+
+ Preview
+
+
+
+ Delete
+
+
+
+
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/app/routes/sys-rijig-adminpanel.dashboard.content-management.tsx b/app/routes/sys-rijig-adminpanel.dashboard.content-management.tsx
new file mode 100644
index 0000000..7e90b15
--- /dev/null
+++ b/app/routes/sys-rijig-adminpanel.dashboard.content-management.tsx
@@ -0,0 +1,37 @@
+import { Outlet } from "@remix-run/react"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"
+import { FileText, BookOpen } from "lucide-react"
+
+export default function ContentManagementLayout() {
+ return (
+
+
+
Content Management
+
+ Kelola artikel, blog, tips, dan panduan untuk aplikasi pengelolaan sampah
+
+
+
+
+
+
+
+ Artikel & Blog
+
+
+
+ Tips & Panduan
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/app/routes/sys-rijig-adminpanel.dashboard.pengaturan.tsx b/app/routes/sys-rijig-adminpanel.dashboard.pengaturan.tsx
new file mode 100644
index 0000000..eecb9b7
--- /dev/null
+++ b/app/routes/sys-rijig-adminpanel.dashboard.pengaturan.tsx
@@ -0,0 +1,770 @@
+import { ActionFunctionArgs, json, LoaderFunctionArgs } from "@remix-run/node"
+import { Form, useActionData, useLoaderData } from "@remix-run/react"
+import { useState } from "react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"
+import { Button } from "~/components/ui/button"
+import { Input } from "~/components/ui/input"
+import { Label } from "~/components/ui/label"
+import { Textarea } from "~/components/ui/textarea"
+import { Switch } from "~/components/ui/switch"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"
+import { Badge } from "~/components/ui/badge"
+import { Separator } from "~/components/ui/separator"
+import { Alert, AlertDescription } from "~/components/ui/alert"
+import {
+ Settings,
+ Users,
+ Bell,
+ Shield,
+ Database,
+ Mail,
+ Globe,
+ Palette,
+ Save,
+ Upload,
+ Download,
+ RefreshCw,
+ CheckCircle,
+ AlertTriangle,
+ Info
+} from "lucide-react"
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ // Mock data - ganti dengan data dari database
+ const settings = {
+ // Pengaturan Umum
+ appName: "EcoWaste Manager",
+ appDescription: "Sistem Pengelolaan Sampah Terpadu untuk Lingkungan yang Lebih Bersih",
+ companyName: "PT. Lingkungan Hijau Indonesia",
+ companyAddress: "Jl. Sudirman No. 123, Jakarta Pusat 10220",
+ companyPhone: "+62 21 1234567",
+ companyEmail: "info@ecowaste.com",
+ timezone: "Asia/Jakarta",
+ language: "id",
+
+ // Pengaturan Sistem
+ maintenanceMode: false,
+ registrationEnabled: true,
+ maxFileSize: "10",
+ sessionTimeout: "60",
+ backupFrequency: "daily",
+
+ // Pengaturan Email
+ smtpHost: "smtp.gmail.com",
+ smtpPort: "587",
+ smtpUser: "noreply@ecowaste.com",
+ smtpPassword: "",
+ emailFrom: "EcoWaste System ",
+
+ // Pengaturan Notifikasi
+ emailNotifications: true,
+ pushNotifications: false,
+ smsNotifications: false,
+ notifyNewUser: true,
+ notifyLowStock: true,
+ notifySystemAlert: true,
+
+ // Statistik
+ totalUsers: 245,
+ totalWaste: "1,250 kg",
+ lastBackup: "2 jam yang lalu",
+ systemUptime: "99.9%"
+ }
+
+ return json({ settings })
+}
+
+export async function action({ request }: ActionFunctionArgs) {
+ const formData = await request.formData()
+ const intent = formData.get("intent")
+
+ try {
+ switch (intent) {
+ case "general":
+ // Save general settings
+ console.log("Saving general settings...")
+ break
+ case "system":
+ // Save system settings
+ console.log("Saving system settings...")
+ break
+ case "email":
+ // Save email settings
+ console.log("Saving email settings...")
+ break
+ case "notifications":
+ // Save notification settings
+ console.log("Saving notification settings...")
+ break
+ case "backup":
+ // Trigger backup
+ console.log("Creating backup...")
+ break
+ case "test-email":
+ // Test email configuration
+ console.log("Testing email...")
+ break
+ }
+
+ return json({ success: true, message: "Pengaturan berhasil disimpan!" })
+ } catch (error) {
+ return json({ success: false, message: "Gagal menyimpan pengaturan." }, { status: 400 })
+ }
+}
+
+export default function Pengaturan() {
+ const { settings } = useLoaderData()
+ const actionData = useActionData()
+ const [activeTab, setActiveTab] = useState("overview")
+
+ return (
+
+
+
Pengaturan
+
+ Kelola pengaturan dan konfigurasi sistem pengelolaan sampah
+
+
+
+ {actionData && (
+
+
+ {actionData.message}
+
+ )}
+
+
+
+
+
+ Overview
+
+
+
+ Umum
+
+
+
+ Sistem
+
+
+
+ Notifikasi
+
+
+
+ Backup
+
+
+
+ {/* Overview Tab */}
+
+
+
+
+ Total Pengguna
+
+
+
+ {settings.totalUsers}
+ +5 dari bulan lalu
+
+
+
+
+
+ Total Sampah
+
+
+
+ {settings.totalWaste}
+ Bulan ini
+
+
+
+
+
+ System Uptime
+
+
+
+ {settings.systemUptime}
+ 30 hari terakhir
+
+
+
+
+
+ Last Backup
+
+
+
+ ✓
+ {settings.lastBackup}
+
+
+
+
+
+
+
+ Status Sistem
+ Kondisi sistem saat ini
+
+
+
+ Database
+ Online
+
+
+ Email Service
+ Connected
+
+
+ Storage
+ 75% Used
+
+
+ Cache
+ Active
+
+
+
+
+
+
+ Aktivitas Terbaru
+ Log aktivitas sistem
+
+
+
+
+ Backup otomatis selesai
+ 2 jam lalu
+
+
+
+
+ User baru terdaftar
+ 4 jam lalu
+
+
+
+
+ Cache dibersihkan
+ 1 hari lalu
+
+
+
+
+ Database dioptimasi
+ 2 hari lalu
+
+
+
+
+
+
+
+ {/* General Settings Tab */}
+
+
+
+
+
+
+
+ Informasi Aplikasi
+
+ Pengaturan dasar aplikasi dan identitas perusahaan
+
+
+
+
+
+
+ Deskripsi Aplikasi
+
+
+
+
+ Alamat Perusahaan
+
+
+
+
+
+
+
+
+
+ Pengaturan Regional
+
+ Zona waktu dan bahasa aplikasi
+
+
+
+
+
+ Zona Waktu
+
+
+
+
+
+ Asia/Jakarta (WIB)
+ Asia/Makassar (WITA)
+ Asia/Jayapura (WIT)
+
+
+
+
+
+ Bahasa
+
+
+
+
+
+ Bahasa Indonesia
+ English
+
+
+
+
+
+
+
+
+
+
+ Simpan Pengaturan
+
+
+
+
+
+
+ {/* System Settings Tab */}
+
+
+
+
+
+
+
+ Pengaturan Sistem
+
+ Konfigurasi sistem dan keamanan
+
+
+
+
+
+
Mode Maintenance
+
+ Nonaktifkan akses pengguna untuk maintenance
+
+
+
+
+
+
+
+
+
+
Registrasi Pengguna Baru
+
+ Izinkan pendaftaran pengguna baru
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tindakan Sistem
+
+ Aksi pemeliharaan dan optimasi sistem
+
+
+
+
+
+
+ Clear Cache
+
+
+
+ Optimize Database
+
+
+
+ Export Logs
+
+
+
+
+
+
+
+
+ Simpan Pengaturan
+
+
+
+
+
+
+ {/* Notifications Tab */}
+
+
+
+
+
+
+
+ Email Configuration
+
+ Pengaturan server email untuk notifikasi
+
+
+
+
+
+
+
+
+ Email Pengirim
+
+
+
+ {
+ // Test email function
+ const form = new FormData()
+ form.append("intent", "test-email")
+ fetch("", { method: "POST", body: form })
+ }}
+ >
+
+ Test Email
+
+
+
+
+
+
+ Pengaturan Notifikasi
+
+ Atur jenis notifikasi yang akan dikirim
+
+
+
+
+
+
+
Email Notifications
+
+ Kirim notifikasi melalui email
+
+
+
+
+
+
+
+
Notifikasi Pengguna Baru
+
+ Notifikasi saat ada pengguna baru mendaftar
+
+
+
+
+
+
+
+
Alert Sistem
+
+ Notifikasi untuk masalah sistem dan error
+
+
+
+
+
+
+
+
+
+
+
+ Simpan Pengaturan
+
+
+
+
+
+
+ {/* Backup Tab */}
+
+
+
+
+ Backup & Restore
+
+ Kelola backup data dan pemulihan sistem
+
+
+
+
+
+
+
+
+
Backup Otomatis
+
+
+ Frekuensi Backup
+
+
+
+
+
+ Harian
+ Mingguan
+ Bulanan
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Backup Sekarang
+
+
+
+
+
+ Download Backup
+
+
+
+
+ Restore Backup
+
+
+
+
+
+
+
+ Riwayat Backup
+
+ Daftar backup yang tersedia
+
+
+
+
+ {[
+ { date: "2024-01-15 14:30", size: "1.4 GB", status: "success" },
+ { date: "2024-01-14 14:30", size: "1.3 GB", status: "success" },
+ { date: "2024-01-13 14:30", size: "1.3 GB", status: "success" },
+ { date: "2024-01-12 14:30", size: "1.2 GB", status: "failed" },
+ ].map((backup, index) => (
+
+
+
+
+
{backup.date}
+
{backup.size}
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/app/routes/sys-rijig-adminpanel.dashboard.tips-panduan._index.tsx b/app/routes/sys-rijig-adminpanel.dashboard.tips-panduan._index.tsx
new file mode 100644
index 0000000..315ba49
--- /dev/null
+++ b/app/routes/sys-rijig-adminpanel.dashboard.tips-panduan._index.tsx
@@ -0,0 +1,158 @@
+import { LoaderFunctionArgs, json } from "@remix-run/node";
+import { useLoaderData, Link } from "@remix-run/react";
+import { Button } from "~/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle
+} from "~/components/ui/card";
+import { Badge } from "~/components/ui/badge";
+import { BookOpen, Plus, Users, ThumbsUp } from "lucide-react";
+
+// Mock data untuk tips & panduan
+const mockTips = [
+ {
+ id: "1",
+ title: "Panduan Lengkap Kompos Rumahan",
+ category: "Panduan",
+ difficulty: "Pemula",
+ likes: 89,
+ saves: 45,
+ createdAt: "2024-01-15"
+ },
+ {
+ id: "2",
+ title: "5 Tips Mengurangi Sampah Plastik",
+ category: "Tips",
+ difficulty: "Mudah",
+ likes: 156,
+ saves: 78,
+ createdAt: "2024-01-14"
+ }
+];
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ return json({ tips: mockTips });
+}
+
+export default function TipsPanduanIndex() {
+ const { tips } = useLoaderData();
+
+ return (
+
+ {/* Header */}
+
+
+
Tips & Panduan
+
+ Kelola tips dan panduan untuk edukasi pengelolaan sampah
+
+
+
+
+
+ Tambah Tips
+
+
+
+
+ {/* Stats */}
+
+
+
+ Total Tips
+
+
+
+ {tips.length}
+
+
+
+
+
+ Total Likes
+
+
+
+
+ {tips.reduce((sum, t) => sum + t.likes, 0)}
+
+
+
+
+
+
+ Total Saves
+
+
+
+
+ {tips.reduce((sum, t) => sum + t.saves, 0)}
+
+
+
+
+
+
+
+ Avg. Engagement
+
+
+
+
+
+ {Math.round(
+ tips.reduce((sum, t) => sum + t.likes + t.saves, 0) /
+ tips.length
+ )}
+
+
+
+
+
+ {/* Tips Grid */}
+
+ {tips.map((tip) => (
+
+
+
+ {tip.category}
+
+ {tip.difficulty}
+
+
+ {tip.title}
+
+
+
+
{tip.createdAt}
+
+
+
+ {tip.likes}
+
+
+
+ {tip.saves}
+
+
+
+
+
+ Edit
+
+
+ Preview
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/app/routes/sys-rijig-adminpanel.dashboard.users._index.tsx b/app/routes/sys-rijig-adminpanel.dashboard.users._index.tsx
new file mode 100644
index 0000000..60bf376
--- /dev/null
+++ b/app/routes/sys-rijig-adminpanel.dashboard.users._index.tsx
@@ -0,0 +1,212 @@
+import { json } from "@remix-run/node";
+import { useLoaderData } from "@remix-run/react";
+import { Users, UserCheck, UserX, MoreHorizontal, Eye, Edit, Trash2 } from "lucide-react";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
+import { Button } from "~/components/ui/button";
+import { Badge } from "~/components/ui/badge";
+import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "~/components/ui/table";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "~/components/ui/dropdown-menu";
+
+export const loader = async () => {
+ const userData = {
+ summary: {
+ totalUsers: 1234,
+ activeUsers: 1100,
+ pendingVerification: 15,
+ collectors: 45
+ },
+ users: [
+ {
+ id: 1,
+ name: "Ahmad Rizki",
+ email: "ahmad.rizki@example.com",
+ role: "Masyarakat",
+ status: "active",
+ joinDate: "2024-01-15",
+ totalTransactions: 25,
+ avatar: ""
+ },
+ {
+ id: 2,
+ name: "Siti Nurhaliza",
+ email: "siti.nurhaliza@example.com",
+ role: "Pengepul",
+ status: "pending",
+ joinDate: "2024-06-20",
+ totalTransactions: 0,
+ avatar: ""
+ },
+ {
+ id: 3,
+ name: "Budi Santoso",
+ email: "budi.santoso@example.com",
+ role: "Masyarakat",
+ status: "active",
+ joinDate: "2024-03-10",
+ totalTransactions: 42,
+ avatar: ""
+ }
+ ]
+ };
+
+ return json({ userData });
+};
+
+export default function UserManagement() {
+ const { userData } = useLoaderData();
+
+ return (
+
+ {/* Header */}
+
+
+
+ Manajemen Pengguna
+
+
+ Kelola semua pengguna platform RIjig
+
+
+
+
+ {/* Summary Cards */}
+
+
+
+ Total Pengguna
+
+
+
+ {userData.summary.totalUsers.toLocaleString()}
+ terdaftar di platform
+
+
+
+
+
+ Pengguna Aktif
+
+
+
+ {userData.summary.activeUsers.toLocaleString()}
+ pengguna terverifikasi
+
+
+
+
+
+ Menunggu Verifikasi
+
+
+
+ {userData.summary.pendingVerification}
+ perlu ditinjau
+
+
+
+
+
+ Total Pengepul
+
+
+
+ {userData.summary.collectors}
+ pengepul aktif
+
+
+
+
+ {/* Users Table */}
+
+
+ Daftar Pengguna
+ Semua pengguna yang terdaftar di platform
+
+
+
+
+
+ Pengguna
+ Email
+ Role
+ Status
+ Tanggal Bergabung
+ Total Transaksi
+ Aksi
+
+
+
+ {userData.users.map((user) => (
+
+
+
+
+
+
+ {user.name.split(' ').map(n => n[0]).join('')}
+
+
+
+
+
+ {user.email}
+
+ {user.role}
+
+
+
+ {user.status === "active" ? "Aktif" : "Pending"}
+
+
+ {new Date(user.joinDate).toLocaleDateString('id-ID')}
+ {user.totalTransactions}
+
+
+
+
+
+
+
+
+
+
+ Lihat Detail
+
+
+
+ Edit
+
+
+
+ Hapus
+
+
+
+
+
+ ))}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/routes/sys-rijig-adminpanel.dashboard.waste._index.tsx b/app/routes/sys-rijig-adminpanel.dashboard.waste._index.tsx
new file mode 100644
index 0000000..ddc7bd1
--- /dev/null
+++ b/app/routes/sys-rijig-adminpanel.dashboard.waste._index.tsx
@@ -0,0 +1,297 @@
+import { json } from "@remix-run/node";
+import { useLoaderData } from "@remix-run/react";
+import {
+ Recycle,
+ Plus,
+ Search,
+ Filter,
+ MoreHorizontal,
+ Edit,
+ Trash2,
+ TrendingUp,
+ TrendingDown
+} from "lucide-react";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle
+} from "~/components/ui/card";
+import { Button } from "~/components/ui/button";
+import { Input } from "~/components/ui/input";
+import { Badge } from "~/components/ui/badge";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow
+} from "~/components/ui/table";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger
+} from "~/components/ui/dropdown-menu";
+
+export const loader = async () => {
+ const wasteData = {
+ summary: {
+ totalTypes: 24,
+ totalVolume: 5678,
+ avgPrice: 2500,
+ trending: "up"
+ },
+ wasteTypes: [
+ {
+ id: 1,
+ name: "Plastik PET",
+ category: "Plastik",
+ currentPrice: 3000,
+ priceChange: "+5%",
+ volume: 1500,
+ trend: "up",
+ status: "active"
+ },
+ {
+ id: 2,
+ name: "Kertas HVS",
+ category: "Kertas",
+ currentPrice: 2000,
+ priceChange: "-2%",
+ volume: 2100,
+ trend: "down",
+ status: "active"
+ },
+ {
+ id: 3,
+ name: "Aluminium",
+ category: "Logam",
+ currentPrice: 8500,
+ priceChange: "+12%",
+ volume: 450,
+ trend: "up",
+ status: "active"
+ },
+ {
+ id: 4,
+ name: "Plastik HDPE",
+ category: "Plastik",
+ currentPrice: 2800,
+ priceChange: "+3%",
+ volume: 900,
+ trend: "up",
+ status: "active"
+ },
+ {
+ id: 5,
+ name: "Kertas Karton",
+ category: "Kertas",
+ currentPrice: 1500,
+ priceChange: "0%",
+ volume: 1200,
+ trend: "stable",
+ status: "active"
+ }
+ ]
+ };
+
+ return json({ wasteData });
+};
+
+export default function WasteManagement() {
+ const { wasteData } = useLoaderData();
+
+ return (
+
+ {/* Header */}
+
+
+
+ Manajemen Data Sampah
+
+
+ Kelola jenis sampah, harga, dan volume transaksi
+
+
+
+
+ Tambah Jenis Sampah
+
+
+
+ {/* Summary Cards */}
+
+
+
+ Total Jenis
+
+
+
+
+ {wasteData.summary.totalTypes}
+
+
+ jenis sampah terdaftar
+
+
+
+
+
+
+ Volume Total
+
+
+
+
+ {wasteData.summary.totalVolume.toLocaleString()}
+
+ kg bulan ini
+
+
+
+
+
+
+ Rata-rata Harga
+
+
+
+
+
+ Rp {wasteData.summary.avgPrice.toLocaleString()}
+
+ per kilogram
+
+
+
+
+
+ Trend Harga
+ {wasteData.summary.trending === "up" ? (
+
+ ) : (
+
+ )}
+
+
+ Naik
+
+ dibanding bulan lalu
+
+
+
+
+
+ {/* Filter and Search */}
+
+
+
+
+ Daftar Jenis Sampah
+
+ Kelola jenis sampah dan harga terkini
+
+
+
+
+
+
+
+
+
+ Nama Sampah
+ Kategori
+ Harga Saat Ini
+ Perubahan
+ Volume (kg)
+ Status
+ Aksi
+
+
+
+ {wasteData.wasteTypes.map((waste) => (
+
+ {waste.name}
+
+ {waste.category}
+
+
+ Rp {waste.currentPrice.toLocaleString()}
+
+
+
+ {waste.trend === "up" && (
+
+ )}
+ {waste.trend === "down" && (
+
+ )}
+ {waste.priceChange}
+
+
+ {waste.volume.toLocaleString()}
+
+
+ {waste.status === "active" ? "Aktif" : "Nonaktif"}
+
+
+
+
+
+
+
+
+
+
+
+
+ Edit
+
+
+
+ Hapus
+
+
+
+
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/app/routes/sys-rijig-adminpanel.tsx b/app/routes/sys-rijig-adminpanel.tsx
new file mode 100644
index 0000000..8699d42
--- /dev/null
+++ b/app/routes/sys-rijig-adminpanel.tsx
@@ -0,0 +1,25 @@
+import { json } from "@remix-run/node";
+import { Outlet, useLoaderData } from "@remix-run/react";
+import { AdminLayoutWrapper } from "~/components/layoutadmin/layout-wrapper";
+
+export const loader = async () => {
+ // Data untuk layout bisa diambil di sini
+ return json({
+ user: {
+ name: "Musharof",
+ email: "admin@example.com",
+ role: "Administrator"
+ }
+ });
+};
+
+export default function AdminPanelLayout() {
+ const { user } = useLoaderData();
+
+ return (
+
+ {/* Outlet akan merender child routes */}
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/sessions.server.tsx b/app/sessions.server.tsx
new file mode 100644
index 0000000..b16e3ac
--- /dev/null
+++ b/app/sessions.server.tsx
@@ -0,0 +1,16 @@
+import {createThemeSessionResolver} from 'remix-themes'
+import { createCookieSessionStorage } from "@remix-run/node"
+
+const sessionStorage = createCookieSessionStorage({
+ cookie: {
+ name: '__remix-themes',
+ // domain: 'remix.run',
+ path: '/',
+ httpOnly: true,
+ sameSite: 'lax',
+ secrets: ['s3cr3t'],
+ // secure: true,
+ },
+})
+
+export const themeSessionResolver = createThemeSessionResolver(sessionStorage)
\ No newline at end of file
diff --git a/app/tailwind.css b/app/tailwind.css
index 303fe15..4665250 100644
--- a/app/tailwind.css
+++ b/app/tailwind.css
@@ -10,3 +10,70 @@ body {
color-scheme: dark;
}
}
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 0 0% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+ }
+ .dark {
+ --background: 0 0% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 0 0% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+ --accent: 0 0% 14.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 14.9%;
+ --input: 0 0% 14.9%;
+ --ring: 0 0% 83.1%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ }
+}
+
+
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..48bf6f4
--- /dev/null
+++ b/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "app/tailwind.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "~/components",
+ "utils": "~/lib/utils",
+ "ui": "~/components/ui",
+ "lib": "~/lib",
+ "hooks": "~/hooks"
+ },
+ "iconLibrary": "lucide"
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index aa142ed..1f27c31 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,15 +5,40 @@
"packages": {
"": {
"dependencies": {
+ "@bprogress/remix": "^1.0.19",
+ "@radix-ui/react-alert-dialog": "^1.1.14",
+ "@radix-ui/react-avatar": "^1.1.10",
+ "@radix-ui/react-collapsible": "^1.1.11",
+ "@radix-ui/react-dialog": "^1.1.14",
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
+ "@radix-ui/react-label": "^2.1.7",
+ "@radix-ui/react-progress": "^1.1.7",
+ "@radix-ui/react-scroll-area": "^1.2.9",
+ "@radix-ui/react-select": "^2.2.5",
+ "@radix-ui/react-separator": "^1.1.7",
+ "@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-switch": "^1.2.5",
+ "@radix-ui/react-tabs": "^1.1.12",
+ "@radix-ui/react-tooltip": "^1.2.7",
"@remix-run/node": "^2.16.8",
"@remix-run/react": "^2.16.8",
"@remix-run/serve": "^2.16.8",
+ "@tabler/icons-react": "^3.34.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
"isbot": "^4.1.0",
+ "lucide-react": "^0.525.0",
+ "motion": "^12.23.0",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-leaflet": "^4.0.0",
+ "remix-themes": "^1.6.1",
+ "tailwind-merge": "^3.3.1",
+ "tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@remix-run/dev": "^2.16.8",
+ "@types/leaflet": "^1.9.19",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.7.4",
@@ -39,7 +64,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -551,6 +575,40 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@bprogress/core": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/@bprogress/core/-/core-1.3.4.tgz",
+ "integrity": "sha512-q/AqpurI/1uJzOrQROuZWixn/+ARekh+uvJGwLCP6HQ/EqAX4SkvNf618tSBxL4NysC0MwqAppb/mRw6Tzi61w==",
+ "license": "MIT"
+ },
+ "node_modules/@bprogress/react": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@bprogress/react/-/react-1.2.7.tgz",
+ "integrity": "sha512-MqJfHW+R5CQeWqyqrLxUjdBRHk24Xl63OkBLo5DMWqUqocUikRTfCIc/jtQQbPk7BRfdr5OP3Lx7YlfQ9QOZMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@bprogress/core": "^1.3.4"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@bprogress/remix": {
+ "version": "1.0.19",
+ "resolved": "https://registry.npmjs.org/@bprogress/remix/-/remix-1.0.19.tgz",
+ "integrity": "sha512-ymUIhYzvVzX1EIs1puhpZJCIbK2ahDeykST5sUd1J0lMmQHX6qVSpLmKMSi0kZobB9zyUZnpz/by7paxumxJYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@bprogress/core": "^1.3.4",
+ "@bprogress/react": "^1.2.7"
+ },
+ "peerDependencies": {
+ "@remix-run/react": ">=2.0.0",
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
"node_modules/@emnapi/core": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
@@ -1104,6 +1162,44 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz",
+ "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz",
+ "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.2",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.4.tgz",
+ "integrity": "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -1170,7 +1266,6 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
@@ -1188,7 +1283,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -1201,7 +1295,6 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
@@ -1217,7 +1310,6 @@
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -1228,7 +1320,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1238,14 +1329,12 @@
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
- "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.29",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -1306,7 +1395,6 @@
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
@@ -1320,7 +1408,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@@ -1330,7 +1417,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
@@ -1429,13 +1515,976 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
+ "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
+ "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-alert-dialog": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.14.tgz",
+ "integrity": "sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dialog": "1.1.14",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@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-arrow": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.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-avatar": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz",
+ "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-is-hydrated": "0.1.0",
+ "@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-collapsible": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.11.tgz",
+ "integrity": "sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.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-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@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-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-dialog": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz",
+ "integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.10",
+ "@radix-ui/react-focus-guards": "1.1.2",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.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-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "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-dismissable-layer": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz",
+ "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "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-dropdown-menu": {
+ "version": "2.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.15.tgz",
+ "integrity": "sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.15",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "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-focus-guards": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz",
+ "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==",
+ "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-focus-scope": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "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-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+ "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-label": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz",
+ "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.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-menu": {
+ "version": "2.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.15.tgz",
+ "integrity": "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.10",
+ "@radix-ui/react-focus-guards": "1.1.2",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.7",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.10",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.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-popper": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz",
+ "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "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-portal": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3",
+ "@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-presence": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz",
+ "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==",
+ "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-progress": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
+ "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.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-roving-focus": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz",
+ "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "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-scroll-area": {
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.9.tgz",
+ "integrity": "sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@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-select": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.5.tgz",
+ "integrity": "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.10",
+ "@radix-ui/react-focus-guards": "1.1.2",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.7",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.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-separator": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
+ "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.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-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-switch": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.5.tgz",
+ "integrity": "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@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-tabs": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz",
+ "integrity": "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "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-tooltip": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz",
+ "integrity": "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.10",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.7",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-visually-hidden": "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-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "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-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-escape-keydown": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "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-is-hydrated": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz",
+ "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.5.0"
+ },
+ "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-rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "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-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/@radix-ui/react-visually-hidden": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
+ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.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/rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
+ "license": "MIT"
+ },
+ "node_modules/@react-leaflet/core": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
+ "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==",
+ "license": "Hippocratic-2.1",
+ "peerDependencies": {
+ "leaflet": "^1.9.0",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ }
+ },
"node_modules/@remix-run/dev": {
"version": "2.16.8",
"resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.16.8.tgz",
@@ -2001,6 +3050,32 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@tabler/icons": {
+ "version": "3.34.0",
+ "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.34.0.tgz",
+ "integrity": "sha512-jtVqv0JC1WU2TTEBN32D9+R6mc1iEBuPwLnBsWaR02SIEciu9aq5806AWkCHuObhQ4ERhhXErLEK7Fs+tEZxiA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/codecalm"
+ }
+ },
+ "node_modules/@tabler/icons-react": {
+ "version": "3.34.0",
+ "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.34.0.tgz",
+ "integrity": "sha512-OpEIR2iZsIXECtAIMbn1zfKfQ3zKJjXyIZlkgOGUL9UkMCFycEiF2Y8AVfEQsyre/3FnBdlWJvGr0NU47n2TbQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tabler/icons": "3.34.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/codecalm"
+ },
+ "peerDependencies": {
+ "react": ">= 16"
+ }
+ },
"node_modules/@tybys/wasm-util": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
@@ -2055,6 +3130,13 @@
"@types/estree": "*"
}
},
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/hast": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
@@ -2079,6 +3161,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/leaflet": {
+ "version": "1.9.19",
+ "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.19.tgz",
+ "integrity": "sha512-pB+n2daHcZPF2FDaWa+6B0a0mSDf4dPU35y5iTXsx7x/PzzshiX5atYiS1jlBn43X7XvM8AP+AB26lnSk0J4GA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
"node_modules/@types/mdast": {
"version": "3.0.15",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz",
@@ -2117,14 +3209,14 @@
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.23",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -2135,7 +3227,7 @@
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
@@ -3330,7 +4422,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -3340,7 +4431,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
@@ -3356,7 +4446,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
- "dev": true,
"license": "MIT"
},
"node_modules/anymatch": {
@@ -3376,7 +4465,6 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
- "dev": true,
"license": "MIT"
},
"node_modules/argparse": {
@@ -3386,6 +4474,18 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/aria-query": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
@@ -3687,7 +4787,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
"license": "MIT"
},
"node_modules/base64-js": {
@@ -3796,7 +4895,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -4002,7 +5100,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -4135,6 +5232,18 @@
"node": ">=10"
}
},
+ "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/clean-stack": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@@ -4181,11 +5290,19 @@
"node": ">=0.8"
}
},
+ "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",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
@@ -4198,7 +5315,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
"license": "MIT"
},
"node_modules/comma-separated-tokens": {
@@ -4216,7 +5332,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -4338,7 +5453,6 @@
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -4353,7 +5467,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -4382,7 +5495,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
- "dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
@@ -4395,7 +5507,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
@@ -4616,11 +5728,16 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
- "dev": true,
"license": "Apache-2.0"
},
"node_modules/diff": {
@@ -4650,7 +5767,6 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
- "dev": true,
"license": "MIT"
},
"node_modules/doctrine": {
@@ -4750,7 +5866,6 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
"license": "MIT"
},
"node_modules/ee-first": {
@@ -4770,7 +5885,6 @@
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
"license": "MIT"
},
"node_modules/encodeurl": {
@@ -5884,7 +6998,6 @@
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -5915,7 +7028,6 @@
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
- "dev": true,
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
@@ -6051,7 +7163,6 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
- "dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
@@ -6068,7 +7179,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
@@ -6109,6 +7219,33 @@
"url": "https://github.com/sponsors/rawify"
}
},
+ "node_modules/framer-motion": {
+ "version": "12.23.0",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.0.tgz",
+ "integrity": "sha512-xf6NxTGAyf7zR4r2KlnhFmsRfKIbjqeBupEDBAaEtVIBJX96sAon00kMlsKButSIRwPSHjbRrAPnYdJJ9kyhbA==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.22.0",
+ "motion-utils": "^12.19.0",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -6258,6 +7395,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/get-port": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz",
@@ -6331,7 +7477,6 @@
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
- "dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -6960,7 +8105,6 @@
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
- "dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
@@ -7054,7 +8198,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -7395,7 +8538,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
"license": "ISC"
},
"node_modules/iterator.prototype": {
@@ -7420,7 +8562,6 @@
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
@@ -7443,7 +8584,6 @@
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
- "dev": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
@@ -7594,6 +8734,13 @@
"node": ">=0.10"
}
},
+ "node_modules/leaflet": {
+ "version": "1.9.4",
+ "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
+ "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
+ "license": "BSD-2-Clause",
+ "peer": true
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -7612,7 +8759,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@@ -7625,7 +8771,6 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "dev": true,
"license": "MIT"
},
"node_modules/loader-utils": {
@@ -7750,6 +8895,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.525.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz",
+ "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/markdown-extensions": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz",
@@ -8015,7 +9169,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@@ -8675,7 +9828,6 @@
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
@@ -8741,7 +9893,6 @@
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -8767,7 +9918,6 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -9015,6 +10165,47 @@
"node": ">= 0.8"
}
},
+ "node_modules/motion": {
+ "version": "12.23.0",
+ "resolved": "https://registry.npmjs.org/motion/-/motion-12.23.0.tgz",
+ "integrity": "sha512-PPNwblArRH9GRC4F3KtOTiIaYd+mtp324vYq3HIL+ueseoAVqPRK5TPFTAQBcIprfVd0NWo3DLzZSiyWaYFXXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "framer-motion": "^12.23.0",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/motion-dom": {
+ "version": "12.22.0",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.22.0.tgz",
+ "integrity": "sha512-ooH7+/BPw9gOsL9VtPhEJHE2m4ltnhMlcGMhEqA0YGNhKof7jdaszvsyThXI6LVIKshJUZ9/CP6HNqQhJfV7kw==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.19.0"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.19.0",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.19.0.tgz",
+ "integrity": "sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw==",
+ "license": "MIT"
+ },
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -9044,7 +10235,6 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0",
@@ -9056,7 +10246,6 @@
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -9217,7 +10406,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -9227,7 +10415,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -9511,7 +10698,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/pako": {
@@ -9597,7 +10783,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -9607,14 +10792,12 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
"license": "MIT"
},
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
@@ -9631,7 +10814,6 @@
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
"license": "ISC"
},
"node_modules/path-to-regexp": {
@@ -9685,7 +10867,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
@@ -9717,7 +10898,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -9727,7 +10907,6 @@
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@@ -9765,7 +10944,6 @@
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -9807,7 +10985,6 @@
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
- "dev": true,
"license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.0.0",
@@ -9825,7 +11002,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"camelcase-css": "^2.0.1"
@@ -9845,7 +11021,6 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -9964,7 +11139,6 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -9990,7 +11164,6 @@
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
@@ -10018,7 +11191,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/prelude-ls": {
@@ -10206,7 +11378,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -10279,6 +11450,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/react-leaflet": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.0.0.tgz",
+ "integrity": "sha512-qJJvoCNe12XHSWVUwhXYmMObPoSYy8h/hn0aDNvcBuq3O8zmVI5S2RdabhaDg/iWMCJ2jbCWZWtIU5VtztO9sg==",
+ "license": "Hippocratic-2.1",
+ "dependencies": {
+ "@react-leaflet/core": "^2.0.0"
+ },
+ "peerDependencies": {
+ "leaflet": "^1.8.0",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ }
+ },
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -10289,6 +11474,53 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
+ "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-router": {
"version": "6.30.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz",
@@ -10321,11 +11553,32 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"pify": "^2.3.0"
@@ -10494,6 +11747,16 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/remix-themes": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/remix-themes/-/remix-themes-1.6.1.tgz",
+ "integrity": "sha512-wqJyNKJ2hiOweycQzsAk7CZm+2mKNAbW2QZcX0riw52XepAxf9R2v8NYyeUz+uWmb3Fulyi71s4aipNRTxCysw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@remix-run/react": ">= 1",
+ "@remix-run/server-runtime": ">= 1"
+ }
+ },
"node_modules/require-like": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz",
@@ -10507,7 +11770,6 @@
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.0",
@@ -10582,7 +11844,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "dev": true,
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
@@ -10696,7 +11957,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -10958,7 +12218,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@@ -10971,7 +12230,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -11079,7 +12337,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -11228,7 +12485,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
@@ -11247,7 +12503,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@@ -11262,14 +12517,12 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
"license": "MIT"
},
"node_modules/string-width/node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -11282,7 +12535,6 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
@@ -11426,7 +12678,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@@ -11440,7 +12691,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@@ -11496,7 +12746,6 @@
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
@@ -11532,7 +12781,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -11541,11 +12789,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tailwind-merge": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz",
+ "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
"node_modules/tailwindcss": {
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@@ -11579,11 +12836,19 @@
"node": ">=14.0.0"
}
},
+ "node_modules/tailwindcss-animate": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
+ "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || insiders"
+ }
+ },
"node_modules/tailwindcss/node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
@@ -11596,7 +12861,6 @@
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
@@ -11726,7 +12990,6 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0"
@@ -11736,7 +12999,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"thenify": ">= 3.1.0 < 4"
@@ -11908,7 +13170,6 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
- "dev": true,
"license": "Apache-2.0"
},
"node_modules/tsconfck": {
@@ -11951,9 +13212,7 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true,
- "license": "0BSD",
- "optional": true
+ "license": "0BSD"
},
"node_modules/turbo-stream": {
"version": "2.4.1",
@@ -12401,6 +13660,58 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
@@ -12418,7 +13729,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true,
"license": "MIT"
},
"node_modules/utils-merge": {
@@ -13243,7 +14553,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
@@ -13262,7 +14571,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
@@ -13280,14 +14588,12 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@@ -13302,7 +14608,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -13315,7 +14620,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -13328,7 +14632,6 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
@@ -13390,7 +14693,6 @@
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
- "dev": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
diff --git a/package.json b/package.json
index 51369e3..f0a41ad 100644
--- a/package.json
+++ b/package.json
@@ -11,15 +11,40 @@
"typecheck": "tsc"
},
"dependencies": {
+ "@bprogress/remix": "^1.0.19",
+ "@radix-ui/react-alert-dialog": "^1.1.14",
+ "@radix-ui/react-avatar": "^1.1.10",
+ "@radix-ui/react-collapsible": "^1.1.11",
+ "@radix-ui/react-dialog": "^1.1.14",
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
+ "@radix-ui/react-label": "^2.1.7",
+ "@radix-ui/react-progress": "^1.1.7",
+ "@radix-ui/react-scroll-area": "^1.2.9",
+ "@radix-ui/react-select": "^2.2.5",
+ "@radix-ui/react-separator": "^1.1.7",
+ "@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-switch": "^1.2.5",
+ "@radix-ui/react-tabs": "^1.1.12",
+ "@radix-ui/react-tooltip": "^1.2.7",
"@remix-run/node": "^2.16.8",
"@remix-run/react": "^2.16.8",
"@remix-run/serve": "^2.16.8",
+ "@tabler/icons-react": "^3.34.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
"isbot": "^4.1.0",
+ "lucide-react": "^0.525.0",
+ "motion": "^12.23.0",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-leaflet": "^4.0.0",
+ "remix-themes": "^1.6.1",
+ "tailwind-merge": "^3.3.1",
+ "tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@remix-run/dev": "^2.16.8",
+ "@types/leaflet": "^1.9.19",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.7.4",
@@ -40,4 +65,4 @@
"engines": {
"node": ">=20.0.0"
}
-}
\ No newline at end of file
+}
diff --git a/public/assets/dashboard_example.png b/public/assets/dashboard_example.png
new file mode 100644
index 0000000..4f501d9
Binary files /dev/null and b/public/assets/dashboard_example.png differ
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 5f06ad4..79c018f 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -1,22 +1,70 @@
import type { Config } from "tailwindcss";
export default {
- content: ["./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}"],
+ darkMode: ["class"],
+ content: ["./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}"],
theme: {
- extend: {
- fontFamily: {
- sans: [
- "Inter",
- "ui-sans-serif",
- "system-ui",
- "sans-serif",
- "Apple Color Emoji",
- "Segoe UI Emoji",
- "Segoe UI Symbol",
- "Noto Color Emoji",
- ],
- },
- },
+ extend: {
+ fontFamily: {
+ sans: [
+ 'Inter',
+ 'ui-sans-serif',
+ 'system-ui',
+ 'sans-serif',
+ 'Apple Color Emoji',
+ 'Segoe UI Emoji',
+ 'Segoe UI Symbol',
+ 'Noto Color Emoji'
+ ]
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)'
+ },
+ colors: {
+ background: 'hsl(var(--background))',
+ foreground: 'hsl(var(--foreground))',
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))'
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))'
+ },
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))'
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))'
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))'
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))'
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))'
+ },
+ border: 'hsl(var(--border))',
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ chart: {
+ '1': 'hsl(var(--chart-1))',
+ '2': 'hsl(var(--chart-2))',
+ '3': 'hsl(var(--chart-3))',
+ '4': 'hsl(var(--chart-4))',
+ '5': 'hsl(var(--chart-5))'
+ }
+ }
+ }
},
- plugins: [],
+ plugins: [require("tailwindcss-animate")],
} satisfies Config;