221 lines
9.9 KiB
TypeScript
221 lines
9.9 KiB
TypeScript
"use client"
|
|
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Separator } from "@/components/ui/separator"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
import { useRouter, useSearchParams } from "next/navigation"
|
|
import Link from "next/link"
|
|
import * as React from "react"
|
|
import { useQuery } from "convex/react"
|
|
import { api } from "@/convex/_generated/api"
|
|
|
|
export default function SettingsPage() {
|
|
const router = useRouter()
|
|
const searchParams = useSearchParams()
|
|
const profile = useQuery(api.users.getCurrentProfile)
|
|
const user = profile?.user
|
|
const accounts = profile?.accounts ?? []
|
|
const allowedTabs = React.useMemo(() => ["account", "billing"], [])
|
|
const queryTab = searchParams.get("tab")
|
|
const initialTab = allowedTabs.includes(queryTab ?? "") ? (queryTab as string) : "account"
|
|
const [tab, setTab] = React.useState(initialTab)
|
|
|
|
React.useEffect(() => {
|
|
if (initialTab !== tab) {
|
|
setTab(initialTab)
|
|
}
|
|
}, [initialTab, tab])
|
|
|
|
const checkoutHref = "/api/checkout"
|
|
|
|
return (
|
|
<div className="relative flex flex-1 flex-col gap-6 overflow-hidden p-4 lg:p-8">
|
|
<div className="pointer-events-none absolute inset-0">
|
|
<div className="absolute -left-40 top-20 h-72 w-72 rounded-full bg-[radial-gradient(circle_at_center,rgba(251,191,36,0.18),transparent_65%)] blur-2xl" />
|
|
<div className="absolute right-10 top-10 h-64 w-64 rounded-full bg-[radial-gradient(circle_at_center,rgba(16,185,129,0.16),transparent_65%)] blur-2xl" />
|
|
<div className="absolute bottom-0 left-1/3 h-64 w-96 rounded-full bg-[radial-gradient(circle_at_center,rgba(56,189,248,0.14),transparent_70%)] blur-3xl" />
|
|
</div>
|
|
|
|
<div className="relative space-y-2">
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
<Badge variant="outline">Account Center</Badge>
|
|
<Badge className="bg-emerald-500/10 text-emerald-300 hover:bg-emerald-500/20">Active</Badge>
|
|
</div>
|
|
<h1 className="text-2xl font-semibold">Account & Billing</h1>
|
|
<p className="text-muted-foreground">
|
|
Manage your subscription, billing, and account details in one place.
|
|
</p>
|
|
</div>
|
|
|
|
<Tabs
|
|
value={tab}
|
|
onValueChange={(value) => {
|
|
setTab(value)
|
|
router.replace(`/settings?tab=${value}`)
|
|
}}
|
|
className="relative"
|
|
>
|
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
<TabsList className="bg-muted/60">
|
|
<TabsTrigger value="account">Account</TabsTrigger>
|
|
<TabsTrigger value="billing">Billing</TabsTrigger>
|
|
</TabsList>
|
|
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
<span>Current plan</span>
|
|
<span className="text-foreground">Starter</span>
|
|
</div>
|
|
</div>
|
|
|
|
<TabsContent value="account">
|
|
<div className="grid gap-4 lg:grid-cols-3">
|
|
<Card className="lg:col-span-2 border-border/60 bg-card/70 backdrop-blur">
|
|
<CardHeader>
|
|
<CardTitle className="text-base">Profile</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4 text-sm text-muted-foreground">
|
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
<div>
|
|
<p className="text-foreground font-medium">{user?.name || user?.email || "Not provided"}</p>
|
|
<p>{user?.email || "Not provided"}</p>
|
|
</div>
|
|
{/* TODO: Wire profile editing flow. */}
|
|
<Button variant="secondary">Coming soon.</Button>
|
|
</div>
|
|
<Separator />
|
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
<div className="space-y-1">
|
|
<p className="text-foreground font-medium">Name</p>
|
|
<p>{user?.name || "Not provided"}</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<p className="text-foreground font-medium">Email</p>
|
|
<p>{user?.email || "Not provided"}</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<p className="text-foreground font-medium">Phone</p>
|
|
<p>{user?.phone || "Not provided"}</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<p className="text-foreground font-medium">Sign-in Methods</p>
|
|
<p>
|
|
{accounts.length > 0
|
|
? Array.from(new Set(accounts.map((account) => {
|
|
if (account.provider === "password") return "Password";
|
|
if (account.provider === "google") return "Google";
|
|
return account.provider;
|
|
}))).join(", ")
|
|
: "Not provided"}
|
|
</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<p className="text-foreground font-medium">Email Verified</p>
|
|
<p>
|
|
{user?.emailVerificationTime
|
|
? new Date(user.emailVerificationTime).toLocaleDateString()
|
|
: "Not verified"}
|
|
</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<p className="text-foreground font-medium">User ID</p>
|
|
<p className="break-all">{user?._id || "Not provided"}</p>
|
|
</div>
|
|
</div>
|
|
{/* TODO: Wire security management flow. */}
|
|
<Button className="w-full sm:w-auto">Coming soon.</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-border/60 bg-card/70 backdrop-blur">
|
|
<CardHeader>
|
|
<CardTitle className="text-base">Integrations</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
{/* TODO: Replace with real provider status. */}
|
|
<p className="text-foreground font-medium">Coming soon.</p>
|
|
<p>Coming soon.</p>
|
|
</div>
|
|
<Badge variant="outline">Linked</Badge>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
{/* TODO: Replace with real provider status. */}
|
|
<p className="text-foreground font-medium">Coming soon.</p>
|
|
<p>Coming soon.</p>
|
|
</div>
|
|
{/* TODO: Wire provider disconnect. */}
|
|
<Button variant="ghost" size="sm">Coming soon.</Button>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
{/* TODO: Replace with real provider status. */}
|
|
<p className="text-foreground font-medium">Coming soon.</p>
|
|
<p>Coming soon.</p>
|
|
</div>
|
|
{/* TODO: Wire provider connect. */}
|
|
<Button variant="outline" size="sm">Coming soon.</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="billing">
|
|
<div className="grid gap-4 lg:grid-cols-3">
|
|
<Card className="lg:col-span-2 border-border/60 bg-card/70 backdrop-blur">
|
|
<CardHeader>
|
|
<CardTitle className="text-base">Plan</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4 text-sm text-muted-foreground">
|
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
<div>
|
|
<p className="text-foreground font-medium">Starter</p>
|
|
<p>Upgrade to unlock full opportunity search and automation.</p>
|
|
</div>
|
|
<Button asChild>
|
|
<Link href={checkoutHref}>Subscribe to Pro</Link>
|
|
</Button>
|
|
</div>
|
|
<Separator />
|
|
<div className="space-y-2">
|
|
<p className="text-foreground font-medium">Pro includes</p>
|
|
<div className="grid gap-1 text-sm text-muted-foreground">
|
|
<p>Unlimited projects and data sources</p>
|
|
<p>Advanced opportunity search</p>
|
|
<p>Priority analysis queue</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-border/60 bg-card/70 backdrop-blur">
|
|
<CardHeader>
|
|
<CardTitle className="text-base">Billing History</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-foreground font-medium">No invoices yet</p>
|
|
<p>Invoices will appear after your first payment.</p>
|
|
</div>
|
|
<Badge variant="outline">Pro</Badge>
|
|
</div>
|
|
<div className="rounded-lg border border-border/60 bg-muted/40 p-3 text-xs text-muted-foreground">
|
|
Need a receipt? Complete checkout to generate your first invoice.
|
|
</div>
|
|
<Button asChild className="w-full" variant="secondary">
|
|
<Link href={checkoutHref}>Subscribe to Pro</Link>
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
</Tabs>
|
|
</div>
|
|
)
|
|
}
|