diff --git a/app/auth/callback/route.ts b/app/auth/callback/route.ts
new file mode 100644
index 0000000..d3c85a9
--- /dev/null
+++ b/app/auth/callback/route.ts
@@ -0,0 +1,19 @@
+import { createClient } from '@/lib/supabase/server'
+import { NextResponse } from 'next/server'
+
+export async function GET(request: Request) {
+ const { searchParams, origin } = new URL(request.url)
+ const code = searchParams.get('code')
+ const next = searchParams.get('next') ?? '/dashboard'
+
+ if (code) {
+ const supabase = await createClient()
+ const { error } = await supabase.auth.exchangeCodeForSession(code)
+ if (!error) {
+ return NextResponse.redirect(`${origin}${next}`)
+ }
+ }
+
+ // Return the user to an error page with instructions
+ return NextResponse.redirect(`${origin}/auth/error`)
+}
diff --git a/app/auth/error/page.tsx b/app/auth/error/page.tsx
new file mode 100644
index 0000000..48b279c
--- /dev/null
+++ b/app/auth/error/page.tsx
@@ -0,0 +1,55 @@
+import Link from 'next/link'
+import { AlertCircle, ArrowLeft } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+
+export default function AuthErrorPage() {
+ return (
+
+ {/* Background elements */}
+
+
+
+ {/* Back to home */}
+
+
+ Back to home
+
+
+ {/* Card */}
+
+ {/* Icon */}
+
+
+ {/* Content */}
+
Authentication Error
+
+ Something went wrong during authentication. This could be due to an expired link or a configuration issue.
+
+
+ {/* Actions */}
+
+
+
+ If the problem persists, please{' '}
+
+ contact us
+
+
+
+
+
+
+ )
+}
diff --git a/app/auth/login/page.tsx b/app/auth/login/page.tsx
new file mode 100644
index 0000000..67b76d3
--- /dev/null
+++ b/app/auth/login/page.tsx
@@ -0,0 +1,206 @@
+'use client'
+
+import { createClient } from '@/lib/supabase/client'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import Link from 'next/link'
+import { useRouter } from 'next/navigation'
+import { useState } from 'react'
+import { Mail, Lock, Loader2, ArrowLeft } from 'lucide-react'
+
+export default function LoginPage() {
+ const [email, setEmail] = useState('')
+ const [password, setPassword] = useState('')
+ const [error, setError] = useState(null)
+ const [isLoading, setIsLoading] = useState(false)
+ const [isGoogleLoading, setIsGoogleLoading] = useState(false)
+ const router = useRouter()
+
+ const handleLogin = async (e: React.FormEvent) => {
+ e.preventDefault()
+ const supabase = createClient()
+ setIsLoading(true)
+ setError(null)
+
+ try {
+ const { error } = await supabase.auth.signInWithPassword({
+ email,
+ password,
+ })
+ if (error) throw error
+ router.push('/dashboard')
+ } catch (error: unknown) {
+ setError(error instanceof Error ? error.message : 'An error occurred')
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const handleGoogleLogin = async () => {
+ const supabase = createClient()
+ setIsGoogleLoading(true)
+ setError(null)
+
+ try {
+ const { error } = await supabase.auth.signInWithOAuth({
+ provider: 'google',
+ options: {
+ redirectTo: `${window.location.origin}/auth/callback`,
+ },
+ })
+ if (error) throw error
+ } catch (error: unknown) {
+ setError(error instanceof Error ? error.message : 'An error occurred')
+ setIsGoogleLoading(false)
+ }
+ }
+
+ return (
+
+ {/* Background elements */}
+
+
+
+ {/* Back to home */}
+
+
+ Back to home
+
+
+ {/* Card */}
+
+ {/* Header */}
+
+
+
+ Cloudrite
+
+
+
Welcome back
+
+ Sign in to your account to continue
+
+
+
+ {/* Google OAuth Button */}
+
+
+ {/* Divider */}
+
+
+
+
+ Or continue with email
+
+
+
+
+ {/* Email/Password Form */}
+
+
+ {/* Footer */}
+
+ Don't have an account?{' '}
+
+ Sign up
+
+
+
+
+
+ )
+}
diff --git a/app/auth/sign-up-success/page.tsx b/app/auth/sign-up-success/page.tsx
new file mode 100644
index 0000000..6f04e44
--- /dev/null
+++ b/app/auth/sign-up-success/page.tsx
@@ -0,0 +1,55 @@
+import Link from 'next/link'
+import { Mail, ArrowLeft } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+
+export default function SignUpSuccessPage() {
+ return (
+
+ {/* Background elements */}
+
+
+
+ {/* Back to home */}
+
+
+ Back to home
+
+
+ {/* Card */}
+
+ {/* Icon */}
+
+
+
+
+ {/* Content */}
+
Check your email
+
+ We've sent you a confirmation link. Please check your email and click the link to verify your account.
+
+
+ {/* Actions */}
+
+
+
+ Didn't receive the email? Check your spam folder or{' '}
+
+ try again
+
+
+
+
+
+
+ )
+}
diff --git a/app/auth/sign-up/page.tsx b/app/auth/sign-up/page.tsx
new file mode 100644
index 0000000..964f60d
--- /dev/null
+++ b/app/auth/sign-up/page.tsx
@@ -0,0 +1,233 @@
+'use client'
+
+import { createClient } from '@/lib/supabase/client'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import Link from 'next/link'
+import { useRouter } from 'next/navigation'
+import { useState } from 'react'
+import { Mail, Lock, Loader2, ArrowLeft, User } from 'lucide-react'
+
+export default function SignUpPage() {
+ const [name, setName] = useState('')
+ const [email, setEmail] = useState('')
+ const [password, setPassword] = useState('')
+ const [error, setError] = useState(null)
+ const [isLoading, setIsLoading] = useState(false)
+ const [isGoogleLoading, setIsGoogleLoading] = useState(false)
+ const router = useRouter()
+
+ const handleSignUp = async (e: React.FormEvent) => {
+ e.preventDefault()
+ const supabase = createClient()
+ setIsLoading(true)
+ setError(null)
+
+ try {
+ const { error } = await supabase.auth.signUp({
+ email,
+ password,
+ options: {
+ emailRedirectTo: `${window.location.origin}/auth/callback`,
+ data: {
+ full_name: name,
+ },
+ },
+ })
+ if (error) throw error
+ router.push('/auth/sign-up-success')
+ } catch (error: unknown) {
+ setError(error instanceof Error ? error.message : 'An error occurred')
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const handleGoogleSignUp = async () => {
+ const supabase = createClient()
+ setIsGoogleLoading(true)
+ setError(null)
+
+ try {
+ const { error } = await supabase.auth.signInWithOAuth({
+ provider: 'google',
+ options: {
+ redirectTo: `${window.location.origin}/auth/callback`,
+ },
+ })
+ if (error) throw error
+ } catch (error: unknown) {
+ setError(error instanceof Error ? error.message : 'An error occurred')
+ setIsGoogleLoading(false)
+ }
+ }
+
+ return (
+
+ {/* Background elements */}
+
+
+
+ {/* Back to home */}
+
+
+ Back to home
+
+
+ {/* Card */}
+
+ {/* Header */}
+
+
+
+ Cloudrite
+
+
+
Create an account
+
+ Get started with Cloudrite today
+
+
+
+ {/* Google OAuth Button */}
+
+
+ {/* Divider */}
+
+
+
+
+ Or continue with email
+
+
+
+
+ {/* Email/Password Form */}
+
+
+ {/* Footer */}
+
+ Already have an account?{' '}
+
+ Sign in
+
+
+
+
+
+ )
+}
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
new file mode 100644
index 0000000..74eb952
--- /dev/null
+++ b/app/dashboard/page.tsx
@@ -0,0 +1,134 @@
+import { createClient } from '@/lib/supabase/server'
+import { redirect } from 'next/navigation'
+import Link from 'next/link'
+import { LogOut, User, ArrowLeft, Cloud, Globe, Wrench } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+
+async function signOut() {
+ 'use server'
+ const supabase = await createClient()
+ await supabase.auth.signOut()
+ redirect('/')
+}
+
+export default async function DashboardPage() {
+ const supabase = await createClient()
+ const { data: { user } } = await supabase.auth.getUser()
+
+ if (!user) {
+ redirect('/auth/login')
+ }
+
+ const userName = user.user_metadata?.full_name || user.email?.split('@')[0] || 'User'
+
+ return (
+
+ {/* Header */}
+
+
+
+
+ Cloudrite
+
+
+
+
+
+
+
+ {/* Main content */}
+
+ {/* Welcome section */}
+
+
+
+ Back to home
+
+
+ Welcome back, {userName}
+
+
+ Your Cloudrite dashboard is coming soon. We're working on exciting features for you.
+
+
+
+ {/* Service cards */}
+
+
+
+
+
+
Web Development
+
+ Manage your websites, view analytics, and request updates.
+
+
Coming Soon
+
+
+
+
+
+
+
Cloud Hosting
+
+ Monitor your hosting, check uptime, and manage your servers.
+
+
Coming Soon
+
+
+
+
+
+
+
I.T Support
+
+ Submit support tickets and track the status of your requests.
+
+
Coming Soon
+
+
+
+ {/* Account info */}
+
+
Account Information
+
+
+ Email
+ {user.email}
+
+
+ Account created
+
+ {new Date(user.created_at).toLocaleDateString('en-NZ', {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric'
+ })}
+
+
+
+ Auth provider
+
+ {user.app_metadata?.provider || 'Email'}
+
+
+
+
+
+
+ )
+}
diff --git a/components/header.tsx b/components/header.tsx
index bd655d7..3721967 100644
--- a/components/header.tsx
+++ b/components/header.tsx
@@ -2,12 +2,16 @@
import { useState, useEffect } from "react"
import Link from "next/link"
-import { Menu, X, Cloud } from "lucide-react"
+import { Menu, X, Cloud, User, LogOut } from "lucide-react"
import { Button } from "@/components/ui/button"
+import { createClient } from "@/lib/supabase/client"
+import type { User as SupabaseUser } from "@supabase/supabase-js"
export function Header() {
const [isScrolled, setIsScrolled] = useState(false)
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
+ const [user, setUser] = useState(null)
+ const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const handleScroll = () => {
@@ -17,6 +21,29 @@ export function Header() {
return () => window.removeEventListener("scroll", handleScroll)
}, [])
+ useEffect(() => {
+ const supabase = createClient()
+
+ // Get initial session
+ supabase.auth.getUser().then(({ data: { user } }) => {
+ setUser(user)
+ setIsLoading(false)
+ })
+
+ // Listen for auth changes
+ const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
+ setUser(session?.user ?? null)
+ })
+
+ return () => subscription.unsubscribe()
+ }, [])
+
+ const handleSignOut = async () => {
+ const supabase = createClient()
+ await supabase.auth.signOut()
+ setUser(null)
+ }
+
const navLinks = [
{ href: "#services", label: "Services" },
{ href: "#features", label: "Why Us" },
@@ -59,14 +86,49 @@ export function Header() {
))}
- {/* CTA Button */}
-
-
+ {/* CTA Button / Auth */}
+
+ {isLoading ? (
+
+ ) : user ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
{/* Mobile Menu Button */}
@@ -109,14 +171,48 @@ export function Header() {
{link.label}
))}
-
+ {user ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
diff --git a/lib/supabase/client.ts b/lib/supabase/client.ts
new file mode 100644
index 0000000..c48a435
--- /dev/null
+++ b/lib/supabase/client.ts
@@ -0,0 +1,8 @@
+import { createBrowserClient } from '@supabase/ssr'
+
+export function createClient() {
+ return createBrowserClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ )
+}
diff --git a/lib/supabase/middleware.ts b/lib/supabase/middleware.ts
new file mode 100644
index 0000000..d09ab14
--- /dev/null
+++ b/lib/supabase/middleware.ts
@@ -0,0 +1,77 @@
+import { createServerClient } from '@supabase/ssr'
+import { NextResponse, type NextRequest } from 'next/server'
+
+export async function updateSession(request: NextRequest) {
+ let supabaseResponse = NextResponse.next({
+ request,
+ })
+
+ // With Fluid compute, don't put this client in a global environment
+ // variable. Always create a new one on each request.
+ const supabase = createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() {
+ return request.cookies.getAll()
+ },
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value }) =>
+ request.cookies.set(name, value),
+ )
+ supabaseResponse = NextResponse.next({
+ request,
+ })
+ cookiesToSet.forEach(({ name, value, options }) =>
+ supabaseResponse.cookies.set(name, value, options),
+ )
+ },
+ },
+ },
+ )
+
+ // Do not run code between createServerClient and
+ // supabase.auth.getUser(). A simple mistake could make it very hard to debug
+ // issues with users being randomly logged out.
+
+ // IMPORTANT: If you remove getUser() and you use server-side rendering
+ // with the Supabase client, your users may be randomly logged out.
+ const {
+ data: { user },
+ } = await supabase.auth.getUser()
+
+ // Protected routes - redirect to login if not authenticated
+ const protectedRoutes = ['/protected', '/dashboard']
+ const isProtectedRoute = protectedRoutes.some(route =>
+ request.nextUrl.pathname.startsWith(route)
+ )
+
+ if (isProtectedRoute && !user) {
+ const url = request.nextUrl.clone()
+ url.pathname = '/auth/login'
+ return NextResponse.redirect(url)
+ }
+
+ // Redirect authenticated users away from auth pages
+ if (user && request.nextUrl.pathname.startsWith('/auth/')) {
+ const url = request.nextUrl.clone()
+ url.pathname = '/dashboard'
+ return NextResponse.redirect(url)
+ }
+
+ // IMPORTANT: You *must* return the supabaseResponse object as it is.
+ // If you're creating a new response object with NextResponse.next() make sure to:
+ // 1. Pass the request in it, like so:
+ // const myNewResponse = NextResponse.next({ request })
+ // 2. Copy over the cookies, like so:
+ // myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
+ // 3. Change the myNewResponse object to fit your needs, but avoid changing
+ // the cookies!
+ // 4. Finally:
+ // return myNewResponse
+ // If this is not done, you may be causing the browser and server to go out
+ // of sync and terminate the user's session prematurely!
+
+ return supabaseResponse
+}
diff --git a/lib/supabase/server.ts b/lib/supabase/server.ts
new file mode 100644
index 0000000..a249098
--- /dev/null
+++ b/lib/supabase/server.ts
@@ -0,0 +1,34 @@
+import { createServerClient } from '@supabase/ssr'
+import { cookies } from 'next/headers'
+
+/**
+ * Especially important if using Fluid compute: Don't put this client in a
+ * global variable. Always create a new client within each function when using
+ * it.
+ */
+export async function createClient() {
+ const cookieStore = await cookies()
+
+ return createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() {
+ return cookieStore.getAll()
+ },
+ setAll(cookiesToSet) {
+ try {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ cookieStore.set(name, value, options),
+ )
+ } catch {
+ // The "setAll" method was called from a Server Component.
+ // This can be ignored if you have middleware refreshing
+ // user sessions.
+ }
+ },
+ },
+ },
+ )
+}
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 0000000..de79b4c
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,20 @@
+import { updateSession } from '@/lib/supabase/middleware'
+import { type NextRequest } from 'next/server'
+
+export async function middleware(request: NextRequest) {
+ return await updateSession(request)
+}
+
+export const config = {
+ matcher: [
+ /*
+ * Match all request paths except:
+ * - _next/static (static files)
+ * - _next/image (image optimization files)
+ * - favicon.ico (favicon file)
+ * - images - .svg, .png, .jpg, .jpeg, .gif, .webp
+ * Feel free to modify this pattern to include more paths.
+ */
+ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
+ ],
+}
diff --git a/package.json b/package.json
index 7e43b1b..b3c65f9 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,6 @@
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
- "@vercel/analytics": "1.6.1",
"@radix-ui/react-accordion": "1.2.12",
"@radix-ui/react-alert-dialog": "1.1.15",
"@radix-ui/react-aspect-ratio": "1.1.8",
@@ -38,6 +37,9 @@
"@radix-ui/react-toggle": "1.1.10",
"@radix-ui/react-toggle-group": "1.1.11",
"@radix-ui/react-tooltip": "1.2.8",
+ "@supabase/ssr": "^0.9.0",
+ "@supabase/supabase-js": "^2.99.3",
+ "@vercel/analytics": "1.6.1",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 61eeab2..4a17b65 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -92,6 +92,12 @@ importers:
'@radix-ui/react-tooltip':
specifier: 1.2.8
version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@supabase/ssr':
+ specifier: ^0.9.0
+ version: 0.9.0(@supabase/supabase-js@2.99.3)
+ '@supabase/supabase-js':
+ specifier: ^2.99.3
+ version: 2.99.3
'@vercel/analytics':
specifier: 1.6.1
version: 1.6.1(next@16.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
@@ -1097,6 +1103,35 @@ packages:
'@radix-ui/rect@1.1.1':
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
+ '@supabase/auth-js@2.99.3':
+ resolution: {integrity: sha512-vMEVLA1kGGYd/kdsJSwtjiFUZM1nGfrz2DWmgMBZtocV48qL+L2+4QpIkueXyBEumMQZFEyhz57i/5zGHjvdBw==}
+ engines: {node: '>=20.0.0'}
+
+ '@supabase/functions-js@2.99.3':
+ resolution: {integrity: sha512-6tk2zrcBkzKaaBXPOG5nshn30uJNFGOH9LxOnE8i850eQmsX+jVm7vql9kTPyvUzEHwU4zdjSOkXS9M+9ukMVA==}
+ engines: {node: '>=20.0.0'}
+
+ '@supabase/postgrest-js@2.99.3':
+ resolution: {integrity: sha512-8HxEf+zNycj7Z8+ONhhlu+7J7Ha+L6weyCtdEeK2mN5OWJbh6n4LPU4iuJ5UlCvvNnbSXMoutY7piITEEAgl2g==}
+ engines: {node: '>=20.0.0'}
+
+ '@supabase/realtime-js@2.99.3':
+ resolution: {integrity: sha512-c1azgZ2nZPczbY5k5u5iFrk1InpxN81IvNE+UBAkjrBz3yc5ALLJNkeTQwbJZT4PZBuYXEzqYGLMuh9fdTtTMg==}
+ engines: {node: '>=20.0.0'}
+
+ '@supabase/ssr@0.9.0':
+ resolution: {integrity: sha512-UFY6otYV3yqCgV+AyHj80vNkTvbf1Gas2LW4dpbQ4ap6p6v3eB2oaDfcI99jsuJzwVBCFU4BJI+oDYyhNk1z0Q==}
+ peerDependencies:
+ '@supabase/supabase-js': ^2.97.0
+
+ '@supabase/storage-js@2.99.3':
+ resolution: {integrity: sha512-lOfIm4hInNcd8x0i1LWphnLKxec42wwbjs+vhaVAvR801Vda0UAMbTooUY6gfqgQb8v29GofqKuQMMTAsl6w/w==}
+ engines: {node: '>=20.0.0'}
+
+ '@supabase/supabase-js@2.99.3':
+ resolution: {integrity: sha512-GuPbzoEaI51AkLw9VGhLNvnzw4PHbS3p8j2/JlvLeZNQMKwZw4aEYQIDBRtFwL5Nv7/275n9m4DHtakY8nCvgg==}
+ engines: {node: '>=20.0.0'}
+
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
@@ -1222,6 +1257,9 @@ packages:
'@types/node@22.19.15':
resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
+ '@types/phoenix@1.6.7':
+ resolution: {integrity: sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==}
+
'@types/react-dom@19.2.3':
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
peerDependencies:
@@ -1230,6 +1268,9 @@ packages:
'@types/react@19.2.14':
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
+ '@types/ws@8.18.1':
+ resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
+
'@vercel/analytics@1.6.1':
resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==}
peerDependencies:
@@ -1296,6 +1337,10 @@ packages:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
+ cookie@1.1.1:
+ resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
+ engines: {node: '>=18'}
+
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
@@ -1403,6 +1448,10 @@ packages:
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+ iceberg-js@0.8.1:
+ resolution: {integrity: sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==}
+ engines: {node: '>=20.0.0'}
+
input-otp@1.4.2:
resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==}
peerDependencies:
@@ -1752,6 +1801,18 @@ packages:
victory-vendor@36.9.2:
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
+ ws@8.20.0:
+ resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
@@ -2626,6 +2687,49 @@ snapshots:
'@radix-ui/rect@1.1.1': {}
+ '@supabase/auth-js@2.99.3':
+ dependencies:
+ tslib: 2.8.1
+
+ '@supabase/functions-js@2.99.3':
+ dependencies:
+ tslib: 2.8.1
+
+ '@supabase/postgrest-js@2.99.3':
+ dependencies:
+ tslib: 2.8.1
+
+ '@supabase/realtime-js@2.99.3':
+ dependencies:
+ '@types/phoenix': 1.6.7
+ '@types/ws': 8.18.1
+ tslib: 2.8.1
+ ws: 8.20.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ '@supabase/ssr@0.9.0(@supabase/supabase-js@2.99.3)':
+ dependencies:
+ '@supabase/supabase-js': 2.99.3
+ cookie: 1.1.1
+
+ '@supabase/storage-js@2.99.3':
+ dependencies:
+ iceberg-js: 0.8.1
+ tslib: 2.8.1
+
+ '@supabase/supabase-js@2.99.3':
+ dependencies:
+ '@supabase/auth-js': 2.99.3
+ '@supabase/functions-js': 2.99.3
+ '@supabase/postgrest-js': 2.99.3
+ '@supabase/realtime-js': 2.99.3
+ '@supabase/storage-js': 2.99.3
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
@@ -2727,6 +2831,8 @@ snapshots:
dependencies:
undici-types: 6.21.0
+ '@types/phoenix@1.6.7': {}
+
'@types/react-dom@19.2.3(@types/react@19.2.14)':
dependencies:
'@types/react': 19.2.14
@@ -2735,6 +2841,10 @@ snapshots:
dependencies:
csstype: 3.2.3
+ '@types/ws@8.18.1':
+ dependencies:
+ '@types/node': 22.19.15
+
'@vercel/analytics@1.6.1(next@16.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)':
optionalDependencies:
next: 16.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -2785,6 +2895,8 @@ snapshots:
- '@types/react'
- '@types/react-dom'
+ cookie@1.1.1: {}
+
csstype@3.2.3: {}
d3-array@3.2.4:
@@ -2871,6 +2983,8 @@ snapshots:
graceful-fs@4.2.11: {}
+ iceberg-js@0.8.1: {}
+
input-otp@1.4.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies:
react: 19.2.4
@@ -3206,4 +3320,6 @@ snapshots:
d3-time: 3.1.0
d3-timer: 3.0.1
+ ws@8.20.0: {}
+
zod@3.25.76: {}