pounce/frontend/src/lib/store.ts
yves.gugger 9acc40b658
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
feat: Complete Watchlist monitoring, Portfolio tracking & Listings marketplace
## Watchlist & Monitoring
-  Automatic domain monitoring based on subscription tier
-  Email alerts when domains become available
-  Health checks (DNS/HTTP/SSL) with caching
-  Expiry warnings for domains <30 days
-  Weekly digest emails
-  Instant alert toggle (optimistic UI updates)
-  Redesigned health check overlays with full details
- 🔒 'Not public' display for .ch/.de domains without public expiry

## Portfolio Management (NEW)
-  Track owned domains with purchase price & date
-  ROI calculation (unrealized & realized)
-  Domain valuation with auto-refresh
-  Renewal date tracking
-  Sale recording with profit calculation
-  List domains for sale directly from portfolio
-  Full portfolio summary dashboard

## Listings / For Sale
-  Renamed from 'Portfolio' to 'For Sale'
-  Fixed listing limits: Scout=0, Trader=5, Tycoon=50
-  Featured badge for Tycoon listings
-  Inquiries modal for sellers
-  Email notifications when buyer inquires
-  Inquiries column in listings table

## Scrapers & Data
-  Added 4 new registrar scrapers (Namecheap, Cloudflare, GoDaddy, Dynadot)
-  Increased scraping frequency to 2x daily (03:00 & 15:00 UTC)
-  Real historical data from database
-  Fixed RDAP/WHOIS for .ch/.de domains
-  Enhanced SSL certificate parsing

## Scheduler Jobs
-  Tiered domain checks (Scout=daily, Trader=hourly, Tycoon=10min)
-  Daily health checks (06:00 UTC)
-  Weekly expiry warnings (Mon 08:00 UTC)
-  Weekly digest emails (Sun 10:00 UTC)
-  Auction cleanup every 15 minutes

## UI/UX Improvements
-  Removed 'Back' buttons from Intel pages
-  Redesigned Radar page to match Market/Intel design
-  Less prominent check frequency footer
-  Consistent StatCard components across all pages
-  Ambient background glows
-  Better error handling

## Documentation
-  Updated README with monitoring section
-  Added env.example with all required variables
-  Updated Memory Bank (activeContext.md)
-  SMTP configuration requirements documented
2025-12-11 16:57:28 +01:00

208 lines
4.7 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)
// Auto-login after registration
await get().login(email, password)
},
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 {
if (api.getToken()) {
const user = await api.getMe()
set({ user, isAuthenticated: true })
// Fetch in parallel for speed
await Promise.all([
get().fetchDomains(),
get().fetchSubscription()
])
}
} catch {
api.logout()
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
}
},
}))