From 1a3faf03d525aea89a79493b7da57a2d56d6b356 Mon Sep 17 00:00:00 2001
From: "DESKTOP-RTLN3BA\\$punk"
Date: Tue, 30 Sep 2025 20:27:34 -0700
Subject: [PATCH 1/5] feat: added contact and pricing
- Updated framer motion
---
surfsense_web/app/contact/page.tsx | 12 +
.../[search_space_id]/chats/chats-client.tsx | 2 +-
.../connectors/(manage)/page.tsx | 2 +-
.../connectors/[connector_id]/edit/page.tsx | 2 +-
.../connectors/[connector_id]/page.tsx | 2 +-
.../add/airtable-connector/page.tsx | 2 +-
.../add/confluence-connector/page.tsx | 2 +-
.../connectors/add/discord-connector/page.tsx | 2 +-
.../connectors/add/github-connector/page.tsx | 2 +-
.../add/google-calendar-connector/page.tsx | 2 +-
.../add/google-gmail-connector/page.tsx | 2 +-
.../connectors/add/jira-connector/page.tsx | 2 +-
.../connectors/add/linear-connector/page.tsx | 2 +-
.../connectors/add/linkup-api/page.tsx | 2 +-
.../connectors/add/luma-connector/page.tsx | 2 +-
.../connectors/add/notion-connector/page.tsx | 2 +-
.../[search_space_id]/connectors/add/page.tsx | 2 +-
.../connectors/add/serper-api/page.tsx | 2 +-
.../connectors/add/slack-connector/page.tsx | 2 +-
.../connectors/add/tavily-api/page.tsx | 2 +-
.../(manage)/components/DocumentsFilters.tsx | 2 +-
.../components/DocumentsTableShell.tsx | 2 +-
.../components/PaginationControls.tsx | 2 +-
.../documents/(manage)/page.tsx | 2 +-
.../documents/upload/page.tsx | 2 +-
.../documents/youtube/page.tsx | 2 +-
.../[search_space_id]/logs/(manage)/page.tsx | 2 +-
.../app/dashboard/api-key/api-key-client.tsx | 2 +-
surfsense_web/app/dashboard/page.tsx | 2 +-
.../app/dashboard/searchspaces/page.tsx | 2 +-
surfsense_web/app/login/GoogleLoginButton.tsx | 2 +-
surfsense_web/app/login/LocalLoginForm.tsx | 2 +-
surfsense_web/app/login/page.tsx | 2 +-
surfsense_web/app/onboard/page.tsx | 2 +-
surfsense_web/app/pricing/page.tsx | 12 +
surfsense_web/app/register/page.tsx | 2 +-
surfsense_web/components/Navbar.tsx | 2 +-
.../components/chat/AnimatedEmptyState.tsx | 2 +-
.../components/contact/contact-form.tsx | 282 +++++
.../components/onboard/add-provider-step.tsx | 2 +-
.../components/onboard/assign-roles-step.tsx | 2 +-
.../components/onboard/completion-step.tsx | 2 +-
surfsense_web/components/pricing.tsx | 212 ++++
.../components/pricing/pricing-section.tsx | 74 ++
.../components/search-space-form.tsx | 2 +-
.../components/settings/llm-role-manager.tsx | 2 +-
.../settings/model-config-manager.tsx | 2 +-
.../components/theme/theme-toggle.tsx | 2 +-
surfsense_web/components/ui/spotlight.tsx | 2 +-
surfsense_web/components/ui/switch.tsx | 29 +
surfsense_web/components/ui/tilt.tsx | 2 +-
surfsense_web/hooks/use-media-query.ts | 39 +
surfsense_web/package.json | 7 +-
surfsense_web/pnpm-lock.yaml | 123 ++-
surfsense_web/public/contact/world.svg | 977 ++++++++++++++++++
55 files changed, 1802 insertions(+), 55 deletions(-)
create mode 100644 surfsense_web/app/contact/page.tsx
create mode 100644 surfsense_web/app/pricing/page.tsx
create mode 100644 surfsense_web/components/contact/contact-form.tsx
create mode 100644 surfsense_web/components/pricing.tsx
create mode 100644 surfsense_web/components/pricing/pricing-section.tsx
create mode 100644 surfsense_web/components/ui/switch.tsx
create mode 100644 surfsense_web/hooks/use-media-query.ts
create mode 100644 surfsense_web/public/contact/world.svg
diff --git a/surfsense_web/app/contact/page.tsx b/surfsense_web/app/contact/page.tsx
new file mode 100644
index 000000000..cb722ff77
--- /dev/null
+++ b/surfsense_web/app/contact/page.tsx
@@ -0,0 +1,12 @@
+import React from 'react'
+import { ContactFormGridWithDetails } from '@/components/contact/contact-form'
+
+const page = () => {
+ return (
+
+
+
+ )
+}
+
+export default page
\ No newline at end of file
diff --git a/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx b/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx
index 1222733d0..4177b451a 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx
@@ -1,7 +1,7 @@
"use client";
import { format } from "date-fns";
-import { AnimatePresence, motion, type Variants } from "framer-motion";
+import { AnimatePresence, motion, type Variants } from "motion/react";
import {
Calendar,
CheckCircle,
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx
index fb803790b..2520307b7 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { format } from "date-fns";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { Calendar as CalendarIcon, Edit, Plus, RefreshCw, Trash2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx
index 83736ff71..b697acdc1 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useEffect } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx
index 4152ff786..bf0362464 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx
index b787374ef..4455d0189 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react";
import Link from "next/link";
import { useParams, useRouter } from "next/navigation";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx
index 2eb767bf4..0da10f547 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx
index 12ff1c790..8913c069a 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx
index 4fc8e1f41..c0467baa9 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, CircleAlert, Github, Info, ListChecks, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx
index b04a5eddc..6c4dbbf81 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react";
import Link from "next/link";
import { useParams, useRouter, useSearchParams } from "next/navigation";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx
index 43123ec4b..b62f564c2 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react";
import Link from "next/link";
import { useParams, useRouter, useSearchParams } from "next/navigation";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx
index c7d4466b8..cfb812cfd 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx
index 9e1581405..602585e5c 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx
index 582898e0a..b033d2baa 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx
index cc5bde60b..67af12347 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Key, Loader2 } from "lucide-react";
import Link from "next/link";
import { useParams, useRouter } from "next/navigation";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx
index dfb66fe9f..6fc383042 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx
index d3cee106e..a8bb9cfe8 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx
@@ -6,7 +6,7 @@ import {
IconChevronDown,
IconChevronRight,
} from "@tabler/icons-react";
-import { AnimatePresence, motion, type Variants } from "framer-motion";
+import { AnimatePresence, motion, type Variants } from "motion/react";
import Link from "next/link";
import { useParams } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/serper-api/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/serper-api/page.tsx
index 667126e8e..79afeddeb 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/serper-api/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/serper-api/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx
index 6e9802003..4f5e607e7 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx
index e2321abcb..d312d46a5 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx
index dd8791223..7b3d7beaf 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx
@@ -1,6 +1,6 @@
"use client";
-import { AnimatePresence, motion, type Variants } from "framer-motion";
+import { AnimatePresence, motion, type Variants } from "motion/react";
import { CircleAlert, CircleX, Columns3, Filter, ListFilter, Trash } from "lucide-react";
import React, { useMemo, useRef } from "react";
import {
diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx
index d9f9bb251..1317944b7 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ChevronDown, ChevronUp, FileX } from "lucide-react";
import React from "react";
import { DocumentViewer } from "@/components/document-viewer";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsx
index 2e6f8f314..33d2f017a 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ChevronFirst, ChevronLast, ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx
index 4a69a7533..dfaee38a8 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { useParams } from "next/navigation";
import { useEffect, useId, useMemo, useState } from "react";
import { toast } from "sonner";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx
index efea74b49..438e10980 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { AnimatePresence, motion } from "framer-motion";
+import { AnimatePresence, motion } from "motion/react";
import { CheckCircle2, FileType, Info, Tag, Upload, X } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useCallback, useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsx
index 494420585..af1b702b7 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsx
@@ -2,7 +2,7 @@
import { IconBrandYoutube } from "@tabler/icons-react";
import { type Tag, TagInput } from "emblor";
-import { motion, type Variants } from "framer-motion";
+import { motion, type Variants } from "motion/react";
import { Loader2 } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx
index c1c8fd132..e5c4351bf 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx
@@ -15,7 +15,7 @@ import {
useReactTable,
type VisibilityState,
} from "@tanstack/react-table";
-import { AnimatePresence, motion, type Variants } from "framer-motion";
+import { AnimatePresence, motion, type Variants } from "motion/react";
import {
Activity,
AlertCircle,
diff --git a/surfsense_web/app/dashboard/api-key/api-key-client.tsx b/surfsense_web/app/dashboard/api-key/api-key-client.tsx
index 97a36d8e6..97b478dc0 100644
--- a/surfsense_web/app/dashboard/api-key/api-key-client.tsx
+++ b/surfsense_web/app/dashboard/api-key/api-key-client.tsx
@@ -1,7 +1,7 @@
"use client";
import { IconCheck, IconCopy, IconKey } from "@tabler/icons-react";
-import { AnimatePresence, motion } from "framer-motion";
+import { AnimatePresence, motion } from "motion/react";
import { ArrowLeft } from "lucide-react";
import { useRouter } from "next/navigation";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
diff --git a/surfsense_web/app/dashboard/page.tsx b/surfsense_web/app/dashboard/page.tsx
index 484b95929..e0ac6b6cf 100644
--- a/surfsense_web/app/dashboard/page.tsx
+++ b/surfsense_web/app/dashboard/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion, type Variants } from "framer-motion";
+import { motion, type Variants } from "motion/react";
import { AlertCircle, Loader2, Plus, Search, Trash2 } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
diff --git a/surfsense_web/app/dashboard/searchspaces/page.tsx b/surfsense_web/app/dashboard/searchspaces/page.tsx
index b1f2f390e..598536c1b 100644
--- a/surfsense_web/app/dashboard/searchspaces/page.tsx
+++ b/surfsense_web/app/dashboard/searchspaces/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { SearchSpaceForm } from "@/components/search-space-form";
diff --git a/surfsense_web/app/login/GoogleLoginButton.tsx b/surfsense_web/app/login/GoogleLoginButton.tsx
index ab0e0ddf8..545f279e7 100644
--- a/surfsense_web/app/login/GoogleLoginButton.tsx
+++ b/surfsense_web/app/login/GoogleLoginButton.tsx
@@ -1,6 +1,6 @@
"use client";
import { IconBrandGoogleFilled } from "@tabler/icons-react";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { Logo } from "@/components/Logo";
import { AmbientBackground } from "./AmbientBackground";
diff --git a/surfsense_web/app/login/LocalLoginForm.tsx b/surfsense_web/app/login/LocalLoginForm.tsx
index b464bea98..a81b9d793 100644
--- a/surfsense_web/app/login/LocalLoginForm.tsx
+++ b/surfsense_web/app/login/LocalLoginForm.tsx
@@ -1,5 +1,5 @@
"use client";
-import { AnimatePresence, motion } from "framer-motion";
+import { AnimatePresence, motion } from "motion/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
diff --git a/surfsense_web/app/login/page.tsx b/surfsense_web/app/login/page.tsx
index c9e9d9090..59ff16ee3 100644
--- a/surfsense_web/app/login/page.tsx
+++ b/surfsense_web/app/login/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { AnimatePresence, motion } from "framer-motion";
+import { AnimatePresence, motion } from "motion/react";
import { Loader2 } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { Suspense, useEffect, useState } from "react";
diff --git a/surfsense_web/app/onboard/page.tsx b/surfsense_web/app/onboard/page.tsx
index 997b7e4c8..983b240c1 100644
--- a/surfsense_web/app/onboard/page.tsx
+++ b/surfsense_web/app/onboard/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { AnimatePresence, motion } from "framer-motion";
+import { AnimatePresence, motion } from "motion/react";
import { ArrowLeft, ArrowRight, Bot, CheckCircle, Sparkles } from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
diff --git a/surfsense_web/app/pricing/page.tsx b/surfsense_web/app/pricing/page.tsx
new file mode 100644
index 000000000..6d71d6ff4
--- /dev/null
+++ b/surfsense_web/app/pricing/page.tsx
@@ -0,0 +1,12 @@
+import React from 'react'
+import PricingBasic from '@/components/pricing/pricing-section'
+
+const page = () => {
+ return (
+
+ )
+}
+
+export default page
\ No newline at end of file
diff --git a/surfsense_web/app/register/page.tsx b/surfsense_web/app/register/page.tsx
index f046e5cff..303d9a378 100644
--- a/surfsense_web/app/register/page.tsx
+++ b/surfsense_web/app/register/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { AnimatePresence, motion } from "framer-motion";
+import { AnimatePresence, motion } from "motion/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
diff --git a/surfsense_web/components/Navbar.tsx b/surfsense_web/components/Navbar.tsx
index 8a38ca863..d1df0c927 100644
--- a/surfsense_web/components/Navbar.tsx
+++ b/surfsense_web/components/Navbar.tsx
@@ -1,6 +1,6 @@
"use client";
import { IconMenu2, IconUser, IconX } from "@tabler/icons-react";
-import { AnimatePresence, motion, useMotionValueEvent, useScroll } from "framer-motion";
+import { AnimatePresence, motion, useMotionValueEvent, useScroll } from "motion/react";
import Link from "next/link";
import { useRef, useState } from "react";
import { cn } from "@/lib/utils";
diff --git a/surfsense_web/components/chat/AnimatedEmptyState.tsx b/surfsense_web/components/chat/AnimatedEmptyState.tsx
index 992b75395..1ab8d5c84 100644
--- a/surfsense_web/components/chat/AnimatedEmptyState.tsx
+++ b/surfsense_web/components/chat/AnimatedEmptyState.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useInView } from "framer-motion";
+import { useInView } from "motion/react";
import { Manrope } from "next/font/google";
import { useEffect, useMemo, useReducer, useRef } from "react";
import { RoughNotation, RoughNotationGroup } from "react-rough-notation";
diff --git a/surfsense_web/components/contact/contact-form.tsx b/surfsense_web/components/contact/contact-form.tsx
new file mode 100644
index 000000000..5af9bb783
--- /dev/null
+++ b/surfsense_web/components/contact/contact-form.tsx
@@ -0,0 +1,282 @@
+"use client";
+import React from "react";
+import { IconMailFilled } from "@tabler/icons-react";
+import { useId } from "react";
+import { cn } from "@/lib/utils";
+import Image from "next/image";
+import { motion } from "motion/react";
+import Link from "next/link";
+
+export function ContactFormGridWithDetails() {
+ return (
+
+
+
+
+
+
+
+
+ Contact us
+
+
+ We are always looking for ways to improve our products and services.
+ Contact us and let us know how we can help you.
+
+
+
+
+ rohan@surfsense.com
+
+
+ {/*
+
+ +1 (800) 123 XX21
+
*/}
+
+
+
+ https://cal.com/mod-surfsense
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const Pin = ({ className }: { className?: string }) => {
+ return (
+
+
+
+ We are here
+
+
+
+
+ <>
+
+
+
+ >
+
+
+ <>
+
+
+
+
+ >
+
+
+ );
+};
+
+export const FeatureIconContainer = ({
+ children,
+ className,
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) => {
+ return (
+
+ );
+};
+
+export const Grid = ({
+ pattern,
+ size,
+}: {
+ pattern?: number[][];
+ size?: number;
+}) => {
+ const p = pattern ?? [
+ [9, 3],
+ [8, 5],
+ [10, 2],
+ [7, 4],
+ [9, 6],
+ ];
+ return (
+
+ );
+};
+
+export function GridPattern({ width, height, x, y, squares, ...props }: any) {
+ const patternId = useId();
+
+ return (
+
+ );
+}
diff --git a/surfsense_web/components/onboard/add-provider-step.tsx b/surfsense_web/components/onboard/add-provider-step.tsx
index af8d486e8..fc45df875 100644
--- a/surfsense_web/components/onboard/add-provider-step.tsx
+++ b/surfsense_web/components/onboard/add-provider-step.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { AlertCircle, Bot, Plus, Trash2 } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
diff --git a/surfsense_web/components/onboard/assign-roles-step.tsx b/surfsense_web/components/onboard/assign-roles-step.tsx
index 2a7b25288..1e4aa2ae2 100644
--- a/surfsense_web/components/onboard/assign-roles-step.tsx
+++ b/surfsense_web/components/onboard/assign-roles-step.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { AlertCircle, Bot, Brain, CheckCircle, Zap } from "lucide-react";
import { useEffect, useState } from "react";
import { Alert, AlertDescription } from "@/components/ui/alert";
diff --git a/surfsense_web/components/onboard/completion-step.tsx b/surfsense_web/components/onboard/completion-step.tsx
index d23da492d..46787030b 100644
--- a/surfsense_web/components/onboard/completion-step.tsx
+++ b/surfsense_web/components/onboard/completion-step.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { ArrowRight, Bot, Brain, CheckCircle, Sparkles, Zap } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
diff --git a/surfsense_web/components/pricing.tsx b/surfsense_web/components/pricing.tsx
new file mode 100644
index 000000000..becb13c96
--- /dev/null
+++ b/surfsense_web/components/pricing.tsx
@@ -0,0 +1,212 @@
+"use client";
+
+import { buttonVariants } from "@/components/ui/button";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import { useMediaQuery } from "@/hooks/use-media-query";
+import { cn } from "@/lib/utils";
+import { motion } from "framer-motion";
+import { Check, Star } from "lucide-react";
+import Link from "next/link";
+import { useState, useRef } from "react";
+import NumberFlow from "@number-flow/react";
+import confetti from "canvas-confetti";
+
+interface PricingPlan {
+ name: string;
+ price: string;
+ yearlyPrice: string;
+ period: string;
+ features: string[];
+ description: string;
+ buttonText: string;
+ href: string;
+ isPopular: boolean;
+}
+
+interface PricingProps {
+ plans: PricingPlan[];
+ title?: string;
+ description?: string;
+}
+
+export function Pricing({
+ plans,
+ title = "Simple, Transparent Pricing",
+ description = "Choose the plan that works for you\nAll plans include access to our platform, lead generation tools, and dedicated support.",
+}: PricingProps) {
+ const [isMonthly, setIsMonthly] = useState(true);
+ const isDesktop = useMediaQuery("(min-width: 768px)");
+ const switchRef = useRef(null);
+
+ const handleToggle = (checked: boolean) => {
+ setIsMonthly(!checked);
+ if (checked && switchRef.current) {
+ const rect = switchRef.current.getBoundingClientRect();
+ const x = rect.left + rect.width / 2;
+ const y = rect.top + rect.height / 2;
+
+ confetti({
+ particleCount: 50,
+ spread: 60,
+ origin: {
+ x: x / window.innerWidth,
+ y: y / window.innerHeight,
+ },
+ colors: [
+ "hsl(var(--primary))",
+ "hsl(var(--accent))",
+ "hsl(var(--secondary))",
+ "hsl(var(--muted))",
+ ],
+ ticks: 200,
+ gravity: 1.2,
+ decay: 0.94,
+ startVelocity: 30,
+ shapes: ["circle"],
+ });
+ }
+ };
+
+ return (
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+
+
+
+ Annual billing (Save 20%)
+
+
+
+
+ {plans.map((plan, index) => (
+
+ {plan.isPopular && (
+
+
+
+ Popular
+
+
+ )}
+
+
+ {plan.name}
+
+
+
+
+
+ {plan.period !== "Next 3 months" && (
+
+ / {plan.period}
+
+ )}
+
+
+
+ {isMonthly ? "billed monthly" : "billed annually"}
+
+
+
+ {plan.features.map((feature, idx) => (
+ -
+
+ {feature}
+
+ ))}
+
+
+
+
+
+ {plan.buttonText}
+
+
+ {plan.description}
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/surfsense_web/components/pricing/pricing-section.tsx b/surfsense_web/components/pricing/pricing-section.tsx
new file mode 100644
index 000000000..85c12bcd0
--- /dev/null
+++ b/surfsense_web/components/pricing/pricing-section.tsx
@@ -0,0 +1,74 @@
+"use client";
+
+import { Pricing } from "@/components/pricing";
+
+const demoPlans = [
+ {
+ name: "STARTER",
+ price: "50",
+ yearlyPrice: "40",
+ period: "per month",
+ features: [
+ "Up to 10 projects",
+ "Basic analytics",
+ "48-hour support response time",
+ "Limited API access",
+ "Community support",
+ ],
+ description: "Perfect for individuals and small projects",
+ buttonText: "Start Free Trial",
+ href: "/sign-up",
+ isPopular: false,
+ },
+ {
+ name: "PROFESSIONAL",
+ price: "99",
+ yearlyPrice: "79",
+ period: "per month",
+ features: [
+ "Unlimited projects",
+ "Advanced analytics",
+ "24-hour support response time",
+ "Full API access",
+ "Priority support",
+ "Team collaboration",
+ "Custom integrations",
+ ],
+ description: "Ideal for growing teams and businesses",
+ buttonText: "Get Started",
+ href: "/sign-up",
+ isPopular: true,
+ },
+ {
+ name: "ENTERPRISE",
+ price: "299",
+ yearlyPrice: "239",
+ period: "per month",
+ features: [
+ "Everything in Professional",
+ "Custom solutions",
+ "Dedicated account manager",
+ "1-hour support response time",
+ "SSO Authentication",
+ "Advanced security",
+ "Custom contracts",
+ "SLA agreement",
+ ],
+ description: "For large organizations with specific needs",
+ buttonText: "Contact Sales",
+ href: "/contact",
+ isPopular: false,
+ },
+];
+
+function PricingBasic() {
+ return (
+
+ );
+}
+
+export default PricingBasic;
diff --git a/surfsense_web/components/search-space-form.tsx b/surfsense_web/components/search-space-form.tsx
index 4516c912a..d87461f5b 100644
--- a/surfsense_web/components/search-space-form.tsx
+++ b/surfsense_web/components/search-space-form.tsx
@@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { motion, type Variants } from "framer-motion";
+import { motion, type Variants } from "motion/react";
import { MoveLeftIcon, Plus, Search, Trash2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/surfsense_web/components/settings/llm-role-manager.tsx b/surfsense_web/components/settings/llm-role-manager.tsx
index d1e7ae98d..3b23c6fc5 100644
--- a/surfsense_web/components/settings/llm-role-manager.tsx
+++ b/surfsense_web/components/settings/llm-role-manager.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import {
AlertCircle,
Bot,
diff --git a/surfsense_web/components/settings/model-config-manager.tsx b/surfsense_web/components/settings/model-config-manager.tsx
index bc59532d1..c6ad7be1f 100644
--- a/surfsense_web/components/settings/model-config-manager.tsx
+++ b/surfsense_web/components/settings/model-config-manager.tsx
@@ -1,6 +1,6 @@
"use client";
-import { AnimatePresence, motion } from "framer-motion";
+import { AnimatePresence, motion } from "motion/react";
import {
AlertCircle,
Bot,
diff --git a/surfsense_web/components/theme/theme-toggle.tsx b/surfsense_web/components/theme/theme-toggle.tsx
index 587dd501a..a2c807907 100644
--- a/surfsense_web/components/theme/theme-toggle.tsx
+++ b/surfsense_web/components/theme/theme-toggle.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { MoonIcon, SunIcon } from "lucide-react";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
diff --git a/surfsense_web/components/ui/spotlight.tsx b/surfsense_web/components/ui/spotlight.tsx
index 67bb5c7c4..2258e8814 100644
--- a/surfsense_web/components/ui/spotlight.tsx
+++ b/surfsense_web/components/ui/spotlight.tsx
@@ -1,5 +1,5 @@
"use client";
-import { motion, type SpringOptions, useSpring, useTransform } from "framer-motion";
+import { motion, type SpringOptions, useSpring, useTransform } from "motion/react";
import { useCallback, useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
diff --git a/surfsense_web/components/ui/switch.tsx b/surfsense_web/components/ui/switch.tsx
new file mode 100644
index 000000000..8c464e46b
--- /dev/null
+++ b/surfsense_web/components/ui/switch.tsx
@@ -0,0 +1,29 @@
+"use client"
+
+import * as React from "react"
+import * as SwitchPrimitives from "@radix-ui/react-switch"
+
+import { cn } from "@/lib/utils"
+
+const Switch = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+Switch.displayName = SwitchPrimitives.Root.displayName
+
+export { Switch }
diff --git a/surfsense_web/components/ui/tilt.tsx b/surfsense_web/components/ui/tilt.tsx
index db0868cd5..985cff94b 100644
--- a/surfsense_web/components/ui/tilt.tsx
+++ b/surfsense_web/components/ui/tilt.tsx
@@ -8,7 +8,7 @@ import {
useMotionValue,
useSpring,
useTransform,
-} from "framer-motion";
+} from "motion/react";
import type React from "react";
import { useRef } from "react";
diff --git a/surfsense_web/hooks/use-media-query.ts b/surfsense_web/hooks/use-media-query.ts
new file mode 100644
index 000000000..af1cbe574
--- /dev/null
+++ b/surfsense_web/hooks/use-media-query.ts
@@ -0,0 +1,39 @@
+import { useEffect, useState } from "react";
+
+/**
+ * Custom hook that tracks if a media query matches
+ * @param query - The media query string to match (e.g., "(min-width: 768px)")
+ * @returns boolean - True if the media query matches, false otherwise
+ */
+export function useMediaQuery(query: string): boolean {
+ const [matches, setMatches] = useState(false);
+
+ useEffect(() => {
+ // Check if we're in the browser (handle SSR)
+ if (typeof window === "undefined") {
+ return;
+ }
+
+ const mediaQuery = window.matchMedia(query);
+
+ // Set initial value
+ setMatches(mediaQuery.matches);
+
+ // Create event listener
+ const handler = (event: MediaQueryListEvent) => {
+ setMatches(event.matches);
+ };
+
+ // Add event listener
+ mediaQuery.addEventListener("change", handler);
+
+ // Cleanup
+ return () => {
+ mediaQuery.removeEventListener("change", handler);
+ };
+ }, [query]);
+
+ return matches;
+}
+
+
diff --git a/surfsense_web/package.json b/surfsense_web/package.json
index 0ae413b34..36b63fad3 100644
--- a/surfsense_web/package.json
+++ b/surfsense_web/package.json
@@ -18,6 +18,7 @@
"@ai-sdk/react": "^1.2.12",
"@hookform/resolvers": "^4.1.3",
"@llamaindex/chat-ui": "^0.5.17",
+ "@number-flow/react": "^0.5.10",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-avatar": "^1.1.10",
@@ -32,6 +33,7 @@
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.3.5",
"@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-toggle-group": "^1.1.10",
@@ -41,16 +43,18 @@
"@types/mdx": "^2.0.13",
"@types/react-syntax-highlighter": "^15.5.13",
"ai": "^4.3.19",
+ "canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"emblor": "^1.4.8",
- "framer-motion": "^12.23.9",
+ "framer-motion": "^12.23.22",
"fumadocs-core": "^15.6.6",
"fumadocs-mdx": "^11.7.1",
"fumadocs-ui": "^15.6.6",
"geist": "^1.4.2",
"lucide-react": "^0.477.0",
+ "motion": "^12.23.22",
"next": "^15.4.4",
"next-themes": "^0.4.6",
"react": "^19.1.0",
@@ -76,6 +80,7 @@
"@eslint/eslintrc": "^3.3.1",
"@tailwindcss/postcss": "^4.1.11",
"@tailwindcss/typography": "^0.5.16",
+ "@types/canvas-confetti": "^1.9.0",
"@types/node": "^20.19.9",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
diff --git a/surfsense_web/pnpm-lock.yaml b/surfsense_web/pnpm-lock.yaml
index 11f90611c..7d387d6fc 100644
--- a/surfsense_web/pnpm-lock.yaml
+++ b/surfsense_web/pnpm-lock.yaml
@@ -17,6 +17,9 @@ importers:
'@llamaindex/chat-ui':
specifier: ^0.5.17
version: 0.5.17(@babel/runtime@7.26.9)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.2)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.38.1)(@lezer/highlight@1.2.1)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(codemirror@6.0.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yjs@13.6.27)
+ '@number-flow/react':
+ specifier: ^0.5.10
+ version: 0.5.10(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-accordion':
specifier: ^1.2.11
version: 1.2.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -59,6 +62,9 @@ importers:
'@radix-ui/react-slot':
specifier: ^1.2.3
version: 1.2.3(@types/react@19.1.8)(react@19.1.0)
+ '@radix-ui/react-switch':
+ specifier: ^1.2.6
+ version: 1.2.6(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-tabs':
specifier: ^1.1.12
version: 1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -86,6 +92,9 @@ importers:
ai:
specifier: ^4.3.19
version: 4.3.19(react@19.1.0)(zod@3.25.76)
+ canvas-confetti:
+ specifier: ^1.9.3
+ version: 1.9.3
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -99,8 +108,8 @@ importers:
specifier: ^1.4.8
version: 1.4.8(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
framer-motion:
- specifier: ^12.23.9
- version: 12.23.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ specifier: ^12.23.22
+ version: 12.23.22(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
fumadocs-core:
specifier: ^15.6.6
version: 15.6.6(@types/react@19.1.8)(next@15.4.4(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -116,6 +125,9 @@ importers:
lucide-react:
specifier: ^0.477.0
version: 0.477.0(react@19.1.0)
+ motion:
+ specifier: ^12.23.22
+ version: 12.23.22(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next:
specifier: ^15.4.4
version: 15.4.4(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -186,6 +198,9 @@ importers:
'@tailwindcss/typography':
specifier: ^0.5.16
version: 0.5.16(tailwindcss@4.1.11)
+ '@types/canvas-confetti':
+ specifier: ^1.9.0
+ version: 1.9.0
'@types/node':
specifier: ^20.19.9
version: 20.19.9
@@ -1047,6 +1062,12 @@ packages:
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
engines: {node: '>=12.4.0'}
+ '@number-flow/react@0.5.10':
+ resolution: {integrity: sha512-a8Wh5eNITn7Km4xbddAH7QH8eNmnduR6k34ER1hkHSGO4H2yU1DDnuAWLQM99vciGInFODemSc0tdxrXkJEpbA==}
+ peerDependencies:
+ react: ^18 || ^19
+ react-dom: ^18 || ^19
+
'@open-draft/deferred-promise@2.2.0':
resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
@@ -1073,6 +1094,9 @@ packages:
'@radix-ui/primitive@1.1.2':
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
+ '@radix-ui/primitive@1.1.3':
+ resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
+
'@radix-ui/react-accordion@1.2.11':
resolution: {integrity: sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A==}
peerDependencies:
@@ -1702,6 +1726,19 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-switch@1.2.6':
+ resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==}
+ 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
+
'@radix-ui/react-tabs@1.1.12':
resolution: {integrity: sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==}
peerDependencies:
@@ -2087,6 +2124,9 @@ packages:
resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==}
engines: {node: '>=12'}
+ '@types/canvas-confetti@1.9.0':
+ resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==}
+
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@@ -2398,6 +2438,9 @@ packages:
caniuse-lite@1.0.30001706:
resolution: {integrity: sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==}
+ canvas-confetti@1.9.3:
+ resolution: {integrity: sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g==}
+
canvas@3.1.2:
resolution: {integrity: sha512-Z/tzFAcBzoCvJlOSlCnoekh1Gu8YMn0J51+UAuXJAbW1Z6I9l2mZgdD7738MepoeeIcUdDtbMnOg6cC7GJxy/g==}
engines: {node: ^18.12.0 || >= 20.9.0}
@@ -2828,6 +2871,9 @@ packages:
jiti:
optional: true
+ esm-env@1.2.2:
+ resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
+
esniff@2.0.1:
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
engines: {node: '>=0.10'}
@@ -2980,8 +3026,8 @@ packages:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
engines: {node: '>=0.4.x'}
- framer-motion@12.23.9:
- resolution: {integrity: sha512-TqEHXj8LWfQSKqfdr5Y4mYltYLw96deu6/K9kGDd+ysqRJPNwF9nb5mZcrLmybHbU7gcJ+HQar41U3UTGanbbQ==}
+ framer-motion@12.23.22:
+ resolution: {integrity: sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
@@ -3968,12 +4014,26 @@ packages:
engines: {node: '>=10'}
hasBin: true
- motion-dom@12.23.9:
- resolution: {integrity: sha512-6Sv++iWS8XMFCgU1qwKj9l4xuC47Hp4+2jvPfyTXkqDg2tTzSgX6nWKD4kNFXk0k7llO59LZTPuJigza4A2K1A==}
+ motion-dom@12.23.21:
+ resolution: {integrity: sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==}
motion-utils@12.23.6:
resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==}
+ motion@12.23.22:
+ resolution: {integrity: sha512-iSq6X9vLHbeYwmHvhK//+U74ROaPnZmBuy60XZzqNl0QtZkWfoZyMDHYnpKuWFv0sNMqHgED8aCXk94LCoQPGg==}
+ 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
+
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@@ -4046,6 +4106,9 @@ packages:
resolution: {integrity: sha512-tt6PvKu4WyzPwWUzy/hvPFqn+uwXO0K1ZHka8az3NnrhWJDmSqI8ncWq0fkL0k/lmmi5tAC11FXwXuh0rFbt1A==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ number-flow@0.5.8:
+ resolution: {integrity: sha512-FPr1DumWyGi5Nucoug14bC6xEz70A1TnhgSHhKyfqjgji2SOTz+iLJxKtv37N5JyJbteGYCm6NQ9p1O4KZ7iiA==}
+
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -6198,6 +6261,13 @@ snapshots:
'@nolyfill/is-core-module@1.0.39': {}
+ '@number-flow/react@0.5.10(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ esm-env: 1.2.2
+ number-flow: 0.5.8
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
'@open-draft/deferred-promise@2.2.0': {}
'@opentelemetry/api@1.9.0': {}
@@ -6218,6 +6288,8 @@ snapshots:
'@radix-ui/primitive@1.1.2': {}
+ '@radix-ui/primitive@1.1.3': {}
+
'@radix-ui/react-accordion@1.2.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/primitive': 1.1.2
@@ -6914,6 +6986,21 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.8
+ '@radix-ui/react-switch@1.2.6(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.8
+ '@types/react-dom': 19.1.6(@types/react@19.1.8)
+
'@radix-ui/react-tabs@1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/primitive': 1.1.2
@@ -7281,6 +7368,8 @@ snapshots:
'@tanstack/table-core@8.21.3': {}
+ '@types/canvas-confetti@1.9.0': {}
+
'@types/debug@4.1.12':
dependencies:
'@types/ms': 2.1.0
@@ -7655,6 +7744,8 @@ snapshots:
caniuse-lite@1.0.30001706: {}
+ canvas-confetti@1.9.3: {}
+
canvas@3.1.2:
dependencies:
node-addon-api: 7.1.1
@@ -8260,6 +8351,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ esm-env@1.2.2: {}
+
esniff@2.0.1:
dependencies:
d: 1.0.2
@@ -8442,9 +8535,9 @@ snapshots:
format@0.2.2: {}
- framer-motion@12.23.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ framer-motion@12.23.22(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
- motion-dom: 12.23.9
+ motion-dom: 12.23.21
motion-utils: 12.23.6
tslib: 2.8.1
optionalDependencies:
@@ -9972,12 +10065,20 @@ snapshots:
mkdirp@3.0.1: {}
- motion-dom@12.23.9:
+ motion-dom@12.23.21:
dependencies:
motion-utils: 12.23.6
motion-utils@12.23.6: {}
+ motion@12.23.22(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ framer-motion: 12.23.22(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ tslib: 2.8.1
+ optionalDependencies:
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
mri@1.2.0: {}
ms@2.1.3: {}
@@ -10036,6 +10137,10 @@ snapshots:
npm-to-yarn@3.0.1: {}
+ number-flow@0.5.8:
+ dependencies:
+ esm-env: 1.2.2
+
object-assign@4.1.1: {}
object-inspect@1.13.4: {}
diff --git a/surfsense_web/public/contact/world.svg b/surfsense_web/public/contact/world.svg
new file mode 100644
index 000000000..84c8e242e
--- /dev/null
+++ b/surfsense_web/public/contact/world.svg
@@ -0,0 +1,977 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
From ba62489715c559772fded136eeaff128e84ef712 Mon Sep 17 00:00:00 2001
From: "DESKTOP-RTLN3BA\\$punk"
Date: Tue, 30 Sep 2025 21:36:07 -0700
Subject: [PATCH 2/5] feat: added drizzle for contact management
---
surfsense_web/.env.example | 4 +-
surfsense_web/app/api/contact/route.ts | 59 ++
surfsense_web/app/db/index.ts | 6 +
surfsense_web/app/db/schema.ts | 9 +
.../components/contact/contact-form.tsx | 121 +++-
surfsense_web/drizzle.config.ts | 11 +
surfsense_web/package.json | 13 +-
surfsense_web/pnpm-lock.yaml | 557 ++++++++++++++++++
8 files changed, 762 insertions(+), 18 deletions(-)
create mode 100644 surfsense_web/app/api/contact/route.ts
create mode 100644 surfsense_web/app/db/index.ts
create mode 100644 surfsense_web/app/db/schema.ts
create mode 100644 surfsense_web/drizzle.config.ts
diff --git a/surfsense_web/.env.example b/surfsense_web/.env.example
index cff9031be..157bfaa37 100644
--- a/surfsense_web/.env.example
+++ b/surfsense_web/.env.example
@@ -1,3 +1,5 @@
NEXT_PUBLIC_FASTAPI_BACKEND_URL=http://localhost:8000
NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE=LOCAL or GOOGLE
-NEXT_PUBLIC_ETL_SERVICE=UNSTRUCTURED or LLAMACLOUD or DOCLING
\ No newline at end of file
+NEXT_PUBLIC_ETL_SERVICE=UNSTRUCTURED or LLAMACLOUD or DOCLING
+# Contact Form Vars - OPTIONAL
+DATABASE_URL=postgresql://postgres:[YOUR-PASSWORD]@db.sdsf.supabase.co:5432/postgres
\ No newline at end of file
diff --git a/surfsense_web/app/api/contact/route.ts b/surfsense_web/app/api/contact/route.ts
new file mode 100644
index 000000000..c7cafa467
--- /dev/null
+++ b/surfsense_web/app/api/contact/route.ts
@@ -0,0 +1,59 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { db } from '@/app/db';
+import { usersTable } from '@/app/db/schema';
+import { z } from 'zod';
+
+// Define validation schema matching the database schema
+const contactSchema = z.object({
+ name: z.string().min(1, 'Name is required').max(255, 'Name is too long'),
+ email: z.string().email('Invalid email address').max(255, 'Email is too long'),
+ company: z.string().min(1, 'Company is required').max(255, 'Company name is too long'),
+ message: z.string().optional().default(''),
+});
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json();
+
+ // Validate the request body
+ const validatedData = contactSchema.parse(body);
+
+ // Insert into database
+ const result = await db.insert(usersTable).values({
+ name: validatedData.name,
+ email: validatedData.email,
+ company: validatedData.company,
+ message: validatedData.message,
+ }).returning();
+
+ return NextResponse.json(
+ {
+ success: true,
+ message: 'Contact form submitted successfully',
+ data: result[0]
+ },
+ { status: 201 }
+ );
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ return NextResponse.json(
+ {
+ success: false,
+ message: 'Validation error',
+ errors: error.errors
+ },
+ { status: 400 }
+ );
+ }
+
+ console.error('Error submitting contact form:', error);
+ return NextResponse.json(
+ {
+ success: false,
+ message: 'Failed to submit contact form'
+ },
+ { status: 500 }
+ );
+ }
+}
+
diff --git a/surfsense_web/app/db/index.ts b/surfsense_web/app/db/index.ts
new file mode 100644
index 000000000..5d7a87757
--- /dev/null
+++ b/surfsense_web/app/db/index.ts
@@ -0,0 +1,6 @@
+import { drizzle } from 'drizzle-orm/postgres-js'
+import postgres from 'postgres'
+import * as schema from './schema'
+
+const client = postgres(process.env.DATABASE_URL!)
+export const db = drizzle({ client, schema });
diff --git a/surfsense_web/app/db/schema.ts b/surfsense_web/app/db/schema.ts
new file mode 100644
index 000000000..fc5678bc3
--- /dev/null
+++ b/surfsense_web/app/db/schema.ts
@@ -0,0 +1,9 @@
+import { integer, pgTable, text, varchar } from "drizzle-orm/pg-core";
+
+export const usersTable = pgTable("users", {
+ id: integer().primaryKey().generatedAlwaysAsIdentity(),
+ name: varchar({ length: 255 }).notNull(),
+ email: varchar({ length: 255 }).notNull().unique(),
+ company: varchar({ length: 255 }).notNull(),
+ message: text().default(''),
+});
diff --git a/surfsense_web/components/contact/contact-form.tsx b/surfsense_web/components/contact/contact-form.tsx
index 5af9bb783..34d7e6311 100644
--- a/surfsense_web/components/contact/contact-form.tsx
+++ b/surfsense_web/components/contact/contact-form.tsx
@@ -1,13 +1,72 @@
"use client";
-import React from "react";
+import React, { useState } from "react";
import { IconMailFilled } from "@tabler/icons-react";
import { useId } from "react";
import { cn } from "@/lib/utils";
import Image from "next/image";
import { motion } from "motion/react";
import Link from "next/link";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+import { toast } from "sonner";
+
+// Define validation schema matching the database schema
+const contactFormSchema = z.object({
+ name: z.string().min(1, 'Name is required').max(255, 'Name is too long'),
+ email: z.string().email('Invalid email address').max(255, 'Email is too long'),
+ company: z.string().min(1, 'Company is required').max(255, 'Company name is too long'),
+ message: z.string().optional().default(''),
+});
+
+type ContactFormData = z.infer;
export function ContactFormGridWithDetails() {
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ reset,
+ } = useForm({
+ resolver: zodResolver(contactFormSchema),
+ });
+
+ const onSubmit = async (data: ContactFormData) => {
+ setIsSubmitting(true);
+
+ try {
+ const response = await fetch('/api/contact', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(data),
+ });
+
+ const result = await response.json();
+
+ if (response.ok) {
+ toast.success('Message sent successfully!', {
+ description: 'We will get back to you as soon as possible.',
+ });
+ reset();
+ } else {
+ toast.error('Failed to send message', {
+ description: result.message || 'Please try again later.',
+ });
+ }
+ } catch (error) {
+ console.error('Error submitting form:', error);
+ toast.error('Something went wrong', {
+ description: 'Please try again later.',
+ });
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
return (
@@ -26,17 +85,12 @@ export function ContactFormGridWithDetails() {
- rohan@surfsense.com
+ rohan@surfsense.com
-
- {/*
-
- +1 (800) 123 XX21
-
*/}
- https://cal.com/mod-surfsense
+ https://cal.com/mod-surfsense
@@ -51,7 +105,10 @@ export function ContactFormGridWithDetails() {
/>
-
+
);
}
diff --git a/surfsense_web/drizzle.config.ts b/surfsense_web/drizzle.config.ts
new file mode 100644
index 000000000..fbd033a9e
--- /dev/null
+++ b/surfsense_web/drizzle.config.ts
@@ -0,0 +1,11 @@
+import 'dotenv/config';
+import { defineConfig } from 'drizzle-kit';
+
+export default defineConfig({
+ out: './drizzle',
+ schema: './app/db/schema.ts',
+ dialect: 'postgresql',
+ dbCredentials: {
+ url: process.env.DATABASE_URL!,
+ },
+});
diff --git a/surfsense_web/package.json b/surfsense_web/package.json
index 36b63fad3..b17dfd626 100644
--- a/surfsense_web/package.json
+++ b/surfsense_web/package.json
@@ -12,7 +12,11 @@
"debug": "cross-env NODE_OPTIONS=--inspect next dev --turbopack",
"debug:browser": "cross-env NODE_OPTIONS=--inspect next dev --turbopack",
"debug:server": "cross-env NODE_OPTIONS=--inspect=0.0.0.0:9229 next dev --turbopack",
- "postinstall": "fumadocs-mdx"
+ "postinstall": "fumadocs-mdx",
+ "db:generate": "drizzle-kit generate",
+ "db:migrate": "drizzle-kit migrate",
+ "db:push": "drizzle-kit push",
+ "db:studio": "drizzle-kit studio"
},
"dependencies": {
"@ai-sdk/react": "^1.2.12",
@@ -47,6 +51,8 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
+ "dotenv": "^17.2.3",
+ "drizzle-orm": "^0.44.5",
"emblor": "^1.4.8",
"framer-motion": "^12.23.22",
"fumadocs-core": "^15.6.6",
@@ -57,6 +63,8 @@
"motion": "^12.23.22",
"next": "^15.4.4",
"next-themes": "^0.4.6",
+ "pg": "^8.16.3",
+ "postgres": "^3.4.7",
"react": "^19.1.0",
"react-day-picker": "^9.8.1",
"react-dom": "^19.1.0",
@@ -82,12 +90,15 @@
"@tailwindcss/typography": "^0.5.16",
"@types/canvas-confetti": "^1.9.0",
"@types/node": "^20.19.9",
+ "@types/pg": "^8.15.5",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"cross-env": "^7.0.3",
+ "drizzle-kit": "^0.31.5",
"eslint": "^9.32.0",
"eslint-config-next": "15.2.0",
"tailwindcss": "^4.1.11",
+ "tsx": "^4.20.6",
"typescript": "^5.8.3"
}
}
diff --git a/surfsense_web/pnpm-lock.yaml b/surfsense_web/pnpm-lock.yaml
index 7d387d6fc..a62cd0a53 100644
--- a/surfsense_web/pnpm-lock.yaml
+++ b/surfsense_web/pnpm-lock.yaml
@@ -104,6 +104,12 @@ importers:
date-fns:
specifier: ^4.1.0
version: 4.1.0
+ dotenv:
+ specifier: ^17.2.3
+ version: 17.2.3
+ drizzle-orm:
+ specifier: ^0.44.5
+ version: 0.44.5(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(pg@8.16.3)(postgres@3.4.7)
emblor:
specifier: ^1.4.8
version: 1.4.8(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -134,6 +140,12 @@ importers:
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ pg:
+ specifier: ^8.16.3
+ version: 8.16.3
+ postgres:
+ specifier: ^3.4.7
+ version: 3.4.7
react:
specifier: ^19.1.0
version: 19.1.0
@@ -204,6 +216,9 @@ importers:
'@types/node':
specifier: ^20.19.9
version: 20.19.9
+ '@types/pg':
+ specifier: ^8.15.5
+ version: 8.15.5
'@types/react':
specifier: ^19.1.8
version: 19.1.8
@@ -213,6 +228,9 @@ importers:
cross-env:
specifier: ^7.0.3
version: 7.0.3
+ drizzle-kit:
+ specifier: ^0.31.5
+ version: 0.31.5
eslint:
specifier: ^9.32.0
version: 9.32.0(jiti@2.4.2)
@@ -222,6 +240,9 @@ importers:
tailwindcss:
specifier: ^4.1.11
version: 4.1.11
+ tsx:
+ specifier: ^4.20.6
+ version: 4.20.6
typescript:
specifier: ^5.8.3
version: 5.8.3
@@ -427,105 +448,212 @@ packages:
'@date-fns/tz@1.2.0':
resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
+ '@drizzle-team/brocli@0.10.2':
+ resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
+
'@emnapi/runtime@1.4.5':
resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
+ '@esbuild-kit/core-utils@3.3.2':
+ resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
+ deprecated: 'Merged into tsx: https://tsx.is'
+
+ '@esbuild-kit/esm-loader@2.6.5':
+ resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==}
+ deprecated: 'Merged into tsx: https://tsx.is'
+
'@esbuild/aix-ppc64@0.25.8':
resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
+ '@esbuild/android-arm64@0.18.20':
+ resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
'@esbuild/android-arm64@0.25.8':
resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
+ '@esbuild/android-arm@0.18.20':
+ resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+
'@esbuild/android-arm@0.25.8':
resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
+ '@esbuild/android-x64@0.18.20':
+ resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
'@esbuild/android-x64@0.25.8':
resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
+ '@esbuild/darwin-arm64@0.18.20':
+ resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
'@esbuild/darwin-arm64@0.25.8':
resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
+ '@esbuild/darwin-x64@0.18.20':
+ resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
'@esbuild/darwin-x64@0.25.8':
resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
+ '@esbuild/freebsd-arm64@0.18.20':
+ resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
'@esbuild/freebsd-arm64@0.25.8':
resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
+ '@esbuild/freebsd-x64@0.18.20':
+ resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
'@esbuild/freebsd-x64@0.25.8':
resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
+ '@esbuild/linux-arm64@0.18.20':
+ resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
'@esbuild/linux-arm64@0.25.8':
resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
+ '@esbuild/linux-arm@0.18.20':
+ resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
'@esbuild/linux-arm@0.25.8':
resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
+ '@esbuild/linux-ia32@0.18.20':
+ resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
'@esbuild/linux-ia32@0.25.8':
resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
+ '@esbuild/linux-loong64@0.18.20':
+ resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
'@esbuild/linux-loong64@0.25.8':
resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
+ '@esbuild/linux-mips64el@0.18.20':
+ resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
'@esbuild/linux-mips64el@0.25.8':
resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
+ '@esbuild/linux-ppc64@0.18.20':
+ resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
'@esbuild/linux-ppc64@0.25.8':
resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
+ '@esbuild/linux-riscv64@0.18.20':
+ resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
'@esbuild/linux-riscv64@0.25.8':
resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
+ '@esbuild/linux-s390x@0.18.20':
+ resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
'@esbuild/linux-s390x@0.25.8':
resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
+ '@esbuild/linux-x64@0.18.20':
+ resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
'@esbuild/linux-x64@0.25.8':
resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==}
engines: {node: '>=18'}
@@ -538,6 +666,12 @@ packages:
cpu: [arm64]
os: [netbsd]
+ '@esbuild/netbsd-x64@0.18.20':
+ resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
'@esbuild/netbsd-x64@0.25.8':
resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==}
engines: {node: '>=18'}
@@ -550,6 +684,12 @@ packages:
cpu: [arm64]
os: [openbsd]
+ '@esbuild/openbsd-x64@0.18.20':
+ resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
'@esbuild/openbsd-x64@0.25.8':
resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==}
engines: {node: '>=18'}
@@ -562,24 +702,48 @@ packages:
cpu: [arm64]
os: [openharmony]
+ '@esbuild/sunos-x64@0.18.20':
+ resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
'@esbuild/sunos-x64@0.25.8':
resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
+ '@esbuild/win32-arm64@0.18.20':
+ resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
'@esbuild/win32-arm64@0.25.8':
resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
+ '@esbuild/win32-ia32@0.18.20':
+ resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
'@esbuild/win32-ia32@0.25.8':
resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
+ '@esbuild/win32-x64@0.18.20':
+ resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
'@esbuild/win32-x64@0.25.8':
resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==}
engines: {node: '>=18'}
@@ -2169,6 +2333,9 @@ packages:
'@types/node@20.19.9':
resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==}
+ '@types/pg@8.15.5':
+ resolution: {integrity: sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==}
+
'@types/prop-types@15.7.15':
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
@@ -2413,6 +2580,9 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
+ buffer-from@1.1.2:
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
@@ -2677,11 +2847,111 @@ packages:
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
engines: {node: '>=12'}
+ dotenv@17.2.3:
+ resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
+ engines: {node: '>=12'}
+
downshift@7.6.2:
resolution: {integrity: sha512-iOv+E1Hyt3JDdL9yYcOgW7nZ7GQ2Uz6YbggwXvKUSleetYhU2nXD482Rz6CzvM4lvI1At34BYruKAL4swRGxaA==}
peerDependencies:
react: '>=16.12.0'
+ drizzle-kit@0.31.5:
+ resolution: {integrity: sha512-+CHgPFzuoTQTt7cOYCV6MOw2w8vqEn/ap1yv4bpZOWL03u7rlVRQhUY0WYT3rHsgVTXwYQDZaSUJSQrMBUKuWg==}
+ hasBin: true
+
+ drizzle-orm@0.44.5:
+ resolution: {integrity: sha512-jBe37K7d8ZSKptdKfakQFdeljtu3P2Cbo7tJoJSVZADzIKOBo9IAJPOmMsH2bZl90bZgh8FQlD8BjxXA/zuBkQ==}
+ peerDependencies:
+ '@aws-sdk/client-rds-data': '>=3'
+ '@cloudflare/workers-types': '>=4'
+ '@electric-sql/pglite': '>=0.2.0'
+ '@libsql/client': '>=0.10.0'
+ '@libsql/client-wasm': '>=0.10.0'
+ '@neondatabase/serverless': '>=0.10.0'
+ '@op-engineering/op-sqlite': '>=2'
+ '@opentelemetry/api': ^1.4.1
+ '@planetscale/database': '>=1.13'
+ '@prisma/client': '*'
+ '@tidbcloud/serverless': '*'
+ '@types/better-sqlite3': '*'
+ '@types/pg': '*'
+ '@types/sql.js': '*'
+ '@upstash/redis': '>=1.34.7'
+ '@vercel/postgres': '>=0.8.0'
+ '@xata.io/client': '*'
+ better-sqlite3: '>=7'
+ bun-types: '*'
+ expo-sqlite: '>=14.0.0'
+ gel: '>=2'
+ knex: '*'
+ kysely: '*'
+ mysql2: '>=2'
+ pg: '>=8'
+ postgres: '>=3'
+ prisma: '*'
+ sql.js: '>=1'
+ sqlite3: '>=5'
+ peerDependenciesMeta:
+ '@aws-sdk/client-rds-data':
+ optional: true
+ '@cloudflare/workers-types':
+ optional: true
+ '@electric-sql/pglite':
+ optional: true
+ '@libsql/client':
+ optional: true
+ '@libsql/client-wasm':
+ optional: true
+ '@neondatabase/serverless':
+ optional: true
+ '@op-engineering/op-sqlite':
+ optional: true
+ '@opentelemetry/api':
+ optional: true
+ '@planetscale/database':
+ optional: true
+ '@prisma/client':
+ optional: true
+ '@tidbcloud/serverless':
+ optional: true
+ '@types/better-sqlite3':
+ optional: true
+ '@types/pg':
+ optional: true
+ '@types/sql.js':
+ optional: true
+ '@upstash/redis':
+ optional: true
+ '@vercel/postgres':
+ optional: true
+ '@xata.io/client':
+ optional: true
+ better-sqlite3:
+ optional: true
+ bun-types:
+ optional: true
+ expo-sqlite:
+ optional: true
+ gel:
+ optional: true
+ knex:
+ optional: true
+ kysely:
+ optional: true
+ mysql2:
+ optional: true
+ pg:
+ optional: true
+ postgres:
+ optional: true
+ prisma:
+ optional: true
+ sql.js:
+ optional: true
+ sqlite3:
+ optional: true
+
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -2755,6 +3025,16 @@ packages:
esast-util-from-js@2.0.1:
resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==}
+ esbuild-register@3.6.0:
+ resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==}
+ peerDependencies:
+ esbuild: '>=0.12 <1'
+
+ esbuild@0.18.20:
+ resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
+ engines: {node: '>=12'}
+ hasBin: true
+
esbuild@0.25.8:
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
engines: {node: '>=18'}
@@ -3043,6 +3323,11 @@ packages:
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
fumadocs-core@15.6.6:
resolution: {integrity: sha512-90sUbejUDevfDHykXXudw+3xTqYjuSZU1evhJiRBiZ0Oy0xQX4p5zPO48b5dhuVp44osvOH0ZKfHsVdkor6kZQ==}
peerDependencies:
@@ -4201,6 +4486,40 @@ packages:
resolution: {integrity: sha512-IHZsA4T7YElCKNNXtiLgqScw4zPd3pG9do8UrznC757gMd7UPeHSL2qwNNMJo4r79fl8oj1Xx+1nh2YkzdMpLQ==}
engines: {node: '>=18'}
+ pg-cloudflare@1.2.7:
+ resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==}
+
+ pg-connection-string@2.9.1:
+ resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==}
+
+ pg-int8@1.0.1:
+ resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
+ engines: {node: '>=4.0.0'}
+
+ pg-pool@3.10.1:
+ resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==}
+ peerDependencies:
+ pg: '>=8.0'
+
+ pg-protocol@1.10.3:
+ resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==}
+
+ pg-types@2.2.0:
+ resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
+ engines: {node: '>=4'}
+
+ pg@8.16.3:
+ resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==}
+ engines: {node: '>= 16.0.0'}
+ peerDependencies:
+ pg-native: '>=3.0.1'
+ peerDependenciesMeta:
+ pg-native:
+ optional: true
+
+ pgpass@1.0.5:
+ resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
+
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -4236,6 +4555,26 @@ packages:
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
engines: {node: ^10 || ^12 || >=14}
+ postgres-array@2.0.0:
+ resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
+ engines: {node: '>=4'}
+
+ postgres-bytea@1.0.0:
+ resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
+ engines: {node: '>=0.10.0'}
+
+ postgres-date@1.0.7:
+ resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
+ engines: {node: '>=0.10.0'}
+
+ postgres-interval@1.2.0:
+ resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
+ engines: {node: '>=0.10.0'}
+
+ postgres@3.4.7:
+ resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==}
+ engines: {node: '>=12'}
+
prebuild-install@7.1.3:
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
engines: {node: '>=10'}
@@ -4687,6 +5026,13 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
+ source-map-support@0.5.21:
+ resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+
+ source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+
source-map@0.7.4:
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
engines: {node: '>= 8'}
@@ -4697,6 +5043,10 @@ packages:
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+ split2@4.2.0:
+ resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+ engines: {node: '>= 10.x'}
+
stable-hash@0.0.4:
resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==}
@@ -4873,6 +5223,11 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+ tsx@4.20.6:
+ resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
@@ -5507,86 +5862,164 @@ snapshots:
'@date-fns/tz@1.2.0': {}
+ '@drizzle-team/brocli@0.10.2': {}
+
'@emnapi/runtime@1.4.5':
dependencies:
tslib: 2.8.1
optional: true
+ '@esbuild-kit/core-utils@3.3.2':
+ dependencies:
+ esbuild: 0.18.20
+ source-map-support: 0.5.21
+
+ '@esbuild-kit/esm-loader@2.6.5':
+ dependencies:
+ '@esbuild-kit/core-utils': 3.3.2
+ get-tsconfig: 4.10.0
+
'@esbuild/aix-ppc64@0.25.8':
optional: true
+ '@esbuild/android-arm64@0.18.20':
+ optional: true
+
'@esbuild/android-arm64@0.25.8':
optional: true
+ '@esbuild/android-arm@0.18.20':
+ optional: true
+
'@esbuild/android-arm@0.25.8':
optional: true
+ '@esbuild/android-x64@0.18.20':
+ optional: true
+
'@esbuild/android-x64@0.25.8':
optional: true
+ '@esbuild/darwin-arm64@0.18.20':
+ optional: true
+
'@esbuild/darwin-arm64@0.25.8':
optional: true
+ '@esbuild/darwin-x64@0.18.20':
+ optional: true
+
'@esbuild/darwin-x64@0.25.8':
optional: true
+ '@esbuild/freebsd-arm64@0.18.20':
+ optional: true
+
'@esbuild/freebsd-arm64@0.25.8':
optional: true
+ '@esbuild/freebsd-x64@0.18.20':
+ optional: true
+
'@esbuild/freebsd-x64@0.25.8':
optional: true
+ '@esbuild/linux-arm64@0.18.20':
+ optional: true
+
'@esbuild/linux-arm64@0.25.8':
optional: true
+ '@esbuild/linux-arm@0.18.20':
+ optional: true
+
'@esbuild/linux-arm@0.25.8':
optional: true
+ '@esbuild/linux-ia32@0.18.20':
+ optional: true
+
'@esbuild/linux-ia32@0.25.8':
optional: true
+ '@esbuild/linux-loong64@0.18.20':
+ optional: true
+
'@esbuild/linux-loong64@0.25.8':
optional: true
+ '@esbuild/linux-mips64el@0.18.20':
+ optional: true
+
'@esbuild/linux-mips64el@0.25.8':
optional: true
+ '@esbuild/linux-ppc64@0.18.20':
+ optional: true
+
'@esbuild/linux-ppc64@0.25.8':
optional: true
+ '@esbuild/linux-riscv64@0.18.20':
+ optional: true
+
'@esbuild/linux-riscv64@0.25.8':
optional: true
+ '@esbuild/linux-s390x@0.18.20':
+ optional: true
+
'@esbuild/linux-s390x@0.25.8':
optional: true
+ '@esbuild/linux-x64@0.18.20':
+ optional: true
+
'@esbuild/linux-x64@0.25.8':
optional: true
'@esbuild/netbsd-arm64@0.25.8':
optional: true
+ '@esbuild/netbsd-x64@0.18.20':
+ optional: true
+
'@esbuild/netbsd-x64@0.25.8':
optional: true
'@esbuild/openbsd-arm64@0.25.8':
optional: true
+ '@esbuild/openbsd-x64@0.18.20':
+ optional: true
+
'@esbuild/openbsd-x64@0.25.8':
optional: true
'@esbuild/openharmony-arm64@0.25.8':
optional: true
+ '@esbuild/sunos-x64@0.18.20':
+ optional: true
+
'@esbuild/sunos-x64@0.25.8':
optional: true
+ '@esbuild/win32-arm64@0.18.20':
+ optional: true
+
'@esbuild/win32-arm64@0.25.8':
optional: true
+ '@esbuild/win32-ia32@0.18.20':
+ optional: true
+
'@esbuild/win32-ia32@0.25.8':
optional: true
+ '@esbuild/win32-x64@0.18.20':
+ optional: true
+
'@esbuild/win32-x64@0.25.8':
optional: true
@@ -7412,6 +7845,12 @@ snapshots:
dependencies:
undici-types: 6.21.0
+ '@types/pg@8.15.5':
+ dependencies:
+ '@types/node': 20.19.9
+ pg-protocol: 1.10.3
+ pg-types: 2.2.0
+
'@types/prop-types@15.7.15': {}
'@types/react-dom@19.1.6(@types/react@19.1.8)':
@@ -7712,6 +8151,8 @@ snapshots:
dependencies:
fill-range: 7.1.1
+ buffer-from@1.1.2: {}
+
buffer@5.7.1:
dependencies:
base64-js: 1.5.1
@@ -7960,6 +8401,8 @@ snapshots:
dotenv@16.6.1: {}
+ dotenv@17.2.3: {}
+
downshift@7.6.2(react@19.1.0):
dependencies:
'@babel/runtime': 7.26.9
@@ -7969,6 +8412,22 @@ snapshots:
react-is: 17.0.2
tslib: 2.8.1
+ drizzle-kit@0.31.5:
+ dependencies:
+ '@drizzle-team/brocli': 0.10.2
+ '@esbuild-kit/esm-loader': 2.6.5
+ esbuild: 0.25.8
+ esbuild-register: 3.6.0(esbuild@0.25.8)
+ transitivePeerDependencies:
+ - supports-color
+
+ drizzle-orm@0.44.5(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(pg@8.16.3)(postgres@3.4.7):
+ optionalDependencies:
+ '@opentelemetry/api': 1.9.0
+ '@types/pg': 8.15.5
+ pg: 8.16.3
+ postgres: 3.4.7
+
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -8135,6 +8594,38 @@ snapshots:
esast-util-from-estree: 2.0.0
vfile-message: 4.0.2
+ esbuild-register@3.6.0(esbuild@0.25.8):
+ dependencies:
+ debug: 4.4.0
+ esbuild: 0.25.8
+ transitivePeerDependencies:
+ - supports-color
+
+ esbuild@0.18.20:
+ optionalDependencies:
+ '@esbuild/android-arm': 0.18.20
+ '@esbuild/android-arm64': 0.18.20
+ '@esbuild/android-x64': 0.18.20
+ '@esbuild/darwin-arm64': 0.18.20
+ '@esbuild/darwin-x64': 0.18.20
+ '@esbuild/freebsd-arm64': 0.18.20
+ '@esbuild/freebsd-x64': 0.18.20
+ '@esbuild/linux-arm': 0.18.20
+ '@esbuild/linux-arm64': 0.18.20
+ '@esbuild/linux-ia32': 0.18.20
+ '@esbuild/linux-loong64': 0.18.20
+ '@esbuild/linux-mips64el': 0.18.20
+ '@esbuild/linux-ppc64': 0.18.20
+ '@esbuild/linux-riscv64': 0.18.20
+ '@esbuild/linux-s390x': 0.18.20
+ '@esbuild/linux-x64': 0.18.20
+ '@esbuild/netbsd-x64': 0.18.20
+ '@esbuild/openbsd-x64': 0.18.20
+ '@esbuild/sunos-x64': 0.18.20
+ '@esbuild/win32-arm64': 0.18.20
+ '@esbuild/win32-ia32': 0.18.20
+ '@esbuild/win32-x64': 0.18.20
+
esbuild@0.25.8:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.8
@@ -8547,6 +9038,9 @@ snapshots:
fs-constants@1.0.0:
optional: true
+ fsevents@2.3.3:
+ optional: true
+
fumadocs-core@15.6.6(@types/react@19.1.8)(next@15.4.4(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@formatjs/intl-localematcher': 0.6.1
@@ -10261,6 +10755,41 @@ snapshots:
canvas: 3.1.2
path2d: 0.2.2
+ pg-cloudflare@1.2.7:
+ optional: true
+
+ pg-connection-string@2.9.1: {}
+
+ pg-int8@1.0.1: {}
+
+ pg-pool@3.10.1(pg@8.16.3):
+ dependencies:
+ pg: 8.16.3
+
+ pg-protocol@1.10.3: {}
+
+ pg-types@2.2.0:
+ dependencies:
+ pg-int8: 1.0.1
+ postgres-array: 2.0.0
+ postgres-bytea: 1.0.0
+ postgres-date: 1.0.7
+ postgres-interval: 1.2.0
+
+ pg@8.16.3:
+ dependencies:
+ pg-connection-string: 2.9.1
+ pg-pool: 3.10.1(pg@8.16.3)
+ pg-protocol: 1.10.3
+ pg-types: 2.2.0
+ pgpass: 1.0.5
+ optionalDependencies:
+ pg-cloudflare: 1.2.7
+
+ pgpass@1.0.5:
+ dependencies:
+ split2: 4.2.0
+
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@@ -10293,6 +10822,18 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
+ postgres-array@2.0.0: {}
+
+ postgres-bytea@1.0.0: {}
+
+ postgres-date@1.0.7: {}
+
+ postgres-interval@1.2.0:
+ dependencies:
+ xtend: 4.0.2
+
+ postgres@3.4.7: {}
+
prebuild-install@7.1.3:
dependencies:
detect-libc: 2.0.3
@@ -10951,12 +11492,21 @@ snapshots:
source-map-js@1.2.1: {}
+ source-map-support@0.5.21:
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+
+ source-map@0.6.1: {}
+
source-map@0.7.4: {}
space-separated-tokens@1.1.5: {}
space-separated-tokens@2.0.2: {}
+ split2@4.2.0: {}
+
stable-hash@0.0.4: {}
static-browser-server@1.0.3:
@@ -11160,6 +11710,13 @@ snapshots:
tslib@2.8.1: {}
+ tsx@4.20.6:
+ dependencies:
+ esbuild: 0.25.8
+ get-tsconfig: 4.10.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
tunnel-agent@0.6.0:
dependencies:
safe-buffer: 5.2.1
From e305bc479603db693ef58e43abc4cd4172b180e8 Mon Sep 17 00:00:00 2001
From: "DESKTOP-RTLN3BA\\$punk"
Date: Tue, 30 Sep 2025 21:53:10 -0700
Subject: [PATCH 3/5] feat: contact to frontpage and biome
---
.../components/ModernHeroWithGradients.tsx | 9 +-
surfsense_web/components/Navbar.tsx | 21 +-
.../components/chat/AnimatedEmptyState.tsx | 1 -
.../components/contact/contact-form.tsx | 659 +++++++++---------
.../components/onboard/add-provider-step.tsx | 2 +-
.../components/onboard/assign-roles-step.tsx | 2 +-
.../components/onboard/completion-step.tsx | 2 +-
surfsense_web/components/pricing.tsx | 362 +++++-----
.../components/pricing/pricing-section.tsx | 126 ++--
.../components/search-space-form.tsx | 2 +-
.../components/settings/llm-role-manager.tsx | 2 +-
.../settings/model-config-manager.tsx | 2 +-
.../components/theme/theme-toggle.tsx | 2 +-
surfsense_web/components/ui/switch.tsx | 46 +-
14 files changed, 620 insertions(+), 618 deletions(-)
diff --git a/surfsense_web/components/ModernHeroWithGradients.tsx b/surfsense_web/components/ModernHeroWithGradients.tsx
index 5c81cf54b..543e1b4eb 100644
--- a/surfsense_web/components/ModernHeroWithGradients.tsx
+++ b/surfsense_web/components/ModernHeroWithGradients.tsx
@@ -1,5 +1,5 @@
"use client";
-import { IconBrandDiscord, IconBrandGithub, IconFileTypeDoc } from "@tabler/icons-react";
+import { IconBrandDiscord, IconBrandGithub, IconFileTypeDoc, IconMail } from "@tabler/icons-react";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { Logo } from "./Logo";
@@ -54,6 +54,13 @@ export function ModernHeroWithGradients() {
Notion, YouTube, GitHub, Discord and more.
+
+
+
Contact Us
+
{
duration: 0.2,
},
}}
+ className="flex items-center gap-2"
>
+
+
+
- Contact us
+ Contact
- We are always looking for ways to improve our products and services. Contact us and let us
- know how we can help you.
+ We'd love to Hear From You.
From 79429b0c9a34fe36cbb36357e82134e2224897ce Mon Sep 17 00:00:00 2001
From: "DESKTOP-RTLN3BA\\$punk"
Date: Tue, 30 Sep 2025 22:26:32 -0700
Subject: [PATCH 5/5] remove framer-motion
---
surfsense_web/components/pricing.tsx | 2 +-
surfsense_web/package.json | 1 -
surfsense_web/pnpm-lock.yaml | 3 ---
3 files changed, 1 insertion(+), 5 deletions(-)
diff --git a/surfsense_web/components/pricing.tsx b/surfsense_web/components/pricing.tsx
index 3d19e1ea9..b666c8982 100644
--- a/surfsense_web/components/pricing.tsx
+++ b/surfsense_web/components/pricing.tsx
@@ -2,7 +2,7 @@
import NumberFlow from "@number-flow/react";
import confetti from "canvas-confetti";
-import { motion } from "framer-motion";
+import { motion } from "motion/react";
import { Check, Star } from "lucide-react";
import Link from "next/link";
import { useRef, useState } from "react";
diff --git a/surfsense_web/package.json b/surfsense_web/package.json
index b17dfd626..517e7690d 100644
--- a/surfsense_web/package.json
+++ b/surfsense_web/package.json
@@ -54,7 +54,6 @@
"dotenv": "^17.2.3",
"drizzle-orm": "^0.44.5",
"emblor": "^1.4.8",
- "framer-motion": "^12.23.22",
"fumadocs-core": "^15.6.6",
"fumadocs-mdx": "^11.7.1",
"fumadocs-ui": "^15.6.6",
diff --git a/surfsense_web/pnpm-lock.yaml b/surfsense_web/pnpm-lock.yaml
index a62cd0a53..bfda2d29d 100644
--- a/surfsense_web/pnpm-lock.yaml
+++ b/surfsense_web/pnpm-lock.yaml
@@ -113,9 +113,6 @@ importers:
emblor:
specifier: ^1.4.8
version: 1.4.8(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- framer-motion:
- specifier: ^12.23.22
- version: 12.23.22(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
fumadocs-core:
specifier: ^15.6.6
version: 15.6.6(@types/react@19.1.8)(next@15.4.4(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)