a
This commit is contained in:
@@ -1,53 +1,220 @@
|
||||
"use client"
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
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="flex flex-1 flex-col gap-6 p-4 lg:p-8">
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-2xl font-semibold">Settings</h1>
|
||||
<p className="text-muted-foreground">Manage account details and API configuration.</p>
|
||||
<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="grid gap-4 lg:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Account</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 text-sm text-muted-foreground">
|
||||
<p>Signed in with Convex Auth.</p>
|
||||
<p>Profile management can be added here in a later phase.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">API Keys</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium text-foreground">OpenAI</p>
|
||||
<p>Set `OPENAI_API_KEY` in your `.env` file.</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium text-foreground">Serper (optional)</p>
|
||||
<p>Set `SERPER_API_KEY` in your `.env` file for more reliable search.</p>
|
||||
</div>
|
||||
<Badge variant="outline">Reload server after changes</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<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>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Billing</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm text-muted-foreground">
|
||||
Billing is not configured yet. Add Stripe or another provider when ready.
|
||||
</CardContent>
|
||||
</Card>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user