forked from
nekomimi.pet/wisp.place-monorepo
Monorepo for Wisp.place. A static site hosting service built on top of the AT Protocol.
1import { useState, useRef, useEffect } from 'react'
2import { createRoot } from 'react-dom/client'
3
4import Layout from '@public/layouts'
5
6function App() {
7 const [showForm, setShowForm] = useState(false)
8 const inputRef = useRef<HTMLInputElement>(null)
9
10 useEffect(() => {
11 if (showForm) {
12 setTimeout(() => inputRef.current?.focus(), 500)
13 }
14 }, [showForm])
15
16 return (
17 <>
18 <section id="header" className="py-24 px-6">
19 <div className="text-center space-y-8">
20 <div className="space-y-4">
21 <h1 className="text-6xl md:text-8xl font-bold text-balance leading-tight">
22 The complete platform to{' '}
23 <span className="gradient-text">
24 publish the web.
25 </span>
26 </h1>
27 <p className="text-xl md:text-2xl text-muted-foreground max-w-3xl mx-auto text-balance">
28 Your decentralized toolkit to stop configuring and
29 start publishing. Securely build, deploy, and own
30 your web presence with AT Protocol.
31 </p>
32 </div>
33
34 <div className="inline-flex items-center gap-2 bg-accent/10 border border-accent/20 rounded-full px-4 py-2">
35 <svg
36 xmlns="http://www.w3.org/2000/svg"
37 className="h-5 w-5 text-accent"
38 viewBox="0 0 20 20"
39 fill="currentColor"
40 >
41 <path
42 fillRule="evenodd"
43 d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.414-1.414L11 9.586V6z"
44 clipRule="evenodd"
45 />
46 </svg>
47 <span className="text-sm font-medium text-accent">
48 Publish once, own forever
49 </span>
50 </div>
51
52 <div className="max-w-md mx-auto space-y-4 mt-8">
53 <div className="relative h-16">
54 <div
55 className={`transition-all duration-500 ease-in-out absolute inset-0 ${
56 showForm
57 ? 'opacity-0 -translate-y-5 pointer-events-none'
58 : 'opacity-100 translate-y-0'
59 }`}
60 >
61 <button
62 onClick={() => setShowForm(true)}
63 className="w-full bg-primary hover:bg-primary/90 text-primary-foreground font-semibold py-4 px-6 text-lg rounded-lg inline-flex items-center justify-center transition-colors"
64 >
65 Log in with AT Proto
66 <svg
67 xmlns="http://www.w3.org/2000/svg"
68 className="ml-2 w-5 h-5"
69 viewBox="0 0 24 24"
70 fill="none"
71 stroke="currentColor"
72 strokeWidth="2"
73 strokeLinecap="round"
74 strokeLinejoin="round"
75 >
76 <path d="M5 12h14M12 5l7 7-7 7" />
77 </svg>
78 </button>
79 </div>
80
81 <div
82 className={`transition-all duration-500 ease-in-out absolute inset-0 ${
83 showForm
84 ? 'opacity-100 translate-y-0'
85 : 'opacity-0 translate-y-5 pointer-events-none'
86 }`}
87 >
88 <form
89 onSubmit={async (e) => {
90 e.preventDefault()
91 try {
92 const handle =
93 inputRef.current?.value
94 const res = await fetch(
95 '/api/auth/signin',
96 {
97 method: 'POST',
98 headers: {
99 'Content-Type':
100 'application/json'
101 },
102 body: JSON.stringify({
103 handle
104 })
105 }
106 )
107 if (!res.ok)
108 throw new Error(
109 'Request failed'
110 )
111 const data = await res.json()
112 if (data.url) {
113 window.location.href = data.url
114 } else {
115 alert('Unexpected response')
116 }
117 } catch (error) {
118 console.error(
119 'Login failed:',
120 error
121 )
122 alert('Authentication failed')
123 }
124 }}
125 className="space-y-3"
126 >
127 <input
128 ref={inputRef}
129 type="text"
130 name="handle"
131 placeholder="Enter your handle (e.g., alice.bsky.social)"
132 className="w-full py-4 px-4 text-lg bg-input border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-accent"
133 />
134 <button
135 type="submit"
136 className="w-full bg-accent hover:bg-accent/90 text-accent-foreground font-semibold py-4 px-6 text-lg rounded-lg inline-flex items-center justify-center transition-colors"
137 >
138 Continue
139 <svg
140 xmlns="http://www.w3.org/2000/svg"
141 className="ml-2 w-5 h-5"
142 viewBox="0 0 24 24"
143 fill="none"
144 stroke="currentColor"
145 strokeWidth="2"
146 strokeLinecap="round"
147 strokeLinejoin="round"
148 >
149 <path d="M5 12h14M12 5l7 7-7 7" />
150 </svg>
151 </button>
152 </form>
153 </div>
154 </div>
155 </div>
156 </div>
157 </section>
158 </>
159 )
160}
161
162const root = createRoot(document.getElementById('elysia')!)
163root.render(
164 <Layout className="gap-6">
165 <App />
166 </Layout>
167)