Signup page for Tophhie Social
1import React, { useState } from "react";
2import { SimpleSignUp } from "./SimpleSignUp"; // if default export, use: import SimpleSignUp from "./SimpleSignUp";
3import { Turnstile } from "react-turnstile";
4
5export default function SignUpUI() {
6 const [handle, setHandle] = useState("");
7 const [email, setEmail] = useState("");
8 const [password, setPassword] = useState("");
9 const [status, setStatus] = useState("");
10 const [isSignedUp, setIsSignedUp] = useState(false);
11 const [turnstileToken, setTurnstileToken] = useState(null);
12
13 const handleSubmit = async (e) => {
14 e.preventDefault();
15
16 const handleRegex = /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
17 if (!handleRegex.test(handle + ".tophhie.social")) {
18 setStatus("Invalid username. Only letters, numbers, and hyphens are allowed.");
19 return;
20 }
21
22 if (!turnstileToken) {
23 setStatus("Please complete the CAPTCHA to continue.");
24 return;
25 }
26
27 try {
28 const signUp = new SimpleSignUp();
29 // pass the Turnstile token as an optional parameter; server-side verification is required
30 await signUp.signUp(handle.toLowerCase(), email, password, turnstileToken, setStatus);
31 setIsSignedUp(true);
32 } catch (err) {
33 console.error(err);
34 setStatus(`Sign-up failed. Please try again. ${err}`);
35 setIsSignedUp(false);
36 }
37 };
38
39 return (
40 <div className="min-h-screen flex items-center justify-center p-6">
41 <div className="bg-white shadow-xl rounded-2xl max-w-lg w-full p-10 space-y-6">
42 <img
43 src="https://blob.tophhie.cloud/tophhiecloud-resources/Logos/tophhiecloud-colour-padded.png"
44 className="mx-auto w-auto"
45 style={{ maxWidth: "40%", height: "auto" }}
46 alt="Tophhie Cloud"
47 />
48
49 <h1 className="text-2xl font-bold text-gray-900">Sign up to Tophhie Social</h1>
50 <p className="text-gray-600">
51 Create your atproto (Bluesky) account on the Tophhie Social server!
52 </p>
53
54 {isSignedUp ? (
55 <div className="text-center text-green-600 font-semibold">
56 <p>Account created successfully! You can now log in.</p><br />
57 <p>When logging into Bluesky (or any atproto app), make sure the server is set to <code>https://tophhie.social</code>.</p>
58 </div>
59 ) : (
60 <>
61 <form className="space-y-4" onSubmit={handleSubmit}>
62 {/* Username + suffix */}
63 <div className="flex w-full items-stretch rounded-lg border border-gray-300 overflow-hidden">
64 <input
65 id="handle"
66 type="text"
67 placeholder="Username"
68 value={handle}
69 onChange={(e) => setHandle(e.target.value)}
70 className="flex-1 min-w-0 px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 text-[16px]" // prevent iOS zoom
71 required
72 />
73 <span className="flex-shrink-0 whitespace-nowrap px-3 py-2 bg-gray-100 text-gray-700 border-l border-gray-300">
74 .tophhie.social
75 </span>
76 </div>
77
78 {/* Email */}
79 <div className="flex rounded-lg shadow-sm border border-gray-300">
80 <input
81 type="email"
82 placeholder="Email"
83 value={email}
84 onChange={(e) => setEmail(e.target.value)}
85 className="flex-1 px-4 py-2 bg-white rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
86 required
87 />
88 </div>
89
90 {/* Password */}
91 <div className="flex rounded-lg shadow-sm border border-gray-300">
92 <input
93 type="password"
94 placeholder="Password"
95 value={password}
96 onChange={(e) => setPassword(e.target.value)}
97 className="flex-1 px-4 py-2 bg-white rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
98 required
99 />
100 </div>
101
102 <div>
103 <span className="text-gray-600">
104 By signing up, you agree to the{" "}
105 <a href="https://blog.tophhie.cloud/atproto-tos" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">
106 Terms of Service
107 </a>{" "}
108 and{" "}
109 <a href="https://blog.tophhie.cloud/atproto-privacy-policy/" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">
110 Privacy Policy
111 </a>.
112 </span>
113 </div>
114 <div className="pt-3">
115 <Turnstile
116 sitekey="0x4AAAAAACb7t8nNLqp1dWJ9"
117 theme="light"
118 onVerify={(token) => {
119 setTurnstileToken(token);
120 setStatus("");
121 }}
122 onExpire={() => setTurnstileToken(null)}
123 />
124 </div>
125 <button
126 type="submit"
127 className="w-full py-2 rounded-lg bg-blue-600 text-white font-semibold hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
128 disabled={!turnstileToken}
129 >
130 Create account
131 </button>
132 </form>
133
134 {status && (
135 <div className="text-sm text-center text-gray-700" role="status">
136 {status}
137 </div>
138 )}
139 </>
140 )}
141 </div>
142 </div>
143 );
144}