fix: Settings functionality + Dashboard tab counts
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
SETTINGS FIXES: - Profile save now calls real API (PUT /auth/me) - Updates store after successful profile save - Notification preferences now functional with state - Save preferences button added - Preferences stored in localStorage - Proper loading/saving states DASHBOARD FIX: - Portfolio data loads on mount (not just when tab active) - Tab counts now show correct numbers - Both Watchlist and Portfolio counts visible immediately API: - Added updateMe() method in api.ts
This commit is contained in:
@ -111,11 +111,12 @@ export default function DashboardPage() {
|
|||||||
}
|
}
|
||||||
}, [isLoading, isAuthenticated, router])
|
}, [isLoading, isAuthenticated, router])
|
||||||
|
|
||||||
|
// Load portfolio data on mount (so we can show count in tab)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAuthenticated && activeTab === 'portfolio') {
|
if (isAuthenticated) {
|
||||||
loadPortfolio()
|
loadPortfolio()
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, activeTab])
|
}, [isAuthenticated])
|
||||||
|
|
||||||
const handleOpenBillingPortal = async () => {
|
const handleOpenBillingPortal = async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -43,6 +43,14 @@ export default function SettingsPage() {
|
|||||||
email: '',
|
email: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Notification preferences (local state - would be persisted via API in production)
|
||||||
|
const [notificationPrefs, setNotificationPrefs] = useState({
|
||||||
|
domain_availability: true,
|
||||||
|
price_alerts: true,
|
||||||
|
weekly_digest: false,
|
||||||
|
})
|
||||||
|
const [savingNotifications, setSavingNotifications] = useState(false)
|
||||||
|
|
||||||
// Price alerts
|
// Price alerts
|
||||||
const [priceAlerts, setPriceAlerts] = useState<PriceAlert[]>([])
|
const [priceAlerts, setPriceAlerts] = useState<PriceAlert[]>([])
|
||||||
const [loadingAlerts, setLoadingAlerts] = useState(false)
|
const [loadingAlerts, setLoadingAlerts] = useState(false)
|
||||||
@ -92,9 +100,10 @@ export default function SettingsPage() {
|
|||||||
setSuccess(null)
|
setSuccess(null)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// In a real app, this would call an API to update the user
|
await api.updateMe({ name: profileForm.name || undefined })
|
||||||
// For now, we'll just show success
|
// Update store with new user info
|
||||||
await new Promise(resolve => setTimeout(resolve, 500))
|
const { checkAuth } = useStore.getState()
|
||||||
|
await checkAuth()
|
||||||
setSuccess('Profile updated successfully')
|
setSuccess('Profile updated successfully')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to update profile')
|
setError(err instanceof Error ? err.message : 'Failed to update profile')
|
||||||
@ -103,6 +112,32 @@ export default function SettingsPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSaveNotifications = async () => {
|
||||||
|
setSavingNotifications(true)
|
||||||
|
setError(null)
|
||||||
|
setSuccess(null)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Store in localStorage for now (would be API in production)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load notification preferences from localStorage
|
||||||
|
useEffect(() => {
|
||||||
|
const saved = localStorage.getItem('notification_prefs')
|
||||||
|
if (saved) {
|
||||||
|
try {
|
||||||
|
setNotificationPrefs(JSON.parse(saved))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleDeletePriceAlert = async (tld: string, alertId: number) => {
|
const handleDeletePriceAlert = async (tld: string, alertId: number) => {
|
||||||
setDeletingAlertId(alertId)
|
setDeletingAlertId(alertId)
|
||||||
try {
|
try {
|
||||||
@ -301,7 +336,12 @@ export default function SettingsPage() {
|
|||||||
<p className="text-body-sm font-medium text-foreground">Domain Availability</p>
|
<p className="text-body-sm font-medium text-foreground">Domain Availability</p>
|
||||||
<p className="text-body-xs text-foreground-muted">Get notified when watched domains become available</p>
|
<p className="text-body-xs text-foreground-muted">Get notified when watched domains become available</p>
|
||||||
</div>
|
</div>
|
||||||
<input type="checkbox" defaultChecked className="w-5 h-5 accent-accent" />
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={notificationPrefs.domain_availability}
|
||||||
|
onChange={(e) => setNotificationPrefs({ ...notificationPrefs, domain_availability: e.target.checked })}
|
||||||
|
className="w-5 h-5 accent-accent cursor-pointer"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label className="flex items-center justify-between p-4 bg-background border border-border rounded-xl cursor-pointer hover:border-foreground/20 transition-colors">
|
<label className="flex items-center justify-between p-4 bg-background border border-border rounded-xl cursor-pointer hover:border-foreground/20 transition-colors">
|
||||||
@ -309,7 +349,12 @@ export default function SettingsPage() {
|
|||||||
<p className="text-body-sm font-medium text-foreground">Price Alerts</p>
|
<p className="text-body-sm font-medium text-foreground">Price Alerts</p>
|
||||||
<p className="text-body-xs text-foreground-muted">Get notified when TLD prices change</p>
|
<p className="text-body-xs text-foreground-muted">Get notified when TLD prices change</p>
|
||||||
</div>
|
</div>
|
||||||
<input type="checkbox" defaultChecked className="w-5 h-5 accent-accent" />
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={notificationPrefs.price_alerts}
|
||||||
|
onChange={(e) => setNotificationPrefs({ ...notificationPrefs, price_alerts: e.target.checked })}
|
||||||
|
className="w-5 h-5 accent-accent cursor-pointer"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label className="flex items-center justify-between p-4 bg-background border border-border rounded-xl cursor-pointer hover:border-foreground/20 transition-colors">
|
<label className="flex items-center justify-between p-4 bg-background border border-border rounded-xl cursor-pointer hover:border-foreground/20 transition-colors">
|
||||||
@ -317,9 +362,24 @@ export default function SettingsPage() {
|
|||||||
<p className="text-body-sm font-medium text-foreground">Weekly Digest</p>
|
<p className="text-body-sm font-medium text-foreground">Weekly Digest</p>
|
||||||
<p className="text-body-xs text-foreground-muted">Receive a weekly summary of your portfolio</p>
|
<p className="text-body-xs text-foreground-muted">Receive a weekly summary of your portfolio</p>
|
||||||
</div>
|
</div>
|
||||||
<input type="checkbox" className="w-5 h-5 accent-accent" />
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={notificationPrefs.weekly_digest}
|
||||||
|
onChange={(e) => setNotificationPrefs({ ...notificationPrefs, weekly_digest: e.target.checked })}
|
||||||
|
className="w-5 h-5 accent-accent cursor-pointer"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleSaveNotifications}
|
||||||
|
disabled={savingNotifications}
|
||||||
|
className="mt-5 px-6 py-3 bg-foreground text-background text-ui font-medium rounded-xl
|
||||||
|
hover:bg-foreground/90 disabled:opacity-50 transition-all flex items-center gap-2"
|
||||||
|
>
|
||||||
|
{savingNotifications ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
|
||||||
|
Save Preferences
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Active Price Alerts */}
|
{/* Active Price Alerts */}
|
||||||
|
|||||||
@ -130,6 +130,19 @@ class ApiClient {
|
|||||||
}>('/auth/me')
|
}>('/auth/me')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateMe(data: { name?: string }) {
|
||||||
|
return this.request<{
|
||||||
|
id: number
|
||||||
|
email: string
|
||||||
|
name: string | null
|
||||||
|
is_active: boolean
|
||||||
|
created_at: string
|
||||||
|
}>('/auth/me', {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Password Reset
|
// Password Reset
|
||||||
async forgotPassword(email: string) {
|
async forgotPassword(email: string) {
|
||||||
return this.request<{ message: string; success: boolean }>('/auth/forgot-password', {
|
return this.request<{ message: string; success: boolean }>('/auth/forgot-password', {
|
||||||
|
|||||||
Reference in New Issue
Block a user