Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place

delete old files

-907
-379
apps/main-app/public/acceptable-use/acceptable-use.tsx
··· 1 - import { createRoot } from 'react-dom/client' 2 - import Layout from '@public/layouts' 3 - import { Button } from '@public/components/ui/button' 4 - import { Card } from '@public/components/ui/card' 5 - import { ArrowLeft, Shield, AlertCircle, CheckCircle, Scale } from 'lucide-react' 6 - 7 - function AcceptableUsePage() { 8 - return ( 9 - <div className="w-full min-h-screen bg-background flex flex-col"> 10 - {/* Header */} 11 - <header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50"> 12 - <div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between"> 13 - <div className="flex items-center gap-2"> 14 - <img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" /> 15 - <span className="text-xl font-semibold text-foreground"> 16 - wisp.place 17 - </span> 18 - </div> 19 - <Button 20 - variant="ghost" 21 - size="sm" 22 - onClick={() => window.location.href = '/'} 23 - > 24 - <ArrowLeft className="w-4 h-4 mr-2" /> 25 - Back to Home 26 - </Button> 27 - </div> 28 - </header> 29 - 30 - {/* Hero Section */} 31 - <div className="bg-gradient-to-b from-accent/10 to-background border-b border-border/40"> 32 - <div className="container mx-auto px-4 py-16 max-w-4xl text-center"> 33 - <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-accent/20 mb-6"> 34 - <Shield className="w-8 h-8 text-accent" /> 35 - </div> 36 - <h1 className="text-4xl md:text-5xl font-bold mb-4">Acceptable Use Policy</h1> 37 - <div className="flex items-center justify-center gap-6 text-sm text-muted-foreground"> 38 - <div className="flex items-center gap-2"> 39 - <span className="font-medium">Effective:</span> 40 - <span>November 10, 2025</span> 41 - </div> 42 - <div className="h-4 w-px bg-border"></div> 43 - <div className="flex items-center gap-2"> 44 - <span className="font-medium">Last Updated:</span> 45 - <span>November 10, 2025</span> 46 - </div> 47 - </div> 48 - </div> 49 - </div> 50 - 51 - {/* Content */} 52 - <div className="container mx-auto px-4 py-12 max-w-4xl"> 53 - <article className="space-y-12"> 54 - {/* Our Philosophy */} 55 - <section> 56 - <h2 className="text-3xl font-bold mb-6 text-foreground">Our Philosophy</h2> 57 - <div className="space-y-4 text-lg leading-relaxed text-muted-foreground"> 58 - <p> 59 - wisp.place exists to give you a corner of the internet that's truly yours—a place to create, experiment, and express yourself freely. We believe in the open web and the fundamental importance of free expression. We're not here to police your thoughts, moderate your aesthetics, or judge your taste. 60 - </p> 61 - <p> 62 - That said, we're also real people running real servers in real jurisdictions (the United States and the Netherlands), and there are legal and practical limits to what we can host. This policy aims to be as permissive as possible while keeping the lights on and staying on the right side of the law. 63 - </p> 64 - </div> 65 - </section> 66 - 67 - {/* What You Can Do */} 68 - <Card className="bg-green-500/5 border-green-500/20 p-8"> 69 - <div className="flex items-start gap-4"> 70 - <div className="flex-shrink-0"> 71 - <CheckCircle className="w-8 h-8 text-green-500" /> 72 - </div> 73 - <div className="space-y-4"> 74 - <h2 className="text-3xl font-bold text-foreground">What You Can Do</h2> 75 - <div className="space-y-4 text-lg leading-relaxed text-muted-foreground"> 76 - <p> 77 - <strong className="text-green-600 dark:text-green-400">Almost anything.</strong> Seriously. Build weird art projects. Write controversial essays. Create spaces that would make corporate platforms nervous. Express unpopular opinions. Make things that are strange, provocative, uncomfortable, or just plain yours. 78 - </p> 79 - <p> 80 - We support creative and personal expression in all its forms, including adult content, political speech, counter-cultural work, and experimental projects. 81 - </p> 82 - </div> 83 - </div> 84 - </div> 85 - </Card> 86 - 87 - {/* What You Can't Do */} 88 - <section> 89 - <div className="flex items-center gap-3 mb-6"> 90 - <AlertCircle className="w-8 h-8 text-red-500" /> 91 - <h2 className="text-3xl font-bold text-foreground">What You Can't Do</h2> 92 - </div> 93 - 94 - <div className="space-y-8"> 95 - <Card className="p-6 border-2"> 96 - <h3 className="text-2xl font-semibold mb-4 text-foreground">Illegal Content</h3> 97 - <p className="text-muted-foreground mb-4"> 98 - Don't host content that's illegal in the United States or the Netherlands. This includes but isn't limited to: 99 - </p> 100 - <ul className="space-y-3 text-muted-foreground"> 101 - <li className="flex items-start gap-3"> 102 - <span className="text-red-500 mt-1">•</span> 103 - <span><strong>Child sexual abuse material (CSAM)</strong> involving real minors in any form</span> 104 - </li> 105 - <li className="flex items-start gap-3"> 106 - <span className="text-red-500 mt-1">•</span> 107 - <span><strong>Realistic or AI-generated depictions</strong> of minors in sexual contexts, including photorealistic renders, deepfakes, or AI-generated imagery</span> 108 - </li> 109 - <li className="flex items-start gap-3"> 110 - <span className="text-red-500 mt-1">•</span> 111 - <span><strong>Non-consensual intimate imagery</strong> (revenge porn, deepfakes, hidden camera footage, etc.)</span> 112 - </li> 113 - <li className="flex items-start gap-3"> 114 - <span className="text-red-500 mt-1">•</span> 115 - <span>Content depicting or facilitating human trafficking, sexual exploitation, or sexual violence</span> 116 - </li> 117 - <li className="flex items-start gap-3"> 118 - <span className="text-red-500 mt-1">•</span> 119 - <span>Instructions for manufacturing explosives, biological weapons, or other instruments designed for mass harm</span> 120 - </li> 121 - <li className="flex items-start gap-3"> 122 - <span className="text-red-500 mt-1">•</span> 123 - <span>Content that facilitates imminent violence or terrorism</span> 124 - </li> 125 - <li className="flex items-start gap-3"> 126 - <span className="text-red-500 mt-1">•</span> 127 - <span>Stolen financial information, credentials, or personal data used for fraud</span> 128 - </li> 129 - </ul> 130 - </Card> 131 - 132 - <Card className="p-6 border-2"> 133 - <h3 className="text-2xl font-semibold mb-4 text-foreground">Intellectual Property Violations</h3> 134 - <div className="space-y-4 text-muted-foreground"> 135 - <p> 136 - Don't host content that clearly violates someone else's copyright, trademark, or other intellectual property rights. We're required to respond to valid DMCA takedown notices. 137 - </p> 138 - <p> 139 - We understand that copyright law is complicated and sometimes ridiculous. We're not going to proactively scan your site or nitpick over fair use. But if we receive a legitimate legal complaint, we'll have to act on it. 140 - </p> 141 - </div> 142 - </Card> 143 - 144 - <Card className="p-6 border-2 border-red-500/30 bg-red-500/5"> 145 - <h3 className="text-2xl font-semibold mb-4 text-foreground">Hate Content</h3> 146 - <div className="space-y-4 text-muted-foreground"> 147 - <p> 148 - You can express controversial ideas. You can be offensive. You can make people uncomfortable. But pure hate—content that exists solely to dehumanize, threaten, or incite violence against people based on race, ethnicity, religion, gender, sexual orientation, disability, or similar characteristics—isn't welcome here. 149 - </p> 150 - <p> 151 - There's a difference between "I have deeply unpopular opinions about X" and "People like X should be eliminated." The former is protected expression. The latter isn't. 152 - </p> 153 - <div className="bg-background/50 border-l-4 border-red-500 p-4 rounded"> 154 - <p className="font-medium text-foreground"> 155 - <strong>A note on enforcement:</strong> While we're generally permissive and believe in giving people the benefit of the doubt, hate content is where we draw a hard line. I will be significantly more aggressive in moderating this type of content than anything else on this list. If your site exists primarily to spread hate or recruit people into hateful ideologies, you will be removed swiftly and without extensive appeals. This is non-negotiable. 156 - </p> 157 - </div> 158 - </div> 159 - </Card> 160 - 161 - <Card className="p-6 border-2"> 162 - <h3 className="text-2xl font-semibold mb-4 text-foreground">Adult Content Guidelines</h3> 163 - <div className="space-y-4 text-muted-foreground"> 164 - <p> 165 - Adult content is allowed. This includes sexually explicit material, erotica, adult artwork, and NSFW creative expression. 166 - </p> 167 - <p className="font-medium">However:</p> 168 - <ul className="space-y-2"> 169 - <li className="flex items-start gap-3"> 170 - <span className="text-red-500 mt-1">•</span> 171 - <span>No content involving real minors in any sexual context whatsoever</span> 172 - </li> 173 - <li className="flex items-start gap-3"> 174 - <span className="text-red-500 mt-1">•</span> 175 - <span>No photorealistic, AI-generated, or otherwise realistic depictions of minors in sexual contexts</span> 176 - </li> 177 - <li className="flex items-start gap-3"> 178 - <span className="text-green-500 mt-1">•</span> 179 - <span>Clearly stylized drawings and written fiction are permitted, provided they remain obviously non-photographic in nature</span> 180 - </li> 181 - <li className="flex items-start gap-3"> 182 - <span className="text-red-500 mt-1">•</span> 183 - <span>No non-consensual content (revenge porn, voyeurism, etc.)</span> 184 - </li> 185 - <li className="flex items-start gap-3"> 186 - <span className="text-red-500 mt-1">•</span> 187 - <span>No content depicting illegal sexual acts (bestiality, necrophilia, etc.)</span> 188 - </li> 189 - <li className="flex items-start gap-3"> 190 - <span className="text-yellow-500 mt-1">•</span> 191 - <span>Adult content should be clearly marked as such if discoverable through public directories or search</span> 192 - </li> 193 - </ul> 194 - </div> 195 - </Card> 196 - 197 - <Card className="p-6 border-2"> 198 - <h3 className="text-2xl font-semibold mb-4 text-foreground">Malicious Technical Activity</h3> 199 - <p className="text-muted-foreground mb-4">Don't use your site to:</p> 200 - <ul className="space-y-2 text-muted-foreground"> 201 - <li className="flex items-start gap-3"> 202 - <span className="text-red-500 mt-1">•</span> 203 - <span>Distribute malware, viruses, or exploits</span> 204 - </li> 205 - <li className="flex items-start gap-3"> 206 - <span className="text-red-500 mt-1">•</span> 207 - <span>Conduct phishing or social engineering attacks</span> 208 - </li> 209 - <li className="flex items-start gap-3"> 210 - <span className="text-red-500 mt-1">•</span> 211 - <span>Launch DDoS attacks or network abuse</span> 212 - </li> 213 - <li className="flex items-start gap-3"> 214 - <span className="text-red-500 mt-1">•</span> 215 - <span>Mine cryptocurrency without explicit user consent</span> 216 - </li> 217 - <li className="flex items-start gap-3"> 218 - <span className="text-red-500 mt-1">•</span> 219 - <span>Scrape, spam, or abuse other services</span> 220 - </li> 221 - </ul> 222 - </Card> 223 - </div> 224 - </section> 225 - 226 - {/* Our Approach to Enforcement */} 227 - <section> 228 - <div className="flex items-center gap-3 mb-6"> 229 - <Scale className="w-8 h-8 text-accent" /> 230 - <h2 className="text-3xl font-bold text-foreground">Our Approach to Enforcement</h2> 231 - </div> 232 - <div className="space-y-6"> 233 - <div className="space-y-4 text-lg leading-relaxed text-muted-foreground"> 234 - <p> 235 - <strong>We actively monitor for obvious violations.</strong> Not to censor your creativity or police your opinions, but to catch the clear-cut stuff that threatens the service's existence and makes this a worse place for everyone. We're looking for the blatantly illegal, the obviously harmful—the stuff that would get servers seized and communities destroyed. 236 - </p> 237 - <p> 238 - We're not reading your blog posts looking for wrongthink. We're making sure this platform doesn't become a haven for the kind of content that ruins good things. 239 - </p> 240 - </div> 241 - 242 - <Card className="p-6 bg-muted/30"> 243 - <p className="font-semibold mb-3 text-foreground">We take action when:</p> 244 - <ol className="space-y-2 text-muted-foreground"> 245 - <li className="flex items-start gap-3"> 246 - <span className="font-bold text-accent">1.</span> 247 - <span>We identify content that clearly violates this policy during routine monitoring</span> 248 - </li> 249 - <li className="flex items-start gap-3"> 250 - <span className="font-bold text-accent">2.</span> 251 - <span>We receive a valid legal complaint (DMCA, court order, etc.)</span> 252 - </li> 253 - <li className="flex items-start gap-3"> 254 - <span className="font-bold text-accent">3.</span> 255 - <span>Someone reports content that violates this policy and we can verify the violation</span> 256 - </li> 257 - <li className="flex items-start gap-3"> 258 - <span className="font-bold text-accent">4.</span> 259 - <span>Your site is causing technical problems for the service or other users</span> 260 - </li> 261 - </ol> 262 - </Card> 263 - 264 - <Card className="p-6 bg-muted/30"> 265 - <p className="font-semibold mb-3 text-foreground">When we do need to take action, we'll try to:</p> 266 - <ul className="space-y-2 text-muted-foreground"> 267 - <li className="flex items-start gap-3"> 268 - <span className="text-accent">•</span> 269 - <span>Contact you first when legally and practically possible</span> 270 - </li> 271 - <li className="flex items-start gap-3"> 272 - <span className="text-accent">•</span> 273 - <span>Be transparent about what's happening and why</span> 274 - </li> 275 - <li className="flex items-start gap-3"> 276 - <span className="text-accent">•</span> 277 - <span>Give you an opportunity to address the issue if appropriate</span> 278 - </li> 279 - </ul> 280 - </Card> 281 - 282 - <p className="text-muted-foreground"> 283 - For serious or repeated violations, we may suspend or terminate your account. 284 - </p> 285 - </div> 286 - </section> 287 - 288 - {/* Regional Compliance */} 289 - <Card className="p-6 bg-blue-500/5 border-blue-500/20"> 290 - <h2 className="text-2xl font-bold mb-4 text-foreground">Regional Compliance</h2> 291 - <p className="text-muted-foreground"> 292 - Our servers are located in the United States and the Netherlands. Content hosted on wisp.place must comply with the laws of both jurisdictions. While we aim to provide broad creative freedom, these legal requirements are non-negotiable. 293 - </p> 294 - </Card> 295 - 296 - {/* Changes to This Policy */} 297 - <section> 298 - <h2 className="text-2xl font-bold mb-4 text-foreground">Changes to This Policy</h2> 299 - <p className="text-muted-foreground"> 300 - We may update this policy as legal requirements or service realities change. If we make significant changes, we'll notify active users. 301 - </p> 302 - </section> 303 - 304 - {/* Questions or Reports */} 305 - <section> 306 - <h2 className="text-2xl font-bold mb-4 text-foreground">Questions or Reports</h2> 307 - <p className="text-muted-foreground"> 308 - If you have questions about this policy or need to report a violation, contact us at{' '} 309 - <a 310 - href="mailto:contact@wisp.place" 311 - className="text-accent hover:text-accent/80 transition-colors font-medium" 312 - > 313 - contact@wisp.place 314 - </a> 315 - . 316 - </p> 317 - </section> 318 - 319 - {/* Final Message */} 320 - <Card className="p-8 bg-accent/10 border-accent/30 border-2"> 321 - <p className="text-lg leading-relaxed text-foreground"> 322 - <strong>Remember:</strong> This policy exists to keep the service running and this community healthy, not to limit your creativity. When in doubt, ask yourself: "Is this likely to get real-world authorities knocking on doors or make this place worse for everyone?" If the answer is yes, it probably doesn't belong here. Everything else? Go wild. 323 - </p> 324 - </Card> 325 - </article> 326 - </div> 327 - 328 - {/* Footer */} 329 - <footer className="border-t border-border/40 bg-muted/20 mt-auto"> 330 - <div className="container mx-auto px-4 py-8"> 331 - <div className="text-center text-sm text-muted-foreground"> 332 - <p> 333 - Built by{' '} 334 - <a 335 - href="https://bsky.app/profile/nekomimi.pet" 336 - target="_blank" 337 - rel="noopener noreferrer" 338 - className="text-accent hover:text-accent/80 transition-colors font-medium" 339 - > 340 - @nekomimi.pet 341 - </a> 342 - {' • '} 343 - Contact:{' '} 344 - <a 345 - href="mailto:contact@wisp.place" 346 - className="text-accent hover:text-accent/80 transition-colors font-medium" 347 - > 348 - contact@wisp.place 349 - </a> 350 - {' • '} 351 - Legal/DMCA:{' '} 352 - <a 353 - href="mailto:legal@wisp.place" 354 - className="text-accent hover:text-accent/80 transition-colors font-medium" 355 - > 356 - legal@wisp.place 357 - </a> 358 - </p> 359 - <p className="mt-2"> 360 - <a 361 - href="/acceptable-use" 362 - className="text-accent hover:text-accent/80 transition-colors font-medium" 363 - > 364 - Acceptable Use Policy 365 - </a> 366 - </p> 367 - </div> 368 - </div> 369 - </footer> 370 - </div> 371 - ) 372 - } 373 - 374 - const root = createRoot(document.getElementById('elysia')!) 375 - root.render( 376 - <Layout className="gap-6"> 377 - <AcceptableUsePage /> 378 - </Layout> 379 - )
-35
apps/main-app/public/acceptable-use/index.html
··· 1 - <!doctype html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8" /> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 - <title>Acceptable Use Policy - wisp.place</title> 7 - <meta name="description" content="Acceptable Use Policy for wisp.place - Guidelines for hosting content on our decentralized static site hosting platform." /> 8 - 9 - <!-- Open Graph / Facebook --> 10 - <meta property="og:type" content="website" /> 11 - <meta property="og:url" content="https://wisp.place/acceptable-use" /> 12 - <meta property="og:title" content="Acceptable Use Policy - wisp.place" /> 13 - <meta property="og:description" content="Acceptable Use Policy for wisp.place - Guidelines for hosting content on our decentralized static site hosting platform." /> 14 - <meta property="og:site_name" content="wisp.place" /> 15 - 16 - <!-- Twitter --> 17 - <meta name="twitter:card" content="summary_large_image" /> 18 - <meta name="twitter:url" content="https://wisp.place/acceptable-use" /> 19 - <meta name="twitter:title" content="Acceptable Use Policy - wisp.place" /> 20 - <meta name="twitter:description" content="Acceptable Use Policy for wisp.place - Guidelines for hosting content on our decentralized static site hosting platform." /> 21 - 22 - <!-- Theme --> 23 - <meta name="theme-color" content="#7c3aed" /> 24 - 25 - <link rel="icon" type="image/x-icon" href="../favicon.ico"> 26 - <link rel="icon" type="image/png" sizes="32x32" href="../favicon-32x32.png"> 27 - <link rel="icon" type="image/png" sizes="16x16" href="../favicon-16x16.png"> 28 - <link rel="apple-touch-icon" sizes="180x180" href="../apple-touch-icon.png"> 29 - <link rel="manifest" href="../site.webmanifest"> 30 - </head> 31 - <body> 32 - <div id="elysia"></div> 33 - <script type="module" src="./acceptable-use.tsx"></script> 34 - </body> 35 - </html>
-29
apps/main-app/public/onboarding/index.html
··· 1 - <!doctype html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8" /> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 - <title>wisp.place</title> 7 - <meta name="description" content="Get started with wisp.place and host your first decentralized static site on AT Protocol." /> 8 - 9 - <!-- Open Graph / Facebook --> 10 - <meta property="og:type" content="website" /> 11 - <meta property="og:url" content="https://wisp.place/onboarding" /> 12 - <meta property="og:title" content="Get Started - wisp.place" /> 13 - <meta property="og:description" content="Get started with wisp.place and host your first decentralized static site on AT Protocol." /> 14 - <meta property="og:site_name" content="wisp.place" /> 15 - 16 - <!-- Twitter --> 17 - <meta name="twitter:card" content="summary" /> 18 - <meta name="twitter:url" content="https://wisp.place/onboarding" /> 19 - <meta name="twitter:title" content="Get Started - wisp.place" /> 20 - <meta name="twitter:description" content="Get started with wisp.place and host your first decentralized static site on AT Protocol." /> 21 - 22 - <!-- Theme --> 23 - <meta name="theme-color" content="#7c3aed" /> 24 - </head> 25 - <body> 26 - <div id="elysia"></div> 27 - <script type="module" src="./onboarding.tsx"></script> 28 - </body> 29 - </html>
-464
apps/main-app/public/onboarding/onboarding.tsx
··· 1 - import { useState, useEffect } from 'react' 2 - import { createRoot } from 'react-dom/client' 3 - import { Button } from '@public/components/ui/button' 4 - import { 5 - Card, 6 - CardContent, 7 - CardDescription, 8 - CardHeader, 9 - CardTitle 10 - } from '@public/components/ui/card' 11 - import { Input } from '@public/components/ui/input' 12 - import { Label } from '@public/components/ui/label' 13 - import { Globe, Upload, CheckCircle2, Loader2, AlertCircle } from 'lucide-react' 14 - import Layout from '@public/layouts' 15 - 16 - type OnboardingStep = 'domain' | 'upload' | 'complete' 17 - 18 - function Onboarding() { 19 - const [step, setStep] = useState<OnboardingStep>('domain') 20 - const [handle, setHandle] = useState('') 21 - const [isCheckingAvailability, setIsCheckingAvailability] = useState(false) 22 - const [isAvailable, setIsAvailable] = useState<boolean | null>(null) 23 - const [domain, setDomain] = useState('') 24 - const [isClaimingDomain, setIsClaimingDomain] = useState(false) 25 - const [claimedDomain, setClaimedDomain] = useState('') 26 - 27 - const [siteName, setSiteName] = useState('') 28 - const [selectedFiles, setSelectedFiles] = useState<FileList | null>(null) 29 - const [isUploading, setIsUploading] = useState(false) 30 - const [uploadProgress, setUploadProgress] = useState('') 31 - const [skippedFiles, setSkippedFiles] = useState<Array<{ name: string; reason: string }>>([]) 32 - const [uploadedCount, setUploadedCount] = useState(0) 33 - 34 - // Check domain availability as user types 35 - useEffect(() => { 36 - if (!handle || handle.length < 3) { 37 - setIsAvailable(null) 38 - setDomain('') 39 - return 40 - } 41 - 42 - const timeoutId = setTimeout(async () => { 43 - setIsCheckingAvailability(true) 44 - try { 45 - const response = await fetch( 46 - `/api/domain/check?handle=${encodeURIComponent(handle)}` 47 - ) 48 - const data = await response.json() 49 - setIsAvailable(data.available) 50 - setDomain(data.domain || '') 51 - } catch (err) { 52 - console.error('Error checking availability:', err) 53 - setIsAvailable(false) 54 - } finally { 55 - setIsCheckingAvailability(false) 56 - } 57 - }, 500) 58 - 59 - return () => clearTimeout(timeoutId) 60 - }, [handle]) 61 - 62 - const handleClaimDomain = async () => { 63 - if (!handle || !isAvailable) return 64 - 65 - setIsClaimingDomain(true) 66 - try { 67 - const response = await fetch('/api/domain/claim', { 68 - method: 'POST', 69 - headers: { 'Content-Type': 'application/json' }, 70 - body: JSON.stringify({ handle }) 71 - }) 72 - 73 - const data = await response.json() 74 - if (data.success) { 75 - setClaimedDomain(data.domain) 76 - setStep('upload') 77 - } else { 78 - throw new Error(data.error || 'Failed to claim domain') 79 - } 80 - } catch (err) { 81 - console.error('Error claiming domain:', err) 82 - const errorMessage = err instanceof Error ? err.message : 'Unknown error' 83 - 84 - // Handle "Already claimed" error - redirect to editor 85 - if (errorMessage.includes('Already claimed')) { 86 - alert('You have already claimed a wisp.place subdomain. Redirecting to editor...') 87 - window.location.href = '/editor' 88 - } else { 89 - alert(`Failed to claim domain: ${errorMessage}`) 90 - } 91 - } finally { 92 - setIsClaimingDomain(false) 93 - } 94 - } 95 - 96 - const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => { 97 - if (e.target.files && e.target.files.length > 0) { 98 - setSelectedFiles(e.target.files) 99 - } 100 - } 101 - 102 - const handleUpload = async () => { 103 - if (!siteName) { 104 - alert('Please enter a site name') 105 - return 106 - } 107 - 108 - setIsUploading(true) 109 - setUploadProgress('Preparing files...') 110 - 111 - try { 112 - const formData = new FormData() 113 - formData.append('siteName', siteName) 114 - 115 - if (selectedFiles) { 116 - for (let i = 0; i < selectedFiles.length; i++) { 117 - formData.append('files', selectedFiles[i]) 118 - } 119 - } 120 - 121 - setUploadProgress('Uploading to AT Protocol...') 122 - const response = await fetch('/wisp/upload-files', { 123 - method: 'POST', 124 - body: formData 125 - }) 126 - 127 - const data = await response.json() 128 - if (data.success) { 129 - setUploadProgress('Upload complete!') 130 - setSkippedFiles(data.skippedFiles || []) 131 - setUploadedCount(data.uploadedCount || data.fileCount || 0) 132 - 133 - // If there are skipped files, show them briefly before redirecting 134 - if (data.skippedFiles && data.skippedFiles.length > 0) { 135 - setTimeout(() => { 136 - window.location.href = `https://${claimedDomain}` 137 - }, 3000) // Give more time to see skipped files 138 - } else { 139 - setTimeout(() => { 140 - window.location.href = `https://${claimedDomain}` 141 - }, 1500) 142 - } 143 - } else { 144 - throw new Error(data.error || 'Upload failed') 145 - } 146 - } catch (err) { 147 - console.error('Upload error:', err) 148 - alert( 149 - `Upload failed: ${err instanceof Error ? err.message : 'Unknown error'}` 150 - ) 151 - setIsUploading(false) 152 - setUploadProgress('') 153 - } 154 - } 155 - 156 - const handleSkipUpload = () => { 157 - // Redirect to editor without uploading 158 - window.location.href = '/editor' 159 - } 160 - 161 - return ( 162 - <div className="w-full min-h-screen bg-background"> 163 - {/* Header */} 164 - <header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50"> 165 - <div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between"> 166 - <div className="flex items-center gap-2"> 167 - <div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"> 168 - <Globe className="w-5 h-5 text-primary-foreground" /> 169 - </div> 170 - <span className="text-xl font-semibold text-foreground"> 171 - wisp.place 172 - </span> 173 - </div> 174 - </div> 175 - </header> 176 - 177 - <div className="container mx-auto px-4 py-12 max-w-2xl"> 178 - {/* Progress indicator */} 179 - <div className="mb-8"> 180 - <div className="flex items-center justify-center gap-2 mb-4"> 181 - <div 182 - className={`w-8 h-8 rounded-full flex items-center justify-center ${step === 'domain' 183 - ? 'bg-primary text-primary-foreground' 184 - : 'bg-green-500 text-white' 185 - }`} 186 - > 187 - {step === 'domain' ? ( 188 - '1' 189 - ) : ( 190 - <CheckCircle2 className="w-5 h-5" /> 191 - )} 192 - </div> 193 - <div className="w-16 h-0.5 bg-border"></div> 194 - <div 195 - className={`w-8 h-8 rounded-full flex items-center justify-center ${step === 'upload' 196 - ? 'bg-primary text-primary-foreground' 197 - : step === 'domain' 198 - ? 'bg-muted text-muted-foreground' 199 - : 'bg-green-500 text-white' 200 - }`} 201 - > 202 - {step === 'complete' ? ( 203 - <CheckCircle2 className="w-5 h-5" /> 204 - ) : ( 205 - '2' 206 - )} 207 - </div> 208 - </div> 209 - <div className="text-center"> 210 - <h1 className="text-2xl font-bold mb-2"> 211 - {step === 'domain' && 'Claim Your Free Domain'} 212 - {step === 'upload' && 'Deploy Your First Site'} 213 - {step === 'complete' && 'All Set!'} 214 - </h1> 215 - <p className="text-muted-foreground"> 216 - {step === 'domain' && 217 - 'Choose a subdomain on wisp.place'} 218 - {step === 'upload' && 219 - 'Upload your site or start with an empty one'} 220 - {step === 'complete' && 'Redirecting to your site...'} 221 - </p> 222 - </div> 223 - </div> 224 - 225 - {/* Domain registration step */} 226 - {step === 'domain' && ( 227 - <Card> 228 - <CardHeader> 229 - <CardTitle>Choose Your Domain</CardTitle> 230 - <CardDescription> 231 - Pick a unique handle for your free *.wisp.place 232 - subdomain 233 - </CardDescription> 234 - </CardHeader> 235 - <CardContent className="space-y-4"> 236 - <div className="space-y-2"> 237 - <Label htmlFor="handle">Your Handle</Label> 238 - <div className="flex gap-2"> 239 - <div className="relative flex-1"> 240 - <Input 241 - id="handle" 242 - placeholder="my-awesome-site" 243 - value={handle} 244 - onChange={(e) => 245 - setHandle( 246 - e.target.value 247 - .toLowerCase() 248 - .replace(/[^a-z0-9-]/g, '') 249 - ) 250 - } 251 - className="pr-10" 252 - /> 253 - {isCheckingAvailability && ( 254 - <Loader2 className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 animate-spin text-muted-foreground" /> 255 - )} 256 - {!isCheckingAvailability && 257 - isAvailable !== null && ( 258 - <div 259 - className={`absolute right-3 top-1/2 -translate-y-1/2 ${isAvailable 260 - ? 'text-green-500' 261 - : 'text-red-500' 262 - }`} 263 - > 264 - {isAvailable ? '✓' : '✗'} 265 - </div> 266 - )} 267 - </div> 268 - </div> 269 - {domain && ( 270 - <p className="text-sm text-muted-foreground"> 271 - Your domain will be:{' '} 272 - <span className="font-mono">{domain}</span> 273 - </p> 274 - )} 275 - {isAvailable === false && handle.length >= 3 && ( 276 - <p className="text-sm text-red-500"> 277 - This handle is not available or invalid 278 - </p> 279 - )} 280 - </div> 281 - 282 - <Button 283 - onClick={handleClaimDomain} 284 - disabled={ 285 - !isAvailable || 286 - isClaimingDomain || 287 - isCheckingAvailability 288 - } 289 - className="w-full" 290 - > 291 - {isClaimingDomain ? ( 292 - <> 293 - <Loader2 className="w-4 h-4 mr-2 animate-spin" /> 294 - Claiming Domain... 295 - </> 296 - ) : ( 297 - <>Claim Domain</> 298 - )} 299 - </Button> 300 - </CardContent> 301 - </Card> 302 - )} 303 - 304 - {/* Upload step */} 305 - {step === 'upload' && ( 306 - <Card> 307 - <CardHeader> 308 - <CardTitle>Deploy Your Site</CardTitle> 309 - <CardDescription> 310 - Upload your static site files or start with an empty 311 - site (you can upload later) 312 - </CardDescription> 313 - </CardHeader> 314 - <CardContent className="space-y-6"> 315 - <div className="p-4 bg-green-500/10 border border-green-500/20 rounded-lg"> 316 - <div className="flex items-center gap-2 text-green-600 dark:text-green-400"> 317 - <CheckCircle2 className="w-4 h-4" /> 318 - <span className="font-medium"> 319 - Domain claimed: {claimedDomain} 320 - </span> 321 - </div> 322 - </div> 323 - 324 - <div className="space-y-2"> 325 - <Label htmlFor="site-name">Site Name</Label> 326 - <Input 327 - id="site-name" 328 - placeholder="my-site" 329 - value={siteName} 330 - onChange={(e) => setSiteName(e.target.value)} 331 - /> 332 - <p className="text-xs text-muted-foreground"> 333 - A unique identifier for this site in your account 334 - </p> 335 - </div> 336 - 337 - <div className="space-y-2"> 338 - <Label>Upload Files (Optional)</Label> 339 - <div className="border-2 border-dashed border-border rounded-lg p-8 text-center hover:border-accent transition-colors"> 340 - <Upload className="w-12 h-12 text-muted-foreground mx-auto mb-4" /> 341 - <input 342 - type="file" 343 - id="file-upload" 344 - multiple 345 - onChange={handleFileSelect} 346 - className="hidden" 347 - {...(({ webkitdirectory: '', directory: '' } as any))} 348 - /> 349 - <label 350 - htmlFor="file-upload" 351 - className="cursor-pointer" 352 - > 353 - <Button 354 - variant="outline" 355 - type="button" 356 - onClick={() => 357 - document 358 - .getElementById('file-upload') 359 - ?.click() 360 - } 361 - > 362 - Choose Folder 363 - </Button> 364 - </label> 365 - {selectedFiles && selectedFiles.length > 0 && ( 366 - <p className="text-sm text-muted-foreground mt-3"> 367 - {selectedFiles.length} files selected 368 - </p> 369 - )} 370 - </div> 371 - <p className="text-xs text-muted-foreground"> 372 - Supported: HTML, CSS, JS, images, fonts, and more 373 - </p> 374 - <p className="text-xs text-muted-foreground"> 375 - Limits: 100MB per file, 300MB total 376 - </p> 377 - </div> 378 - 379 - {uploadProgress && ( 380 - <div className="space-y-3"> 381 - <div className="p-4 bg-muted rounded-lg"> 382 - <div className="flex items-center gap-2"> 383 - <Loader2 className="w-4 h-4 animate-spin" /> 384 - <span className="text-sm"> 385 - {uploadProgress} 386 - </span> 387 - </div> 388 - </div> 389 - 390 - {skippedFiles.length > 0 && ( 391 - <div className="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg"> 392 - <div className="flex items-start gap-2 text-yellow-600 dark:text-yellow-400 mb-2"> 393 - <AlertCircle className="w-4 h-4 mt-0.5 flex-shrink-0" /> 394 - <div className="flex-1"> 395 - <span className="font-medium"> 396 - {skippedFiles.length} file{skippedFiles.length > 1 ? 's' : ''} skipped 397 - </span> 398 - {uploadedCount > 0 && ( 399 - <span className="text-sm ml-2"> 400 - ({uploadedCount} uploaded successfully) 401 - </span> 402 - )} 403 - </div> 404 - </div> 405 - <div className="ml-6 space-y-1 max-h-32 overflow-y-auto"> 406 - {skippedFiles.slice(0, 5).map((file, idx) => ( 407 - <div key={idx} className="text-xs"> 408 - <span className="font-mono">{file.name}</span> 409 - <span className="text-muted-foreground"> - {file.reason}</span> 410 - </div> 411 - ))} 412 - {skippedFiles.length > 5 && ( 413 - <div className="text-xs text-muted-foreground"> 414 - ...and {skippedFiles.length - 5} more 415 - </div> 416 - )} 417 - </div> 418 - </div> 419 - )} 420 - </div> 421 - )} 422 - 423 - <div className="flex gap-3"> 424 - <Button 425 - onClick={handleSkipUpload} 426 - variant="outline" 427 - className="flex-1" 428 - disabled={isUploading} 429 - > 430 - Skip for Now 431 - </Button> 432 - <Button 433 - onClick={handleUpload} 434 - className="flex-1" 435 - disabled={!siteName || isUploading} 436 - > 437 - {isUploading ? ( 438 - <> 439 - <Loader2 className="w-4 h-4 mr-2 animate-spin" /> 440 - Uploading... 441 - </> 442 - ) : ( 443 - <> 444 - {selectedFiles && selectedFiles.length > 0 445 - ? 'Upload & Deploy' 446 - : 'Create Empty Site'} 447 - </> 448 - )} 449 - </Button> 450 - </div> 451 - </CardContent> 452 - </Card> 453 - )} 454 - </div> 455 - </div> 456 - ) 457 - } 458 - 459 - const root = createRoot(document.getElementById('elysia')!) 460 - root.render( 461 - <Layout> 462 - <Onboarding /> 463 - </Layout> 464 - )