Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
1import { useState } from 'react' 2 3export interface CustomDomain { 4 id: string 5 domain: string 6 did: string 7 rkey: string 8 verified: boolean 9 last_verified_at: number | null 10 created_at: number 11} 12 13export interface WispDomain { 14 domain: string 15 rkey: string | null 16} 17 18type VerificationStatus = 'idle' | 'verifying' | 'success' | 'error' 19 20export function useDomainData() { 21 const [wispDomains, setWispDomains] = useState<WispDomain[]>([]) 22 const [customDomains, setCustomDomains] = useState<CustomDomain[]>([]) 23 const [domainsLoading, setDomainsLoading] = useState(true) 24 const [verificationStatus, setVerificationStatus] = useState<{ 25 [id: string]: VerificationStatus 26 }>({}) 27 28 const fetchDomains = async () => { 29 try { 30 const response = await fetch('/api/user/domains') 31 const data = await response.json() 32 setWispDomains(data.wispDomains || []) 33 setCustomDomains(data.customDomains || []) 34 } catch (err) { 35 console.error('Failed to fetch domains:', err) 36 } finally { 37 setDomainsLoading(false) 38 } 39 } 40 41 const addCustomDomain = async (domain: string) => { 42 try { 43 const response = await fetch('/api/domain/custom/add', { 44 method: 'POST', 45 headers: { 'Content-Type': 'application/json' }, 46 body: JSON.stringify({ domain }) 47 }) 48 49 const data = await response.json() 50 if (data.success) { 51 await fetchDomains() 52 return { success: true, id: data.id } 53 } else { 54 throw new Error(data.error || 'Failed to add domain') 55 } 56 } catch (err) { 57 console.error('Add domain error:', err) 58 alert( 59 `Failed to add domain: ${err instanceof Error ? err.message : 'Unknown error'}` 60 ) 61 return { success: false } 62 } 63 } 64 65 const verifyDomain = async (id: string) => { 66 setVerificationStatus({ ...verificationStatus, [id]: 'verifying' }) 67 68 try { 69 const response = await fetch('/api/domain/custom/verify', { 70 method: 'POST', 71 headers: { 'Content-Type': 'application/json' }, 72 body: JSON.stringify({ id }) 73 }) 74 75 const data = await response.json() 76 if (data.success && data.verified) { 77 setVerificationStatus({ ...verificationStatus, [id]: 'success' }) 78 await fetchDomains() 79 } else { 80 setVerificationStatus({ ...verificationStatus, [id]: 'error' }) 81 if (data.error) { 82 alert(`Verification failed: ${data.error}`) 83 } 84 } 85 } catch (err) { 86 console.error('Verify domain error:', err) 87 setVerificationStatus({ ...verificationStatus, [id]: 'error' }) 88 alert( 89 `Verification failed: ${err instanceof Error ? err.message : 'Unknown error'}` 90 ) 91 } 92 } 93 94 const deleteCustomDomain = async (id: string) => { 95 if (!confirm('Are you sure you want to remove this custom domain?')) { 96 return false 97 } 98 99 try { 100 const response = await fetch(`/api/domain/custom/${id}`, { 101 method: 'DELETE' 102 }) 103 104 const data = await response.json() 105 if (data.success) { 106 await fetchDomains() 107 return true 108 } else { 109 throw new Error('Failed to delete domain') 110 } 111 } catch (err) { 112 console.error('Delete domain error:', err) 113 alert( 114 `Failed to delete domain: ${err instanceof Error ? err.message : 'Unknown error'}` 115 ) 116 return false 117 } 118 } 119 120 const mapWispDomain = async (domain: string, siteRkey: string | null) => { 121 try { 122 const response = await fetch('/api/domain/wisp/map-site', { 123 method: 'POST', 124 headers: { 'Content-Type': 'application/json' }, 125 body: JSON.stringify({ domain, siteRkey }) 126 }) 127 const data = await response.json() 128 if (!data.success) throw new Error('Failed to map wisp domain') 129 return true 130 } catch (err) { 131 console.error('Map wisp domain error:', err) 132 throw err 133 } 134 } 135 136 const deleteWispDomain = async (domain: string) => { 137 if (!confirm('Are you sure you want to remove this wisp.place domain?')) { 138 return false 139 } 140 141 try { 142 const response = await fetch(`/api/domain/wisp/${encodeURIComponent(domain)}`, { 143 method: 'DELETE' 144 }) 145 146 const data = await response.json() 147 if (data.success) { 148 await fetchDomains() 149 return true 150 } else { 151 throw new Error('Failed to delete domain') 152 } 153 } catch (err) { 154 console.error('Delete wisp domain error:', err) 155 alert( 156 `Failed to delete domain: ${err instanceof Error ? err.message : 'Unknown error'}` 157 ) 158 return false 159 } 160 } 161 162 const mapCustomDomain = async (domainId: string, siteRkey: string | null) => { 163 try { 164 const response = await fetch(`/api/domain/custom/${domainId}/map-site`, { 165 method: 'POST', 166 headers: { 'Content-Type': 'application/json' }, 167 body: JSON.stringify({ siteRkey }) 168 }) 169 const data = await response.json() 170 if (!data.success) throw new Error(`Failed to map custom domain ${domainId}`) 171 return true 172 } catch (err) { 173 console.error('Map custom domain error:', err) 174 throw err 175 } 176 } 177 178 const claimWispDomain = async (handle: string) => { 179 try { 180 const response = await fetch('/api/domain/claim', { 181 method: 'POST', 182 headers: { 'Content-Type': 'application/json' }, 183 body: JSON.stringify({ handle }) 184 }) 185 186 const data = await response.json() 187 if (data.success) { 188 await fetchDomains() 189 return { success: true } 190 } else { 191 throw new Error(data.error || 'Failed to claim domain') 192 } 193 } catch (err) { 194 console.error('Claim domain error:', err) 195 const errorMessage = err instanceof Error ? err.message : 'Unknown error' 196 197 // Handle domain limit error more gracefully 198 if (errorMessage.includes('Domain limit reached')) { 199 alert('You have already claimed 3 wisp.place subdomains (maximum limit).') 200 await fetchDomains() 201 } else { 202 alert(`Failed to claim domain: ${errorMessage}`) 203 } 204 return { success: false, error: errorMessage } 205 } 206 } 207 208 const checkWispAvailability = async (handle: string) => { 209 const trimmedHandle = handle.trim().toLowerCase() 210 if (!trimmedHandle) { 211 return { available: null } 212 } 213 214 try { 215 const response = await fetch(`/api/domain/check?handle=${encodeURIComponent(trimmedHandle)}`) 216 const data = await response.json() 217 return { available: data.available } 218 } catch (err) { 219 console.error('Check availability error:', err) 220 return { available: false } 221 } 222 } 223 224 return { 225 wispDomains, 226 customDomains, 227 domainsLoading, 228 verificationStatus, 229 fetchDomains, 230 addCustomDomain, 231 verifyDomain, 232 deleteCustomDomain, 233 mapWispDomain, 234 deleteWispDomain, 235 mapCustomDomain, 236 claimWispDomain, 237 checkWispAvailability 238 } 239}