LAYOUT CONSISTENCY: - Header and content now use same max-width (max-w-7xl) - All pages use consistent PageContainer wrapper - Unified spacing and padding NEW REUSABLE COMPONENTS (PremiumTable.tsx): - SearchInput: Consistent search box styling - TabBar: Consistent tabs with counts and icons - FilterBar: Flex container for filter rows - SelectDropdown: Consistent dropdown styling - ActionButton: Consistent button (primary/secondary/ghost) PERFORMANCE OPTIMIZATIONS: 1. Watchlist Page: - useMemo for stats, filtered domains, columns - useCallback for all handlers - memo() for HealthReportModal 2. Auctions Page: - useMemo for tabs, sorted auctions - useCallback for handlers - Pure functions for calculations 3. TLD Pricing Page: - useMemo for filtered data, stats, columns - useCallback for data loading - memo() for Sparkline component 4. Portfolio Page: - useMemo for expiringSoonCount, subtitle - useCallback for all CRUD handlers - Uses new ActionButton 5. Alerts Page: - useMemo for stats - useCallback for all handlers - Uses new ActionButton 6. Marketplace/Listings Pages: - useMemo for filtered/sorted listings, stats - useCallback for data loading - Uses new components 7. Dashboard Page: - useMemo for computed values (greeting, subtitle, etc.) - useCallback for data loading 8. Settings Page: - Added TabBar import for future use - Added useCallback, useMemo imports RESULT: - Reduced unnecessary re-renders - Memoized expensive calculations - Consistent visual styling across all pages - Better mobile responsiveness
564 lines
25 KiB
TypeScript
564 lines
25 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState, useCallback, useMemo } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
|
|
import { PageContainer, TabBar } from '@/components/PremiumTable'
|
|
import { useStore } from '@/lib/store'
|
|
import { api, PriceAlert } from '@/lib/api'
|
|
import {
|
|
User,
|
|
Bell,
|
|
CreditCard,
|
|
Shield,
|
|
ChevronRight,
|
|
Loader2,
|
|
Check,
|
|
AlertCircle,
|
|
Trash2,
|
|
ExternalLink,
|
|
Crown,
|
|
Zap,
|
|
Key,
|
|
TrendingUp,
|
|
} from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import clsx from 'clsx'
|
|
|
|
type SettingsTab = 'profile' | 'notifications' | 'billing' | 'security'
|
|
|
|
export default function SettingsPage() {
|
|
const router = useRouter()
|
|
const { user, isAuthenticated, isLoading, checkAuth, subscription } = useStore()
|
|
|
|
const [activeTab, setActiveTab] = useState<SettingsTab>('profile')
|
|
const [saving, setSaving] = useState(false)
|
|
const [success, setSuccess] = useState<string | null>(null)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
// Profile form
|
|
const [profileForm, setProfileForm] = useState({
|
|
name: '',
|
|
email: '',
|
|
})
|
|
|
|
// Notification preferences
|
|
const [notificationPrefs, setNotificationPrefs] = useState({
|
|
domain_availability: true,
|
|
price_alerts: true,
|
|
weekly_digest: false,
|
|
})
|
|
const [savingNotifications, setSavingNotifications] = useState(false)
|
|
|
|
// Price alerts
|
|
const [priceAlerts, setPriceAlerts] = useState<PriceAlert[]>([])
|
|
const [loadingAlerts, setLoadingAlerts] = useState(false)
|
|
const [deletingAlertId, setDeletingAlertId] = useState<number | null>(null)
|
|
|
|
useEffect(() => {
|
|
checkAuth()
|
|
}, [checkAuth])
|
|
|
|
useEffect(() => {
|
|
if (!isLoading && !isAuthenticated) {
|
|
router.push('/login')
|
|
}
|
|
}, [isLoading, isAuthenticated, router])
|
|
|
|
useEffect(() => {
|
|
if (user) {
|
|
setProfileForm({
|
|
name: user.name || '',
|
|
email: user.email || '',
|
|
})
|
|
}
|
|
}, [user])
|
|
|
|
useEffect(() => {
|
|
if (isAuthenticated && activeTab === 'notifications') {
|
|
loadPriceAlerts()
|
|
}
|
|
}, [isAuthenticated, activeTab])
|
|
|
|
const loadPriceAlerts = async () => {
|
|
setLoadingAlerts(true)
|
|
try {
|
|
const alerts = await api.getPriceAlerts()
|
|
setPriceAlerts(alerts)
|
|
} catch (err) {
|
|
console.error('Failed to load alerts:', err)
|
|
} finally {
|
|
setLoadingAlerts(false)
|
|
}
|
|
}
|
|
|
|
const handleSaveProfile = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setSaving(true)
|
|
setError(null)
|
|
setSuccess(null)
|
|
|
|
try {
|
|
await api.updateMe({ name: profileForm.name || undefined })
|
|
const { checkAuth } = useStore.getState()
|
|
await checkAuth()
|
|
setSuccess('Profile updated successfully')
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to update profile')
|
|
} finally {
|
|
setSaving(false)
|
|
}
|
|
}
|
|
|
|
const handleSaveNotifications = async () => {
|
|
setSavingNotifications(true)
|
|
setError(null)
|
|
setSuccess(null)
|
|
|
|
try {
|
|
localStorage.setItem('notification_prefs', JSON.stringify(notificationPrefs))
|
|
setSuccess('Notification preferences saved')
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to save preferences')
|
|
} finally {
|
|
setSavingNotifications(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
const saved = localStorage.getItem('notification_prefs')
|
|
if (saved) {
|
|
try {
|
|
setNotificationPrefs(JSON.parse(saved))
|
|
} catch {}
|
|
}
|
|
}, [])
|
|
|
|
const handleDeletePriceAlert = async (tld: string, alertId: number) => {
|
|
setDeletingAlertId(alertId)
|
|
try {
|
|
await api.deletePriceAlert(tld)
|
|
setPriceAlerts(prev => prev.filter(a => a.id !== alertId))
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to delete alert')
|
|
} finally {
|
|
setDeletingAlertId(null)
|
|
}
|
|
}
|
|
|
|
const handleOpenBillingPortal = async () => {
|
|
try {
|
|
const { portal_url } = await api.createPortalSession()
|
|
window.location.href = portal_url
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to open billing portal')
|
|
}
|
|
}
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<div className="w-5 h-5 border-2 border-accent border-t-transparent rounded-full animate-spin" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!isAuthenticated || !user) {
|
|
return null
|
|
}
|
|
|
|
const tierName = subscription?.tier_name || subscription?.tier || 'Scout'
|
|
const isProOrHigher = ['Trader', 'Tycoon', 'Professional', 'Enterprise'].includes(tierName)
|
|
|
|
const tabs = [
|
|
{ id: 'profile' as const, label: 'Profile', icon: User },
|
|
{ id: 'notifications' as const, label: 'Notifications', icon: Bell },
|
|
{ id: 'billing' as const, label: 'Billing', icon: CreditCard },
|
|
{ id: 'security' as const, label: 'Security', icon: Shield },
|
|
]
|
|
|
|
return (
|
|
<CommandCenterLayout
|
|
title="Settings"
|
|
subtitle="Manage your account"
|
|
>
|
|
<PageContainer>
|
|
{/* Messages */}
|
|
{error && (
|
|
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-xl flex items-center gap-3">
|
|
<AlertCircle className="w-5 h-5 text-red-400 shrink-0" />
|
|
<p className="text-sm text-red-400 flex-1">{error}</p>
|
|
<button onClick={() => setError(null)} className="text-red-400 hover:text-red-300">
|
|
<Trash2 className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{success && (
|
|
<div className="p-4 bg-accent/10 border border-accent/20 rounded-xl flex items-center gap-3">
|
|
<Check className="w-5 h-5 text-accent shrink-0" />
|
|
<p className="text-sm text-accent flex-1">{success}</p>
|
|
<button onClick={() => setSuccess(null)} className="text-accent hover:text-accent/80">
|
|
<Trash2 className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex flex-col lg:flex-row gap-6">
|
|
{/* Sidebar */}
|
|
<div className="lg:w-72 shrink-0 space-y-5">
|
|
{/* Mobile: Horizontal scroll tabs */}
|
|
<nav className="lg:hidden flex gap-2 overflow-x-auto pb-2 -mx-4 px-4 scrollbar-hide">
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id)}
|
|
className={clsx(
|
|
"flex items-center gap-2.5 px-5 py-3 text-sm font-medium rounded-xl whitespace-nowrap transition-all",
|
|
activeTab === tab.id
|
|
? "bg-gradient-to-r from-accent to-accent/80 text-background shadow-lg shadow-accent/20"
|
|
: "bg-background-secondary/50 text-foreground-muted hover:text-foreground border border-border/50"
|
|
)}
|
|
>
|
|
<tab.icon className="w-4 h-4" />
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
|
|
{/* Desktop: Vertical tabs */}
|
|
<nav className="hidden lg:block p-2 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 border border-border/40 rounded-2xl backdrop-blur-sm">
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id)}
|
|
className={clsx(
|
|
"w-full flex items-center gap-3 px-5 py-3.5 text-sm font-medium rounded-xl transition-all",
|
|
activeTab === tab.id
|
|
? "bg-gradient-to-r from-accent to-accent/80 text-background shadow-lg shadow-accent/20"
|
|
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5"
|
|
)}
|
|
>
|
|
<tab.icon className="w-4 h-4" />
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
|
|
{/* Plan info */}
|
|
<div className="hidden lg:block p-5 bg-accent/5 border border-accent/20 rounded-2xl">
|
|
<div className="flex items-center gap-2 mb-3">
|
|
{isProOrHigher ? <Crown className="w-5 h-5 text-accent" /> : <Zap className="w-5 h-5 text-accent" />}
|
|
<span className="text-sm font-semibold text-foreground">{tierName} Plan</span>
|
|
</div>
|
|
<p className="text-xs text-foreground-muted mb-4">
|
|
{subscription?.domains_used || 0} / {subscription?.domain_limit || 5} domains tracked
|
|
</p>
|
|
{!isProOrHigher && (
|
|
<Link
|
|
href="/pricing"
|
|
className="flex items-center justify-center gap-2 w-full py-2.5 bg-gradient-to-r from-accent to-accent/80 text-background text-sm font-medium rounded-xl hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)] transition-all"
|
|
>
|
|
Upgrade
|
|
<ChevronRight className="w-3.5 h-3.5" />
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="flex-1 min-w-0">
|
|
{/* Profile Tab */}
|
|
{activeTab === 'profile' && (
|
|
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
|
|
<h2 className="text-lg font-medium text-foreground mb-6">Profile Information</h2>
|
|
|
|
<form onSubmit={handleSaveProfile} className="space-y-5">
|
|
<div>
|
|
<label className="block text-sm text-foreground-muted mb-2">Name</label>
|
|
<input
|
|
type="text"
|
|
value={profileForm.name}
|
|
onChange={(e) => setProfileForm({ ...profileForm, name: e.target.value })}
|
|
placeholder="Your name"
|
|
className="w-full h-11 px-4 bg-background border border-border/50 rounded-xl text-foreground
|
|
placeholder:text-foreground-subtle focus:outline-none focus:border-accent/50 transition-all"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm text-foreground-muted mb-2">Email</label>
|
|
<input
|
|
type="email"
|
|
value={profileForm.email}
|
|
disabled
|
|
className="w-full h-11 px-4 bg-foreground/5 border border-border/50 rounded-xl text-foreground-muted cursor-not-allowed"
|
|
/>
|
|
<p className="text-xs text-foreground-subtle mt-1.5">Email cannot be changed</p>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={saving}
|
|
className="flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-accent to-accent/80 text-background font-medium rounded-xl
|
|
hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)] disabled:opacity-50 transition-all"
|
|
>
|
|
{saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
|
|
Save Changes
|
|
</button>
|
|
</form>
|
|
</div>
|
|
)}
|
|
|
|
{/* Notifications Tab */}
|
|
{activeTab === 'notifications' && (
|
|
<div className="space-y-6">
|
|
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
|
|
<h2 className="text-lg font-medium text-foreground mb-5">Email Preferences</h2>
|
|
|
|
<div className="space-y-3">
|
|
{[
|
|
{ key: 'domain_availability', label: 'Domain Availability', desc: 'Get notified when watched domains become available' },
|
|
{ key: 'price_alerts', label: 'Price Alerts', desc: 'Get notified when TLD prices change' },
|
|
{ key: 'weekly_digest', label: 'Weekly Digest', desc: 'Receive a weekly summary of your portfolio' },
|
|
].map((item) => (
|
|
<label key={item.key} className="flex items-center justify-between p-4 bg-foreground/5 border border-border/30 rounded-xl cursor-pointer hover:border-foreground/20 transition-colors">
|
|
<div>
|
|
<p className="text-sm font-medium text-foreground">{item.label}</p>
|
|
<p className="text-xs text-foreground-muted">{item.desc}</p>
|
|
</div>
|
|
<input
|
|
type="checkbox"
|
|
checked={notificationPrefs[item.key as keyof typeof notificationPrefs]}
|
|
onChange={(e) => setNotificationPrefs({ ...notificationPrefs, [item.key]: e.target.checked })}
|
|
className="w-5 h-5 accent-accent cursor-pointer"
|
|
/>
|
|
</label>
|
|
))}
|
|
</div>
|
|
|
|
<button
|
|
onClick={handleSaveNotifications}
|
|
disabled={savingNotifications}
|
|
className="mt-5 flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-accent to-accent/80 text-background font-medium rounded-xl
|
|
hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)] disabled:opacity-50 transition-all"
|
|
>
|
|
{savingNotifications ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
|
|
Save Preferences
|
|
</button>
|
|
</div>
|
|
|
|
{/* Active Price Alerts */}
|
|
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
|
|
<h2 className="text-lg font-medium text-foreground mb-5">Active Price Alerts</h2>
|
|
|
|
{loadingAlerts ? (
|
|
<div className="py-10 flex items-center justify-center">
|
|
<Loader2 className="w-6 h-6 animate-spin text-accent" />
|
|
</div>
|
|
) : priceAlerts.length === 0 ? (
|
|
<div className="py-12 text-center border border-dashed border-border/50 rounded-xl bg-foreground/5">
|
|
<Bell className="w-10 h-10 text-foreground-subtle mx-auto mb-4" />
|
|
<p className="text-foreground-muted mb-3">No price alerts set</p>
|
|
<Link href="/command/pricing" className="text-accent hover:text-accent/80 text-sm font-medium">
|
|
Browse TLD prices →
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{priceAlerts.map((alert) => (
|
|
<div
|
|
key={alert.id}
|
|
className="flex items-center justify-between p-4 bg-foreground/5 border border-border/30 rounded-xl hover:border-foreground/20 transition-colors"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className="relative">
|
|
<div className={clsx(
|
|
"w-2.5 h-2.5 rounded-full",
|
|
alert.is_active ? "bg-accent" : "bg-foreground-subtle"
|
|
)} />
|
|
{alert.is_active && (
|
|
<span className="absolute inset-0 rounded-full bg-accent animate-ping opacity-40" />
|
|
)}
|
|
</div>
|
|
<div>
|
|
<Link
|
|
href={`/tld-pricing/${alert.tld}`}
|
|
className="text-sm font-mono font-medium text-foreground hover:text-accent transition-colors"
|
|
>
|
|
.{alert.tld}
|
|
</Link>
|
|
<p className="text-xs text-foreground-muted">
|
|
Alert on {alert.threshold_percent}% change
|
|
{alert.target_price && ` or below $${alert.target_price}`}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => handleDeletePriceAlert(alert.tld, alert.id)}
|
|
disabled={deletingAlertId === alert.id}
|
|
className="p-2 text-foreground-subtle hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-all"
|
|
>
|
|
{deletingAlertId === alert.id ? (
|
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
) : (
|
|
<Trash2 className="w-4 h-4" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Billing Tab */}
|
|
{activeTab === 'billing' && (
|
|
<div className="space-y-6">
|
|
{/* Current Plan */}
|
|
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
|
|
<h2 className="text-lg font-medium text-foreground mb-6">Your Current Plan</h2>
|
|
|
|
<div className="p-5 bg-accent/5 border border-accent/20 rounded-xl mb-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="flex items-center gap-3">
|
|
{tierName === 'Tycoon' ? <Crown className="w-6 h-6 text-accent" /> : tierName === 'Trader' ? <TrendingUp className="w-6 h-6 text-accent" /> : <Zap className="w-6 h-6 text-accent" />}
|
|
<div>
|
|
<p className="text-xl font-semibold text-foreground">{tierName}</p>
|
|
<p className="text-sm text-foreground-muted">
|
|
{tierName === 'Scout' ? 'Free forever' : tierName === 'Trader' ? '$9/month' : '$29/month'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<span className={clsx(
|
|
"px-3 py-1.5 text-xs font-medium rounded-full",
|
|
isProOrHigher ? "bg-accent/10 text-accent" : "bg-foreground/5 text-foreground-muted"
|
|
)}>
|
|
{isProOrHigher ? 'Active' : 'Free'}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Plan Stats */}
|
|
<div className="grid grid-cols-3 gap-4 p-4 bg-background/50 rounded-xl mb-4">
|
|
<div className="text-center">
|
|
<p className="text-2xl font-semibold text-foreground">{subscription?.domain_limit || 5}</p>
|
|
<p className="text-xs text-foreground-muted">Domains</p>
|
|
</div>
|
|
<div className="text-center border-x border-border/50">
|
|
<p className="text-2xl font-semibold text-foreground">
|
|
{subscription?.check_frequency === 'realtime' ? '10m' :
|
|
subscription?.check_frequency === 'hourly' ? '1h' : '24h'}
|
|
</p>
|
|
<p className="text-xs text-foreground-muted">Check Interval</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-2xl font-semibold text-foreground">
|
|
{subscription?.portfolio_limit === -1 ? '∞' : subscription?.portfolio_limit || 0}
|
|
</p>
|
|
<p className="text-xs text-foreground-muted">Portfolio</p>
|
|
</div>
|
|
</div>
|
|
|
|
{isProOrHigher ? (
|
|
<button
|
|
onClick={handleOpenBillingPortal}
|
|
className="w-full flex items-center justify-center gap-2 py-3 bg-background text-foreground font-medium rounded-xl border border-border/50
|
|
hover:border-foreground/20 transition-all"
|
|
>
|
|
<ExternalLink className="w-4 h-4" />
|
|
Manage Subscription
|
|
</button>
|
|
) : (
|
|
<Link
|
|
href="/pricing"
|
|
className="w-full flex items-center justify-center gap-2 py-3 bg-gradient-to-r from-accent to-accent/80 text-background font-medium rounded-xl
|
|
hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)] transition-all"
|
|
>
|
|
<Zap className="w-4 h-4" />
|
|
Upgrade Plan
|
|
</Link>
|
|
)}
|
|
</div>
|
|
|
|
{/* Plan Features */}
|
|
<h3 className="text-sm font-medium text-foreground mb-3">Your Plan Includes</h3>
|
|
<ul className="grid grid-cols-2 gap-2">
|
|
{[
|
|
`${subscription?.domain_limit || 5} Watchlist Domains`,
|
|
`${subscription?.check_frequency === 'realtime' ? '10-minute' : subscription?.check_frequency === 'hourly' ? 'Hourly' : 'Daily'} Scans`,
|
|
'Email Alerts',
|
|
'TLD Price Data',
|
|
].map((feature) => (
|
|
<li key={feature} className="flex items-center gap-2 text-sm">
|
|
<Check className="w-4 h-4 text-accent" />
|
|
<span className="text-foreground">{feature}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Security Tab */}
|
|
{activeTab === 'security' && (
|
|
<div className="space-y-6">
|
|
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
|
|
<h2 className="text-lg font-medium text-foreground mb-4">Password</h2>
|
|
<p className="text-sm text-foreground-muted mb-5">
|
|
Change your password or reset it if you've forgotten it.
|
|
</p>
|
|
<Link
|
|
href="/forgot-password"
|
|
className="inline-flex items-center gap-2 px-5 py-3 bg-foreground/5 border border-border/50 text-foreground font-medium rounded-xl
|
|
hover:border-foreground/20 transition-all"
|
|
>
|
|
<Key className="w-4 h-4" />
|
|
Change Password
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
|
|
<h2 className="text-lg font-medium text-foreground mb-5">Account Security</h2>
|
|
|
|
<div className="space-y-3">
|
|
<div className="flex items-center justify-between p-4 bg-foreground/5 border border-border/30 rounded-xl">
|
|
<div>
|
|
<p className="text-sm font-medium text-foreground">Email Verified</p>
|
|
<p className="text-xs text-foreground-muted">Your email address has been verified</p>
|
|
</div>
|
|
<div className="w-8 h-8 bg-accent/10 rounded-lg flex items-center justify-center">
|
|
<Check className="w-4 h-4 text-accent" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between p-4 bg-foreground/5 border border-border/30 rounded-xl">
|
|
<div>
|
|
<p className="text-sm font-medium text-foreground">Two-Factor Authentication</p>
|
|
<p className="text-xs text-foreground-muted">Coming soon</p>
|
|
</div>
|
|
<span className="text-xs px-2.5 py-1 bg-foreground/5 text-foreground-muted rounded-full border border-border/30">Soon</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="relative overflow-hidden rounded-2xl border border-red-500/20 bg-red-500/5 p-6">
|
|
<h2 className="text-lg font-medium text-red-400 mb-2">Danger Zone</h2>
|
|
<p className="text-sm text-foreground-muted mb-5">
|
|
Permanently delete your account and all associated data.
|
|
</p>
|
|
<button
|
|
className="px-5 py-3 bg-red-500 text-white font-medium rounded-xl hover:bg-red-500/90 transition-all"
|
|
>
|
|
Delete Account
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</PageContainer>
|
|
</CommandCenterLayout>
|
|
)
|
|
}
|