import { useState, useEffect } from 'react' import { createRoot } from 'react-dom/client' import { Button } from '@public/components/ui/button' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@public/components/ui/tabs' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@public/components/ui/dialog' import { Checkbox } from '@public/components/ui/checkbox' import { Label } from '@public/components/ui/label' import { Badge } from '@public/components/ui/badge' import { SkeletonShimmer } from '@public/components/ui/skeleton' import { Loader2, Trash2, LogOut } from 'lucide-react' import Layout from '@public/layouts' import { useUserInfo } from './hooks/useUserInfo' import { useSiteData, type SiteWithDomains } from './hooks/useSiteData' import { useDomainData } from './hooks/useDomainData' import { SitesTab } from './tabs/SitesTab' import { DomainsTab } from './tabs/DomainsTab' import { UploadTab } from './tabs/UploadTab' import { CLITab } from './tabs/CLITab' function Dashboard() { // Use custom hooks const { userInfo, loading, fetchUserInfo } = useUserInfo() const { sites, sitesLoading, isSyncing, fetchSites, syncSites, deleteSite } = useSiteData() const { wispDomains, customDomains, domainsLoading, verificationStatus, fetchDomains, addCustomDomain, verifyDomain, deleteCustomDomain, mapWispDomain, deleteWispDomain, mapCustomDomain, claimWispDomain, checkWispAvailability } = useDomainData() // Site configuration modal state (shared across components) const [configuringSite, setConfiguringSite] = useState(null) const [selectedDomains, setSelectedDomains] = useState>(new Set()) const [isSavingConfig, setIsSavingConfig] = useState(false) const [isDeletingSite, setIsDeletingSite] = useState(false) // Fetch initial data on mount useEffect(() => { fetchUserInfo() fetchSites() fetchDomains() }, []) // Handle site configuration modal const handleConfigureSite = (site: SiteWithDomains) => { setConfiguringSite(site) // Build set of currently mapped domains const mappedDomains = new Set() if (site.domains) { site.domains.forEach(domainInfo => { if (domainInfo.type === 'wisp') { // For wisp domains, use the domain itself as the identifier mappedDomains.add(`wisp:${domainInfo.domain}`) } else if (domainInfo.id) { mappedDomains.add(domainInfo.id) } }) } setSelectedDomains(mappedDomains) } const handleSaveSiteConfig = async () => { if (!configuringSite) return setIsSavingConfig(true) try { // Handle wisp domain mappings const selectedWispDomainIds = Array.from(selectedDomains).filter(id => id.startsWith('wisp:')) const selectedWispDomains = selectedWispDomainIds.map(id => id.replace('wisp:', '')) // Get currently mapped wisp domains const currentlyMappedWispDomains = wispDomains.filter( d => d.rkey === configuringSite.rkey ) // Unmap wisp domains that are no longer selected for (const domain of currentlyMappedWispDomains) { if (!selectedWispDomains.includes(domain.domain)) { await mapWispDomain(domain.domain, null) } } // Map newly selected wisp domains for (const domainName of selectedWispDomains) { const isAlreadyMapped = currentlyMappedWispDomains.some(d => d.domain === domainName) if (!isAlreadyMapped) { await mapWispDomain(domainName, configuringSite.rkey) } } // Handle custom domain mappings const selectedCustomDomainIds = Array.from(selectedDomains).filter(id => !id.startsWith('wisp:')) const currentlyMappedCustomDomains = customDomains.filter( d => d.rkey === configuringSite.rkey ) // Unmap domains that are no longer selected for (const domain of currentlyMappedCustomDomains) { if (!selectedCustomDomainIds.includes(domain.id)) { await mapCustomDomain(domain.id, null) } } // Map newly selected domains for (const domainId of selectedCustomDomainIds) { const isAlreadyMapped = currentlyMappedCustomDomains.some(d => d.id === domainId) if (!isAlreadyMapped) { await mapCustomDomain(domainId, configuringSite.rkey) } } // Refresh both domains and sites to get updated mappings await fetchDomains() await fetchSites() setConfiguringSite(null) } catch (err) { console.error('Save config error:', err) alert( `Failed to save configuration: ${err instanceof Error ? err.message : 'Unknown error'}` ) } finally { setIsSavingConfig(false) } } const handleDeleteSite = async () => { if (!configuringSite) return if (!confirm(`Are you sure you want to delete "${configuringSite.display_name || configuringSite.rkey}"? This action cannot be undone.`)) { return } setIsDeletingSite(true) const success = await deleteSite(configuringSite.rkey) if (success) { // Refresh domains in case this site was mapped await fetchDomains() setConfiguringSite(null) } setIsDeletingSite(false) } const handleUploadComplete = async () => { await fetchSites() } const handleLogout = async () => { try { const response = await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }) const result = await response.json() if (result.success) { // Redirect to home page after successful logout window.location.href = '/' } else { alert('Logout failed: ' + (result.error || 'Unknown error')) } } catch (err) { alert('Logout failed: ' + (err instanceof Error ? err.message : 'Unknown error')) } } if (loading) { return (
{/* Header Skeleton */}
wisp.place wisp.place
{/* Title Skeleton */}
{/* Tabs Skeleton */}
{/* Content Skeleton */}
{[...Array(3)].map((_, i) => (
))}
) } return (
{/* Header */}
wisp.place wisp.place
{userInfo?.handle || 'Loading...'}

Dashboard

Manage your sites and domains

Sites Domains Upload CLI {/* Sites Tab */} {/* Domains Tab */} {/* Upload Tab */} {/* CLI Tab */}
{/* Footer */} {/* Site Configuration Modal */} !open && setConfiguringSite(null)} > Configure Site Domains Select which domains should be mapped to this site. You can select multiple domains. {configuringSite && (

Site:

{configuringSite.display_name || configuringSite.rkey}

Available Domains:

{wispDomains.map((wispDomain) => { const domainId = `wisp:${wispDomain.domain}` return (
{ const newSelected = new Set(selectedDomains) if (checked) { newSelected.add(domainId) } else { newSelected.delete(domainId) } setSelectedDomains(newSelected) }} />
) })} {customDomains .filter((d) => d.verified) .map((domain) => (
{ const newSelected = new Set(selectedDomains) if (checked) { newSelected.add(domain.id) } else { newSelected.delete(domain.id) } setSelectedDomains(newSelected) }} />
))} {customDomains.filter(d => d.verified).length === 0 && wispDomains.length === 0 && (

No domains available. Add a custom domain or claim a wisp.place subdomain.

)}

Note: If no domains are selected, the site will be accessible at:{' '} sites.wisp.place/{userInfo?.handle || '...'}/{configuringSite.rkey}

)}
) } const root = createRoot(document.getElementById('elysia')!) root.render( )