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
207 lines
4.8 KiB
TypeScript
207 lines
4.8 KiB
TypeScript
/**
|
|
* Global state management with Zustand
|
|
*/
|
|
import { create } from 'zustand'
|
|
import { api } from './api'
|
|
|
|
interface User {
|
|
id: number
|
|
email: string
|
|
name: string | null
|
|
is_admin?: boolean
|
|
is_verified?: boolean
|
|
}
|
|
|
|
interface Domain {
|
|
id: number
|
|
name: string
|
|
status: string
|
|
is_available: boolean
|
|
registrar: string | null
|
|
expiration_date: string | null
|
|
notify_on_available: boolean
|
|
created_at: string
|
|
last_checked: string | null
|
|
}
|
|
|
|
interface Subscription {
|
|
tier: string
|
|
tier_name?: string
|
|
domain_limit: number
|
|
domains_used: number
|
|
portfolio_limit?: number
|
|
check_frequency?: string
|
|
history_days?: number
|
|
features?: {
|
|
email_alerts: boolean
|
|
priority_alerts: boolean
|
|
full_whois: boolean
|
|
expiration_tracking: boolean
|
|
domain_valuation: boolean
|
|
market_insights: boolean
|
|
api_access: boolean
|
|
webhooks: boolean
|
|
bulk_tools: boolean
|
|
seo_metrics: boolean
|
|
}
|
|
}
|
|
|
|
interface AppState {
|
|
// Auth
|
|
user: User | null
|
|
isAuthenticated: boolean
|
|
isLoading: boolean
|
|
|
|
// Domains
|
|
domains: Domain[]
|
|
domainsTotal: number
|
|
domainsPage: number
|
|
|
|
// Subscription
|
|
subscription: Subscription | null
|
|
|
|
// Actions
|
|
login: (email: string, password: string) => Promise<void>
|
|
register: (email: string, password: string, name?: string) => Promise<void>
|
|
logout: () => void
|
|
checkAuth: () => Promise<void>
|
|
|
|
fetchDomains: (page?: number) => Promise<void>
|
|
addDomain: (name: string) => Promise<void>
|
|
deleteDomain: (id: number) => Promise<void>
|
|
refreshDomain: (id: number) => Promise<void>
|
|
updateDomain: (id: number, updates: Partial<Domain>) => void
|
|
|
|
fetchSubscription: () => Promise<void>
|
|
}
|
|
|
|
export const useStore = create<AppState>((set, get) => ({
|
|
// Initial state
|
|
user: null,
|
|
isAuthenticated: false,
|
|
isLoading: true,
|
|
domains: [],
|
|
domainsTotal: 0,
|
|
domainsPage: 1,
|
|
subscription: null,
|
|
|
|
// Auth actions
|
|
login: async (email, password) => {
|
|
await api.login(email, password)
|
|
const user = await api.getMe()
|
|
set({ user, isAuthenticated: true, isLoading: false })
|
|
|
|
// Fetch user data (only once after login)
|
|
await Promise.all([
|
|
get().fetchDomains(),
|
|
get().fetchSubscription()
|
|
])
|
|
},
|
|
|
|
register: async (email, password, name) => {
|
|
await api.register(email, password, name)
|
|
// Note: No auto-login after registration
|
|
// User should verify email first (verification email is sent)
|
|
// They can then log in manually via the login page
|
|
},
|
|
|
|
logout: () => {
|
|
api.logout()
|
|
set({
|
|
user: null,
|
|
isAuthenticated: false,
|
|
domains: [],
|
|
subscription: null,
|
|
})
|
|
},
|
|
|
|
checkAuth: async () => {
|
|
// Skip if already authenticated and have data (prevents redundant fetches)
|
|
const state = get()
|
|
if (state.isAuthenticated && state.user && state.subscription) {
|
|
set({ isLoading: false })
|
|
return
|
|
}
|
|
|
|
set({ isLoading: true })
|
|
try {
|
|
// Cookie-based auth: if cookie is present and valid, /auth/me succeeds.
|
|
const user = await api.getMe()
|
|
set({ user, isAuthenticated: true })
|
|
|
|
// Fetch in parallel for speed
|
|
await Promise.all([
|
|
get().fetchDomains(),
|
|
get().fetchSubscription()
|
|
])
|
|
} catch {
|
|
set({ user: null, isAuthenticated: false })
|
|
} finally {
|
|
set({ isLoading: false })
|
|
}
|
|
},
|
|
|
|
// Domain actions
|
|
fetchDomains: async (page = 1) => {
|
|
try {
|
|
const response = await api.getDomains(page)
|
|
set({
|
|
domains: response.domains,
|
|
domainsTotal: response.total,
|
|
domainsPage: response.page,
|
|
})
|
|
} catch (error) {
|
|
console.error('Failed to fetch domains:', error)
|
|
}
|
|
},
|
|
|
|
addDomain: async (name) => {
|
|
await api.addDomain(name)
|
|
await get().fetchDomains(get().domainsPage)
|
|
await get().fetchSubscription()
|
|
},
|
|
|
|
deleteDomain: async (id) => {
|
|
await api.deleteDomain(id)
|
|
await get().fetchDomains(get().domainsPage)
|
|
await get().fetchSubscription()
|
|
},
|
|
|
|
refreshDomain: async (id) => {
|
|
const updated = await api.refreshDomain(id)
|
|
const domains = get().domains.map((d) =>
|
|
d.id === id ? { ...d, ...updated } : d
|
|
)
|
|
set({ domains })
|
|
},
|
|
|
|
updateDomain: (id, updates) => {
|
|
const domains = get().domains.map((d) =>
|
|
d.id === id ? { ...d, ...updates } : d
|
|
)
|
|
set({ domains })
|
|
},
|
|
|
|
// Subscription actions
|
|
fetchSubscription: async () => {
|
|
try {
|
|
const sub = await api.getSubscription()
|
|
set({
|
|
subscription: {
|
|
tier: sub.tier,
|
|
tier_name: sub.tier_name,
|
|
domain_limit: sub.domain_limit,
|
|
domains_used: sub.domains_used,
|
|
portfolio_limit: sub.portfolio_limit,
|
|
check_frequency: sub.check_frequency,
|
|
history_days: sub.history_days,
|
|
features: sub.features,
|
|
},
|
|
})
|
|
} catch {
|
|
// User might not have subscription
|
|
}
|
|
},
|
|
}))
|
|
|