One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links ๐Ÿ“… calendar.xyehr.cn
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #220 from EvanTechDev/feature/fix-login-and-auth-page-loading-errors

fix(auth): gate Clerk flows until SDK is loaded and stabilize OAuth redirects

authored by

Evan Huang and committed by
GitHub
b8a27c96 8839068c

+39 -47
+14 -21
components/auth/login-form.tsx
··· 74 74 75 75 try { 76 76 if (!isLoaded || !signIn) { 77 - setError("Auth service is still loading. Please try again in a moment."); 78 77 return; 79 78 } 80 79 ··· 102 101 } 103 102 }; 104 103 105 - const handleOAuthLogin = (strategy: "oauth_google" | "oauth_microsoft" | "oauth_github") => { 104 + const handleOAuthLogin = async (strategy: "oauth_google" | "oauth_microsoft" | "oauth_github") => { 106 105 const siteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY; 107 106 if (siteKey && !isCaptchaCompleted) { 108 107 setError("Please complete the CAPTCHA verification."); 109 108 return; 110 109 } 111 110 if (!isLoaded || !signIn) { 112 - setError("Auth service is still loading. Please try again in a moment."); 113 111 return; 114 112 } 115 113 116 - const redirect = 117 - signIn.authenticateWithRedirect ?? 118 - (signIn as unknown as { authWithRedirect?: typeof signIn.authenticateWithRedirect }) 119 - .authWithRedirect ?? 120 - (signIn as unknown as { authenticatorWithRedirect?: typeof signIn.authenticateWithRedirect }) 121 - .authenticatorWithRedirect; 122 - 123 - if (!redirect) { 124 - setError("OAuth is unavailable right now. Please refresh and try again."); 125 - return; 114 + try { 115 + await signIn.authenticateWithRedirect({ 116 + strategy, 117 + redirectUrl: "/sign-in/sso-callback", 118 + redirectUrlComplete: "/app", 119 + }); 120 + } catch (err: any) { 121 + setError(err.errors?.[0]?.longMessage || "OAuth login failed. Please try again."); 126 122 } 127 - 128 - redirect.call(signIn, { 129 - strategy, 130 - redirectUrl: "/sign-in/sso-callback", 131 - redirectUrlComplete: "/app", 132 - }); 133 123 }; 134 124 135 125 const siteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY; ··· 148 138 variant="outline" 149 139 className="w-full" 150 140 type="button" 141 + disabled={!isLoaded} 151 142 onClick={() => handleOAuthLogin("oauth_microsoft")} 152 143 > 153 144 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23" width="20" height="20"> ··· 162 153 variant="outline" 163 154 className="w-full" 164 155 type="button" 156 + disabled={!isLoaded} 165 157 onClick={() => handleOAuthLogin("oauth_google")} 166 158 > 167 159 <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"> ··· 177 169 variant="outline" 178 170 className="w-full" 179 171 type="button" 172 + disabled={!isLoaded} 180 173 onClick={() => handleOAuthLogin("oauth_github")} 181 174 > 182 175 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20"> ··· 244 237 <Button 245 238 type="submit" 246 239 className="w-full bg-[#0066ff] hover:bg-[#0047cc] text-white" 247 - disabled={siteKey && (!isCaptchaCompleted || isLoading)} 240 + disabled={!isLoaded || (siteKey && (!isCaptchaCompleted || isLoading))} 248 241 > 249 - {isLoading ? "Signing in..." : "Sign in"} 242 + {!isLoaded ? "Loading auth..." : isLoading ? "Signing in..." : "Sign in"} 250 243 </Button> 251 244 </div> 252 245 <div className="text-center text-sm">
+11 -6
components/auth/reset-form.tsx
··· 21 21 className, 22 22 ...props 23 23 }: React.ComponentPropsWithoutRef<"div">) { 24 - const { signIn } = useSignIn(); 24 + const { isLoaded, signIn } = useSignIn(); 25 25 const router = useRouter(); 26 26 const [step, setStep] = useState<"email" | "code" | "password">("email"); 27 27 const [formData, setFormData] = useState({ ··· 82 82 setError(""); 83 83 84 84 try { 85 + if (!isLoaded || !signIn) { 86 + return; 87 + } 85 88 if (step === "email") { 86 - await signIn?.create({ 89 + await signIn.create({ 87 90 strategy: "reset_password_email_code", 88 91 identifier: formData.email, 89 92 }); 90 93 setStep("code"); 91 94 } else if (step === "code") { 92 - const result = await signIn?.attemptFirstFactor({ 95 + const result = await signIn.attemptFirstFactor({ 93 96 strategy: "reset_password_email_code", 94 97 code: formData.code, 95 98 }); ··· 97 100 setStep("password"); 98 101 } 99 102 } else { 100 - const result = await signIn?.resetPassword({ 103 + const result = await signIn.resetPassword({ 101 104 password: formData.password, 102 105 }); 103 106 if (result?.status === "complete") { ··· 237 240 type="submit" 238 241 className="w-full bg-[#0066ff] hover:bg-[#0047cc] text-white" 239 242 disabled={ 240 - siteKey && step === "email" 243 + !isLoaded 244 + ? true 245 + : siteKey && step === "email" 241 246 ? !isCaptchaCompleted || isLoading 242 247 : isLoading 243 248 } 244 249 > 245 - {isLoading ? "Processing..." : stepContent.buttonText} 250 + {!isLoaded ? "Loading auth..." : isLoading ? "Processing..." : stepContent.buttonText} 246 251 </Button> 247 252 248 253 <div className="text-center text-sm">
+14 -20
components/auth/sign-up-form.tsx
··· 136 136 return; 137 137 } 138 138 if (!isLoaded || !signUp) { 139 - setError("Auth service is still loading. Please try again in a moment."); 140 139 return; 141 140 } 142 - 143 - const redirect = 144 - signUp.authenticateWithRedirect ?? 145 - (signUp as unknown as { authWithRedirect?: typeof signUp.authenticateWithRedirect }) 146 - .authWithRedirect; 147 - 148 - if (!redirect) { 149 - setError("OAuth is unavailable right now. Please refresh and try again."); 150 - return; 151 - } 152 - 153 - redirect.call(signUp, { 154 - strategy, 155 - redirectUrl: "/sign-up/sso-callback", 156 - redirectUrlComplete: "/app", 157 - }); 141 + signUp 142 + .authenticateWithRedirect({ 143 + strategy, 144 + redirectUrl: "/sign-up/sso-callback", 145 + redirectUrlComplete: "/app", 146 + }) 147 + .catch((err: any) => { 148 + setError(err.errors?.[0]?.longMessage || "OAuth sign up failed. Please try again."); 149 + }); 158 150 }; 159 151 160 152 const handleSubmit = async (e: React.FormEvent) => { ··· 169 161 170 162 try { 171 163 if (!isLoaded || !signUp) { 172 - setError("Auth service is still loading. Please try again in a moment."); 173 164 return; 174 165 } 175 166 ··· 295 286 variant="outline" 296 287 className="w-full" 297 288 type="button" 289 + disabled={!isLoaded} 298 290 onClick={() => handleOAuthSignUp("oauth_microsoft")} 299 291 > 300 292 <svg ··· 314 306 variant="outline" 315 307 className="w-full" 316 308 type="button" 309 + disabled={!isLoaded} 317 310 onClick={() => handleOAuthSignUp("oauth_google")} 318 311 > 319 312 <svg ··· 346 339 variant="outline" 347 340 className="w-full" 348 341 type="button" 342 + disabled={!isLoaded} 349 343 onClick={() => handleOAuthSignUp("oauth_github")} 350 344 > 351 345 <svg ··· 448 442 <Button 449 443 type="submit" 450 444 className="w-full bg-[#0066ff] hover:bg-[#0047cc] text-white" 451 - disabled={siteKey && (!isCaptchaCompleted || isLoading)} 445 + disabled={!isLoaded || (siteKey && (!isCaptchaCompleted || isLoading))} 452 446 > 453 - {isLoading ? "Creating account..." : "Create account"} 447 + {!isLoaded ? "Loading auth..." : isLoading ? "Creating account..." : "Create account"} 454 448 </Button> 455 449 </div> 456 450