From af928c34c787b5890d6144791ea85c413b35fc8d Mon Sep 17 00:00:00 2001 From: celine Date: Mon, 13 Apr 2026 18:17:25 -0400 Subject: [PATCH] adding loading states to log in so users know things are happening --- components/LoginButton.tsx | 188 +++++++++++++----------- components/Subscribe/EmailSubscribe.tsx | 23 ++- components/Subscribe/HandleInput.tsx | 21 ++- 3 files changed, 139 insertions(+), 93 deletions(-) diff --git a/components/LoginButton.tsx b/components/LoginButton.tsx index 0f61a3dd..0891f1fa 100644 --- a/components/LoginButton.tsx +++ b/components/LoginButton.tsx @@ -47,18 +47,23 @@ export function LoginActionButton() { >("log in"); let [loginEmail, setLoginEmail] = useState(""); let [tokenId, setTokenId] = useState(null); + let [loading, setLoading] = useState(false); let toaster = useToaster(); const handleEmailSubmit = async () => { + setLoading(true); const id = await requestAuthEmailToken(loginEmail); + setLoading(false); setTokenId(id); setState("email confirm"); }; const handleCodeSubmit = async (code: string) => { if (!tokenId) return; + setLoading(true); const confirmedToken = await confirmEmailAuthToken(tokenId, code); if (!confirmedToken) { + setLoading(false); toaster({ content:
Incorrect code!
, type: "error", @@ -67,10 +72,6 @@ export function LoginActionButton() { const localLeaflets = getHomeDocs(); await loginWithEmailToken(localLeaflets.filter((l) => !l.hidden)); mutate("identity"); - toaster({ - content:
Welcome back!
, - type: "success", - }); } }; return ( @@ -85,95 +86,108 @@ export function LoginActionButton() { } >
- -
- {state === "log in" ? ( - <> -
-

- Log in with
- Atmosphere account -

- - What's that? -
- } + +
+ {state === "log in" ? ( + <> +
+

+ Log in with
+ Atmosphere account +

+ + What's that? +
+ } + /> +
+ } + loading={loading} + onSubmit={(handle) => { + setLoading(true); + window.location.href = `/api/oauth/login?handle=${encodeURIComponent(handle)}&redirect_url=/`; + }} /> -
- } - onSubmit={(handle) => { - window.location.href = `/api/oauth/login?handle=${encodeURIComponent(handle)}&redirect_url=/`; - }} - /> -
- + + ) : state === "email log in" ? ( +
{ + e.preventDefault(); + handleEmailSubmit(); }} > - or log in with email - - - ) : state === "email log in" ? ( - { e.preventDefault(); handleEmailSubmit(); }}> -

Log in with Email

+

Log in with Email

- - - - } + + + + } + /> +
+ + + ) : state === "email confirm" ? ( + -
- - - ) : state === "email confirm" ? ( - - ) : ( -
-

- Leaflet is part of
- the Atmosphere. -

-
- Create an Atmosphere account on Bluesky to get started! + ) : ( +
+

+ Leaflet is part of
+ the Atmosphere. +

+
+ Create an Atmosphere account on Bluesky to get started! +
+ + Sign up via Bluesky + +
- - Sign up via Bluesky - - -
- )} -
+ )} +
); diff --git a/components/Subscribe/EmailSubscribe.tsx b/components/Subscribe/EmailSubscribe.tsx index b63d3542..bcd6a175 100644 --- a/components/Subscribe/EmailSubscribe.tsx +++ b/components/Subscribe/EmailSubscribe.tsx @@ -2,6 +2,8 @@ import * as OneTimePasswordField from "@radix-ui/react-one-time-password-field"; import { ButtonPrimary } from "components/Buttons"; import { Input } from "components/Input"; +import { DotLoader } from "components/utils/DotLoader"; +import { theme } from "tailwind.config"; export const EmailInput = (props: { action: React.ReactNode; @@ -10,22 +12,34 @@ export const EmailInput = (props: { value: string; onChange: (val: string) => void; disabled?: boolean; + loading?: boolean; }) => { return (
props.onChange(e.target.value)} />
- {props.action} + {props.loading ? : props.action}
); @@ -33,9 +47,10 @@ export const EmailInput = (props: { export const EmailConfirm = (props: { emailValue: string; + loading?: boolean; onSubmit: (code: string) => void; }) => { - let inputClassName = "input-with-border text-2xl w-8 h-12 text-center"; + let inputClassName = `input-with-border text-2xl w-8 h-12 text-center ${props.loading ? "bg-border-light! text-tertiary!" : ""}`; return (

Confirm your email

diff --git a/components/Subscribe/HandleInput.tsx b/components/Subscribe/HandleInput.tsx index 6526c829..fa1ecf03 100644 --- a/components/Subscribe/HandleInput.tsx +++ b/components/Subscribe/HandleInput.tsx @@ -4,6 +4,8 @@ import { Input } from "components/Input"; import { Combobox, ComboboxResult } from "components/Combobox"; import { useState } from "react"; import { useDebouncedEffect } from "src/hooks/useDebouncedEffect"; +import { DotLoader } from "components/utils/DotLoader"; +import { theme } from "tailwind.config"; type ActorSuggestion = { handle: string; @@ -17,6 +19,7 @@ export const HandleInput = (props: { action?: React.ReactNode; className?: string; large?: boolean; + loading?: boolean; onSubmit?: (handle: string) => void; }) => { let [handleValue, setHandleValue] = useState(""); @@ -63,7 +66,7 @@ export const HandleInput = (props: { return ( { if (!open) { setDropdownOpen(false); @@ -80,6 +83,17 @@ export const HandleInput = (props: { trigger={
@ @@ -94,8 +108,11 @@ export const HandleInput = (props: { onClick={(e) => e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()} autoComplete="off" + disabled={props.loading} /> - {props.onSubmit ? ( + {props.loading ? ( + + ) : props.onSubmit ? (