Signup page for Tophhie Social
at main 144 lines 5.7 kB view raw
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}