Something went wrong. Try again.
a tool for shared writing and social publishing
Something went wrong. Try again.
9.9 kB · 319 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320"use client";import { useIdentityData } from "./IdentityProvider";import { Popover } from "./Popover";import { Modal } from "./Modal";
import { ButtonPrimary } from "./Buttons";import { ActionButton } from "./ActionBar/ActionButton";import { AccountSmall } from "./Icons/AccountSmall";import { AtmosphericHandleInfo } from "./Subscribe/HandleSubscribe";import { HandleInput } from "./Subscribe/HandleInput";import { EmailInput, EmailConfirm } from "./Subscribe/EmailSubscribe";import { useState } from "react";import { GoToArrow } from "./Icons/GoToArrow";import { ToggleGroup } from "./ToggleGroup";import { useToaster } from "./Toast";import { BlueskyTiny } from "./Icons/BlueskyTiny";import { requestAuthEmailToken, confirmEmailAuthToken,} from "actions/emailAuth";import { loginWithEmailToken } from "actions/login";import { getHomeDocs } from "app/(home-pages)/home/storage";import { mutate } from "swr";
export const LoginModal = (props: { noEmailLogin?: boolean; trigger: React.ReactNode; asChild?: boolean; redirectRoute?: string;}) => { let [open, setOpen] = useState(false); return ( <Modal asChild={props.asChild} trigger={props.trigger} open={open} onOpenChange={setOpen} className="w-full!" > <LoginContent noEmailLogin={props.noEmailLogin} redirectRoute={props.redirectRoute} open={open} onSuccess={() => setOpen(false)} /> </Modal> );};
export const LoginContent = (props: { pageView?: boolean; noEmailLogin?: boolean; redirectRoute?: string; open?: boolean; onSuccess?: () => void; className?: string;}) => { let identityData = useIdentityData(); let [state, setState] = useState< "log in" | "sign up" | "email log in" | "email confirm" >("log in"); let [loginEmail, setLoginEmail] = useState(""); let [tokenId, setTokenId] = useState<string | null>(null); let [loading, setLoading] = useState(false); let toaster = useToaster();
if (identityData.identity?.atp_did) return null; if (identityData.identity?.email && !identityData.identity.atp_did) { return ( <LinkAtmosphereContent pageView={props.pageView} redirectRoute={props.redirectRoute} open={props.open} /> ); }
const handleEmailSubmit = async () => { setLoading(true); try { const id = await requestAuthEmailToken(loginEmail); setTokenId(id); setState("email confirm"); } catch (e) { toaster({ content: ( <div className="font-bold"> Could not send email — please try again </div> ), type: "error", }); } finally { setLoading(false); } };
const handleCodeSubmit = async (code: string) => { if (!tokenId) return; setLoading(true); const confirmedToken = await confirmEmailAuthToken(tokenId, code); if (!confirmedToken) { setLoading(false); toaster({ content: <div className="font-bold">Incorrect code!</div>, type: "error", }); return; } const localLeaflets = getHomeDocs(); await loginWithEmailToken(localLeaflets.filter((l) => !l.hidden)); mutate("identity"); toaster({ content: <div className="font-bold">Logged in! Welcome!</div>, type: "success", }); setLoading(false); props.onSuccess?.(); };
return ( <div className={`flex flex-col gap-2 w-full sm:w-xs ${props.className}`}> <ToggleGroup value={ state === "email log in" || state === "email confirm" ? "log in" : state } onChange={setState} options={[ { value: "log in", label: "Log In" }, { value: "sign up", label: "Sign Up" }, ]} fullWidth /> <div className={`accent-container flex flex-col gap-1 ${props.pageView ? " p-4 py-5" : "px-3 py-4 "}`} > {state === "log in" ? ( <> <div className="flex flex-col gap-1 text-center mx-auto leading-tight pb-2"> <h3> Log in with <br /> Atmosphere account </h3> <AtmosphericHandleInfo trigger={ <div className="text-sm text-accent-contrast"> What's the Atmosphere? </div> } /> </div> <HandleInput large autoFocus={props.open !== false} action={<GoToArrow className="text-accent-contrast" />} loading={loading} onSubmit={(handle) => { setLoading(true); let redirectUrl: string; if (props.redirectRoute) { redirectUrl = props.redirectRoute; } else { let url = new URL(window.location.href); url.searchParams.set("refreshAuth", ""); redirectUrl = url.toString(); } window.location.href = `/api/oauth/login?handle=${encodeURIComponent(handle)}&redirect_url=${encodeURIComponent(redirectUrl)}`; }} /> {props.noEmailLogin ? null : ( <> <hr className="border-border-light mt-2 mb-1" /> <button className="text-sm text-accent-contrast" onClick={(e) => { e.preventDefault(); e.stopPropagation(); setState("email log in"); }} > or log in with email </button> </> )} </> ) : state === "email log in" ? ( <form className="flex flex-col gap-1" onSubmit={(e) => { e.preventDefault(); e.stopPropagation(); handleEmailSubmit(); }} > <h3 className="text-center">Log in with Email</h3>
<EmailInput large autoFocus={props.open !== false} value={loginEmail} onChange={setLoginEmail} loading={loading} action={ <button type="submit"> <GoToArrow className="h-fit" /> </button> } /> <hr className="border-border-light my-2" /> <button type="button" className="text-accent-contrast text-sm" onClick={(e) => { e.preventDefault(); e.stopPropagation(); setState("log in"); }} > or log in with Atmosphere account </button> </form> ) : state === "email confirm" ? ( <EmailConfirm autoFocus={props.open !== false} emailValue={loginEmail} loading={loading} onSubmit={handleCodeSubmit} onBack={() => setState("email log in")} /> ) : ( <div className="text-center text-sm"> <h3 className="pb-1"> Leaflet is part of <br /> the Atmosphere. </h3> <div className="text-secondary pb-3"> Create an Atmosphere account on Bluesky to get started! </div> <form action="/api/oauth/login" method="GET"> <input type="hidden" name="redirect_url" value={props.redirectRoute || "/"} /> <input type="hidden" name="signup" value="true" /> <ButtonPrimary className="mx-auto mb-1"> <BlueskyTiny /> Sign up via Bluesky </ButtonPrimary> </form> <AtmosphericHandleInfo /> </div> )} </div> </div> );};
const LinkAtmosphereContent = (props: { pageView?: boolean; redirectRoute?: string; open?: boolean;}) => { let [loading, setLoading] = useState(false); return ( <div className={`accent-container w-xs flex flex-col gap-1 ${props.pageView ? " p-4 py-5" : "px-3 py-4 "}`} > <div className="flex flex-col gap-1 text-center mx-auto leading-tight pb-2"> <h3> Link your <br /> Atmosphere account </h3> <AtmosphericHandleInfo trigger={ <div className="text-sm text-accent-contrast"> What's the Atmosphere? </div> } /> </div> <HandleInput large autoFocus={props.open !== false} action={<GoToArrow className="text-accent-contrast" />} loading={loading} onSubmit={(handle) => { setLoading(true); let redirectUrl: string; if (props.redirectRoute) { redirectUrl = props.redirectRoute; } else { let url = new URL(window.location.href); url.searchParams.set("refreshAuth", ""); redirectUrl = url.toString(); } window.location.href = `/api/oauth/login?handle=${encodeURIComponent(handle)}&redirect_url=${encodeURIComponent(redirectUrl)}&link=true`; }} /> <hr className="border-border-light mt-2 mb-1" /> <form action="/api/oauth/login" method="GET"> <input type="hidden" name="redirect_url" value={props.redirectRoute || "/"} /> <input type="hidden" name="signup" value="true" /> <input type="hidden" name="link" value="true" /> <button type="submit" className="text-sm text-accent-contrast flex gap-1 items-center mx-auto" > <BlueskyTiny /> or sign up via Bluesky </button> </form> </div> );};