Navigation: Radar first, For Sale under Monetize; Settings page redesign
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
This commit is contained in:
@ -1,9 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState, useCallback, useMemo } from 'react'
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { TerminalLayout } from '@/components/TerminalLayout'
|
||||
import { PageContainer, TabBar } from '@/components/PremiumTable'
|
||||
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
|
||||
import { useStore } from '@/lib/store'
|
||||
import { api, PriceAlert } from '@/lib/api'
|
||||
import {
|
||||
@ -21,6 +20,8 @@ import {
|
||||
Zap,
|
||||
Key,
|
||||
TrendingUp,
|
||||
X,
|
||||
Settings,
|
||||
} from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import clsx from 'clsx'
|
||||
@ -36,13 +37,11 @@ export default function SettingsPage() {
|
||||
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,
|
||||
@ -50,7 +49,6 @@ export default function SettingsPage() {
|
||||
})
|
||||
const [savingNotifications, setSavingNotifications] = useState(false)
|
||||
|
||||
// Price alerts
|
||||
const [priceAlerts, setPriceAlerts] = useState<PriceAlert[]>([])
|
||||
const [loadingAlerts, setLoadingAlerts] = useState(false)
|
||||
const [deletingAlertId, setDeletingAlertId] = useState<number | null>(null)
|
||||
@ -102,7 +100,7 @@ export default function SettingsPage() {
|
||||
await api.updateMe({ name: profileForm.name || undefined })
|
||||
const { checkAuth } = useStore.getState()
|
||||
await checkAuth()
|
||||
setSuccess('Profile updated successfully')
|
||||
setSuccess('Profile updated')
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to update profile')
|
||||
} finally {
|
||||
@ -117,7 +115,7 @@ export default function SettingsPage() {
|
||||
|
||||
try {
|
||||
localStorage.setItem('notification_prefs', JSON.stringify(notificationPrefs))
|
||||
setSuccess('Notification preferences saved')
|
||||
setSuccess('Preferences saved')
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to save preferences')
|
||||
} finally {
|
||||
@ -157,8 +155,8 @@ export default function SettingsPage() {
|
||||
|
||||
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 className="min-h-screen flex items-center justify-center bg-[#020202]">
|
||||
<Loader2 className="w-6 h-6 text-accent animate-spin" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -178,46 +176,69 @@ export default function SettingsPage() {
|
||||
]
|
||||
|
||||
return (
|
||||
<TerminalLayout
|
||||
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>
|
||||
)}
|
||||
<CommandCenterLayout minimal>
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* HEADER */}
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
<section className="pt-6 lg:pt-8 pb-6">
|
||||
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-6">
|
||||
<div className="space-y-3">
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<Settings className="w-4 h-4 text-accent" />
|
||||
<span className="text-[10px] font-mono tracking-wide text-accent">Account</span>
|
||||
</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>
|
||||
<h1 className="font-display text-[2rem] lg:text-[2.5rem] leading-[1] tracking-[-0.02em]">
|
||||
<span className="text-white">Settings</span>
|
||||
</h1>
|
||||
</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">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="px-3 py-1.5 border border-white/10 bg-white/[0.02] flex items-center gap-2">
|
||||
{tierName === 'Tycoon' ? <Crown className="w-4 h-4 text-amber-400" /> :
|
||||
tierName === 'Trader' ? <TrendingUp className="w-4 h-4 text-accent" /> :
|
||||
<Zap className="w-4 h-4 text-accent" />}
|
||||
<span className="text-xs font-mono text-white">{tierName}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Messages */}
|
||||
{error && (
|
||||
<div className="mb-6 p-4 bg-red-500/10 border border-red-500/20 flex items-center gap-3 text-red-400">
|
||||
<AlertCircle className="w-5 h-5" />
|
||||
<p className="text-sm flex-1">{error}</p>
|
||||
<button onClick={() => setError(null)}><X className="w-4 h-4" /></button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{success && (
|
||||
<div className="mb-6 p-4 bg-accent/10 border border-accent/20 flex items-center gap-3 text-accent">
|
||||
<Check className="w-5 h-5" />
|
||||
<p className="text-sm flex-1">{success}</p>
|
||||
<button onClick={() => setSuccess(null)}><X className="w-4 h-4" /></button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* TABS + CONTENT */}
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
<section className="pb-12">
|
||||
<div className="flex flex-col lg:flex-row gap-8">
|
||||
{/* Tab Navigation */}
|
||||
<div className="lg:w-56 shrink-0">
|
||||
{/* Mobile: Horizontal */}
|
||||
<nav className="lg:hidden flex gap-2 overflow-x-auto pb-2 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",
|
||||
"flex items-center gap-2 px-4 py-2 text-xs font-mono 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"
|
||||
? "bg-accent text-black"
|
||||
: "bg-white/[0.02] border border-white/10 text-white/50 hover:text-white"
|
||||
)}
|
||||
>
|
||||
<tab.icon className="w-4 h-4" />
|
||||
@ -226,17 +247,17 @@ export default function SettingsPage() {
|
||||
))}
|
||||
</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">
|
||||
{/* Desktop: Vertical */}
|
||||
<nav className="hidden lg:block space-y-1">
|
||||
{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",
|
||||
"w-full flex items-center gap-3 px-4 py-3 text-sm font-mono 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"
|
||||
? "bg-white/[0.03] text-white border-l-2 border-accent"
|
||||
: "text-white/40 hover:text-white hover:bg-white/[0.02] border-l-2 border-transparent"
|
||||
)}
|
||||
>
|
||||
<tab.icon className="w-4 h-4" />
|
||||
@ -245,22 +266,22 @@ export default function SettingsPage() {
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Plan info */}
|
||||
<div className="hidden lg:block p-5 bg-accent/5 border border-accent/20 rounded-2xl">
|
||||
{/* Plan Info - Desktop */}
|
||||
<div className="hidden lg:block mt-8 p-4 bg-accent/5 border border-accent/20">
|
||||
<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>
|
||||
{isProOrHigher ? <Crown className="w-4 h-4 text-accent" /> : <Zap className="w-4 h-4 text-accent" />}
|
||||
<span className="text-xs font-mono text-white">{tierName}</span>
|
||||
</div>
|
||||
<p className="text-xs text-foreground-muted mb-4">
|
||||
{subscription?.domains_used || 0} / {subscription?.domain_limit || 5} domains tracked
|
||||
<p className="text-[10px] text-white/40 font-mono mb-4">
|
||||
{subscription?.domains_used || 0} / {subscription?.domain_limit || 5} domains
|
||||
</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"
|
||||
className="flex items-center justify-center gap-2 w-full py-2 bg-accent text-black text-xs font-semibold hover:bg-white transition-colors"
|
||||
>
|
||||
Upgrade
|
||||
<ChevronRight className="w-3.5 h-3.5" />
|
||||
<ChevronRight className="w-3 h-3" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
@ -270,41 +291,39 @@ export default function SettingsPage() {
|
||||
<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>
|
||||
<div className="border border-white/[0.08] bg-white/[0.01] p-6">
|
||||
<h2 className="text-sm font-mono text-white/40 tracking-wide mb-6">Profile Information</h2>
|
||||
|
||||
<form onSubmit={handleSaveProfile} className="space-y-5">
|
||||
<form onSubmit={handleSaveProfile} className="space-y-5 max-w-md">
|
||||
<div>
|
||||
<label className="block text-sm text-foreground-muted mb-2">Name</label>
|
||||
<label className="block text-xs text-white/50 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"
|
||||
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-foreground-muted mb-2">Email</label>
|
||||
<label className="block text-xs text-white/50 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"
|
||||
className="w-full px-4 py-3 bg-white/[0.02] border border-white/10 text-white/40 cursor-not-allowed"
|
||||
/>
|
||||
<p className="text-xs text-foreground-subtle mt-1.5">Email cannot be changed</p>
|
||||
<p className="text-[10px] text-white/30 mt-1">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"
|
||||
className="flex items-center gap-2 px-5 py-2.5 bg-accent text-black text-sm font-semibold hover:bg-white disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
|
||||
Save Changes
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@ -313,25 +332,25 @@ export default function SettingsPage() {
|
||||
{/* 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="border border-white/[0.08] bg-white/[0.01] p-6">
|
||||
<h2 className="text-sm font-mono text-white/40 tracking-wide mb-5">Email Preferences</h2>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2 max-w-md">
|
||||
{[
|
||||
{ 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">
|
||||
<label key={item.key} className="flex items-center justify-between p-4 bg-white/[0.02] border border-white/[0.06] cursor-pointer hover:border-white/[0.1] transition-colors">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">{item.label}</p>
|
||||
<p className="text-xs text-foreground-muted">{item.desc}</p>
|
||||
<p className="text-sm text-white">{item.label}</p>
|
||||
<p className="text-[10px] text-white/40">{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"
|
||||
className="w-4 h-4 accent-accent cursor-pointer"
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
@ -340,27 +359,26 @@ export default function SettingsPage() {
|
||||
<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"
|
||||
className="mt-5 flex items-center gap-2 px-5 py-2.5 bg-accent text-black text-sm font-semibold hover:bg-white disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{savingNotifications ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
|
||||
Save Preferences
|
||||
Save
|
||||
</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>
|
||||
<div className="border border-white/[0.08] bg-white/[0.01] p-6">
|
||||
<h2 className="text-sm font-mono text-white/40 tracking-wide 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="/terminal/intel" className="text-accent hover:text-accent/80 text-sm font-medium">
|
||||
<div className="py-12 text-center border border-dashed border-white/10">
|
||||
<Bell className="w-8 h-8 text-white/10 mx-auto mb-4" />
|
||||
<p className="text-white/40 text-sm mb-3">No price alerts set</p>
|
||||
<Link href="/terminal/intel" className="text-accent hover:text-white text-xs font-mono">
|
||||
Browse TLD prices →
|
||||
</Link>
|
||||
</div>
|
||||
@ -369,26 +387,21 @@ export default function SettingsPage() {
|
||||
{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"
|
||||
className="flex items-center justify-between p-4 bg-white/[0.02] border border-white/[0.06] hover:border-white/[0.1] 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 className={clsx(
|
||||
"w-2 h-2",
|
||||
alert.is_active ? "bg-accent" : "bg-white/20"
|
||||
)} />
|
||||
<div>
|
||||
<Link
|
||||
href={`/tld-pricing/${alert.tld}`}
|
||||
className="text-sm font-mono font-medium text-foreground hover:text-accent transition-colors"
|
||||
href={`/terminal/intel/${alert.tld}`}
|
||||
className="text-sm font-mono text-white hover:text-accent transition-colors"
|
||||
>
|
||||
.{alert.tld}
|
||||
</Link>
|
||||
<p className="text-xs text-foreground-muted">
|
||||
<p className="text-[10px] text-white/40">
|
||||
Alert on {alert.threshold_percent}% change
|
||||
{alert.target_price && ` or below $${alert.target_price}`}
|
||||
</p>
|
||||
@ -397,7 +410,7 @@ export default function SettingsPage() {
|
||||
<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"
|
||||
className="p-2 text-white/30 hover:text-red-400 hover:bg-red-400/10 transition-all"
|
||||
>
|
||||
{deletingAlertId === alert.id ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
@ -416,55 +429,55 @@ export default function SettingsPage() {
|
||||
{/* 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="border border-white/[0.08] bg-white/[0.01] p-6">
|
||||
<h2 className="text-sm font-mono text-white/40 tracking-wide mb-6">Current Plan</h2>
|
||||
|
||||
<div className="p-5 bg-accent/5 border border-accent/20 rounded-xl mb-6">
|
||||
<div className="p-5 bg-accent/5 border border-accent/20 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" />}
|
||||
{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">
|
||||
<p className="text-lg font-display text-white">{tierName}</p>
|
||||
<p className="text-xs text-white/40">
|
||||
{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"
|
||||
"px-2 py-1 text-[10px] font-mono",
|
||||
isProOrHigher ? "bg-accent/10 text-accent" : "bg-white/5 text-white/40"
|
||||
)}>
|
||||
{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="grid grid-cols-3 gap-4 p-4 bg-black/30 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>
|
||||
<p className="text-xl font-display text-white">{subscription?.domain_limit || 5}</p>
|
||||
<p className="text-[10px] text-white/40 font-mono">Domains</p>
|
||||
</div>
|
||||
<div className="text-center border-x border-border/50">
|
||||
<p className="text-2xl font-semibold text-foreground">
|
||||
<div className="text-center border-x border-white/10">
|
||||
<p className="text-xl font-display text-white">
|
||||
{subscription?.check_frequency === 'realtime' ? '10m' :
|
||||
subscription?.check_frequency === 'hourly' ? '1h' : '24h'}
|
||||
</p>
|
||||
<p className="text-xs text-foreground-muted">Check Interval</p>
|
||||
<p className="text-[10px] text-white/40 font-mono">Interval</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-2xl font-semibold text-foreground">
|
||||
<p className="text-xl font-display text-white">
|
||||
{subscription?.portfolio_limit === -1 ? '∞' : subscription?.portfolio_limit || 0}
|
||||
</p>
|
||||
<p className="text-xs text-foreground-muted">Portfolio</p>
|
||||
<p className="text-[10px] text-white/40 font-mono">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"
|
||||
className="w-full flex items-center justify-center gap-2 py-3 bg-black/50 text-white text-sm font-medium border border-white/10 hover:border-white/20 transition-all"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
Manage Subscription
|
||||
@ -472,8 +485,7 @@ export default function SettingsPage() {
|
||||
) : (
|
||||
<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"
|
||||
className="w-full flex items-center justify-center gap-2 py-3 bg-accent text-black text-sm font-semibold hover:bg-white transition-all"
|
||||
>
|
||||
<Zap className="w-4 h-4" />
|
||||
Upgrade Plan
|
||||
@ -482,7 +494,7 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
|
||||
{/* Plan Features */}
|
||||
<h3 className="text-sm font-medium text-foreground mb-3">Your Plan Includes</h3>
|
||||
<h3 className="text-xs font-mono text-white/40 mb-3">Includes</h3>
|
||||
<ul className="grid grid-cols-2 gap-2">
|
||||
{[
|
||||
`${subscription?.domain_limit || 5} Watchlist Domains`,
|
||||
@ -490,9 +502,9 @@ export default function SettingsPage() {
|
||||
'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 key={feature} className="flex items-center gap-2 text-xs">
|
||||
<Check className="w-3 h-3 text-accent" />
|
||||
<span className="text-white/60">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@ -503,52 +515,51 @@ export default function SettingsPage() {
|
||||
{/* 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">
|
||||
<div className="border border-white/[0.08] bg-white/[0.01] p-6">
|
||||
<h2 className="text-sm font-mono text-white/40 tracking-wide mb-4">Password</h2>
|
||||
<p className="text-xs text-white/40 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"
|
||||
className="inline-flex items-center gap-2 px-4 py-2.5 bg-white/5 border border-white/10 text-white text-sm hover:border-white/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="border border-white/[0.08] bg-white/[0.01] p-6">
|
||||
<h2 className="text-sm font-mono text-white/40 tracking-wide 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 className="space-y-2">
|
||||
<div className="flex items-center justify-between p-4 bg-white/[0.02] border border-white/[0.06]">
|
||||
<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>
|
||||
<p className="text-sm text-white">Email Verified</p>
|
||||
<p className="text-[10px] text-white/40">Your email has been verified</p>
|
||||
</div>
|
||||
<div className="w-8 h-8 bg-accent/10 rounded-lg flex items-center justify-center">
|
||||
<div className="w-7 h-7 bg-accent/10 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 className="flex items-center justify-between p-4 bg-white/[0.02] border border-white/[0.06]">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">Two-Factor Authentication</p>
|
||||
<p className="text-xs text-foreground-muted">Coming soon</p>
|
||||
<p className="text-sm text-white">Two-Factor Authentication</p>
|
||||
<p className="text-[10px] text-white/40">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>
|
||||
<span className="text-[10px] px-2 py-1 bg-white/5 text-white/40 border border-white/10 font-mono">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">
|
||||
<div className="border border-red-500/20 bg-red-500/5 p-6">
|
||||
<h2 className="text-sm font-mono text-red-400 tracking-wide mb-2">Danger Zone</h2>
|
||||
<p className="text-xs text-white/40 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"
|
||||
className="px-4 py-2.5 bg-red-500 text-white text-sm font-semibold hover:bg-red-400 transition-all"
|
||||
>
|
||||
Delete Account
|
||||
</button>
|
||||
@ -557,7 +568,7 @@ export default function SettingsPage() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PageContainer>
|
||||
</TerminalLayout>
|
||||
</section>
|
||||
</CommandCenterLayout>
|
||||
)
|
||||
}
|
||||
|
||||
@ -16,13 +16,12 @@ import {
|
||||
Crown,
|
||||
Zap,
|
||||
Shield,
|
||||
CreditCard,
|
||||
Menu,
|
||||
X,
|
||||
Sparkles,
|
||||
Tag,
|
||||
Target,
|
||||
Coins,
|
||||
Radar,
|
||||
} from 'lucide-react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import clsx from 'clsx'
|
||||
@ -36,15 +35,12 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
||||
const pathname = usePathname()
|
||||
const { user, logout, subscription, domains } = useStore()
|
||||
|
||||
// Internal state for uncontrolled mode
|
||||
const [internalCollapsed, setInternalCollapsed] = useState(false)
|
||||
const [mobileOpen, setMobileOpen] = useState(false)
|
||||
|
||||
// Use controlled or uncontrolled state
|
||||
const collapsed = controlledCollapsed ?? internalCollapsed
|
||||
const setCollapsed = onCollapsedChange ?? setInternalCollapsed
|
||||
|
||||
// Load collapsed state from localStorage
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem('sidebar-collapsed')
|
||||
if (saved) {
|
||||
@ -52,12 +48,10 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Close mobile menu on route change
|
||||
useEffect(() => {
|
||||
setMobileOpen(false)
|
||||
}, [pathname])
|
||||
|
||||
// Save collapsed state
|
||||
const toggleCollapsed = () => {
|
||||
const newState = !collapsed
|
||||
setCollapsed(newState)
|
||||
@ -67,12 +61,17 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
||||
const tierName = subscription?.tier_name || subscription?.tier || 'Scout'
|
||||
const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap
|
||||
|
||||
// Count available domains for notification badge
|
||||
const availableCount = domains?.filter(d => d.is_available).length || 0
|
||||
const isTycoon = tierName.toLowerCase() === 'tycoon'
|
||||
|
||||
// SECTION 1: Discover - External market data
|
||||
// SECTION 1: Discover - Radar first, then external market data
|
||||
const discoverItems = [
|
||||
{
|
||||
href: '/terminal/radar',
|
||||
label: 'RADAR',
|
||||
icon: Radar,
|
||||
badge: null,
|
||||
},
|
||||
{
|
||||
href: '/terminal/market',
|
||||
label: 'MARKET',
|
||||
@ -95,12 +94,6 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
||||
badge: number | null
|
||||
tycoonOnly?: boolean
|
||||
}> = [
|
||||
{
|
||||
href: '/terminal/radar',
|
||||
label: 'RADAR',
|
||||
icon: LayoutDashboard,
|
||||
badge: null,
|
||||
},
|
||||
{
|
||||
href: '/terminal/watchlist',
|
||||
label: 'WATCHLIST',
|
||||
@ -113,15 +106,9 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
||||
icon: Target,
|
||||
badge: null,
|
||||
},
|
||||
{
|
||||
href: '/terminal/listing',
|
||||
label: 'FOR SALE',
|
||||
icon: Tag,
|
||||
badge: null,
|
||||
},
|
||||
]
|
||||
|
||||
// SECTION 3: Monetize - Passive income features
|
||||
// SECTION 3: Monetize - Passive income + For Sale
|
||||
const monetizeItems: Array<{
|
||||
href: string
|
||||
label: string
|
||||
@ -136,6 +123,12 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
||||
badge: null,
|
||||
isNew: true,
|
||||
},
|
||||
{
|
||||
href: '/terminal/listing',
|
||||
label: 'FOR SALE',
|
||||
icon: Tag,
|
||||
badge: null,
|
||||
},
|
||||
]
|
||||
|
||||
const bottomItems = [
|
||||
@ -159,7 +152,6 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
||||
"relative flex items-center justify-center transition-all duration-300",
|
||||
collapsed ? "w-8 h-8" : "w-10 h-10"
|
||||
)}>
|
||||
{/* Minimalist Glow */}
|
||||
<div className="absolute inset-0 bg-accent/20 blur-lg rounded-full opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
<Image
|
||||
src="/pounce-puma.png"
|
||||
@ -370,7 +362,7 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
||||
</Link>
|
||||
))}
|
||||
|
||||
{/* User Card - Technical */}
|
||||
{/* User Card */}
|
||||
<div className={clsx(
|
||||
"mt-6 border border-white/[0.08] bg-white/[0.02]",
|
||||
collapsed ? "p-2 border-none bg-transparent" : "p-4"
|
||||
@ -404,7 +396,6 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Usage bar - Sharp */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-[10px] font-mono uppercase tracking-wider text-white/30">
|
||||
<span>Usage</span>
|
||||
@ -450,7 +441,7 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Collapse Toggle - Desktop only */}
|
||||
{/* Collapse Toggle */}
|
||||
<button
|
||||
onClick={toggleCollapsed}
|
||||
className={clsx(
|
||||
@ -507,10 +498,10 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
||||
<aside
|
||||
className={clsx(
|
||||
"hidden lg:flex fixed left-0 top-0 bottom-0 z-40 flex-col",
|
||||
"bg-[#020202] backdrop-blur-xl", // Pitch black
|
||||
"border-r border-white/[0.08]", // Subtle border
|
||||
"bg-[#020202] backdrop-blur-xl",
|
||||
"border-r border-white/[0.08]",
|
||||
"transition-all duration-300 ease-out",
|
||||
collapsed ? "w-[72px]" : "w-[240px]" // Slightly narrower
|
||||
collapsed ? "w-[72px]" : "w-[240px]"
|
||||
)}
|
||||
>
|
||||
<SidebarContent />
|
||||
|
||||
Reference in New Issue
Block a user