a tool for shared writing and social publishing
0

Configure Feed

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

replacing all login with new log in flow, removing ond log ins

celine (Apr 13, 2026, 8:12 PM EDT) fa6786af ba84643b

+266 -391
+14 -10
app/[leaflet_id]/actions/PublishButton.tsx
··· 36 36 import * as Y from "yjs"; 37 37 import * as base64 from "base64-js"; 38 38 import { YJSFragmentToString } from "src/utils/yjsFragmentToString"; 39 - import { BlueskyLogin } from "app/login/LoginForm"; 40 39 import { moveLeafletToPublication } from "actions/publications/moveLeafletToPublication"; 41 40 import { AddTiny } from "components/Icons/AddTiny"; 42 41 import { OAuthErrorMessage, isOAuthSessionError } from "components/OAuthError"; 43 42 import { useLocalPublishedAt } from "components/Pages/Backdater"; 43 + import { LoginModal } from "components/LoginButton"; 44 44 45 45 export const PublishButton = (props: { entityID: string }) => { 46 46 let { data: pub } = useLeafletPublicationData(); ··· 188 188 onOpenChange={(o) => setOpen(o)} 189 189 side={isMobile ? "top" : "right"} 190 190 align={isMobile ? "center" : "start"} 191 - className="sm:max-w-sm w-[1000px] p-0!" 191 + className="sm:max-w-xs w-[1000px] p-0!" 192 192 trigger={ 193 193 <ActionButton 194 194 primary ··· 200 200 {!identity || !identity.atp_did ? ( 201 201 <div className="p-1"> 202 202 <div 203 - className={`bg-[var(--accent-light)] w-full rounded-md flex flex-col text-center justify-center p-2 pb-4 text-sm`} 203 + className={`bg-[var(--accent-light)] w-full rounded-md flex flex-col text-center justify-center p-2 pb-4 `} 204 204 > 205 205 <div className="mx-auto pt-2 scale-90"> 206 206 <PubListEmptyIllo /> 207 207 </div> 208 - <div className="pt-1 font-bold">Publish on AT Proto</div> 208 + <div className="pt-1 font-bold">Publish to Atmosphere</div> 209 209 { 210 210 <> 211 - <div className="pb-2 text-secondary text-xs"> 212 - Link a Bluesky account to start <br /> a publishing on AT 213 - Proto 211 + <div className="pb-2 text-secondary text-sm"> 212 + {!identity ? `Log in with` : "Link"} an Atmosphere account 213 + <br /> to start publishing! 214 214 </div> 215 215 216 - <BlueskyLogin 217 - compact 218 - redirectRoute={`/${permission_token.id}?publish`} 216 + <LoginModal 217 + noEmailLogin 218 + trigger={ 219 + <ButtonPrimary compact className="mx-auto"> 220 + {!identity ? `Log in` : "Link"} 221 + </ButtonPrimary> 222 + } 219 223 /> 220 224 </> 221 225 }
+12 -17
app/[leaflet_id]/actions/ShareOptions/index.tsx
··· 6 6 import { Menu, MenuItem } from "components/Menu"; 7 7 import { ActionButton } from "components/ActionBar/ActionButton"; 8 8 import useSWR from "swr"; 9 - import LoginForm from "app/login/LoginForm"; 9 + 10 10 import { CustomDomainMenu } from "./DomainOptions"; 11 11 import { useIdentityData } from "components/IdentityProvider"; 12 12 import { ··· 14 14 useLeafletPublicationData, 15 15 } from "components/PageSWRDataProvider"; 16 16 import { ShareSmall } from "components/Icons/ShareSmall"; 17 - import { getPublicationURL, getDocumentURL } from "app/lish/createPub/getPublicationURL"; 17 + import { 18 + getPublicationURL, 19 + getDocumentURL, 20 + } from "app/lish/createPub/getPublicationURL"; 18 21 import { AtUri } from "@atproto/syntax"; 19 22 import { useIsMobile } from "src/hooks/isMobile"; 23 + import { LoginModal } from "components/LoginButton"; 20 24 21 - export type ShareMenuStates = "default" | "login" | "domain"; 25 + export type ShareMenuStates = "default" | "domain"; 22 26 23 27 export let useReadOnlyShareLink = () => { 24 28 let { permission_token, rootEntity } = useReplicache(); ··· 64 68 /> 65 69 } 66 70 > 67 - {menuState === "login" ? ( 68 - <div className="px-3 py-1"> 69 - <LoginForm text="Save your Leaflets and access them on multiple devices!" /> 70 - </div> 71 - ) : menuState === "domain" ? ( 71 + {menuState === "domain" ? ( 72 72 <CustomDomainMenu setShareMenuState={setMenuState} /> 73 73 ) : ( 74 74 <ShareMenu ··· 216 216 217 217 if (identity === null) 218 218 return ( 219 - <div className="text-tertiary font-normal text-sm px-3 py-1"> 220 - <button 221 - className="text-accent-contrast hover:font-bold" 222 - onClick={() => { 223 - props.setMenuState("login"); 224 - }} 225 - > 226 - Log In 227 - </button>{" "} 219 + <div className="text-tertiary font-normal text-sm px-2 py-1"> 220 + <LoginModal 221 + trigger={<div className="text-accent-contrast font-bold">Log in</div>} 222 + />{" "} 228 223 to publish on a custom domain! 229 224 </div> 230 225 );
+22 -32
app/lish/Subscribe.tsx
··· 21 21 import { DotLoader } from "components/utils/DotLoader"; 22 22 import { addFeed } from "./addFeed"; 23 23 import { useSearchParams } from "next/navigation"; 24 - import LoginForm from "app/login/LoginForm"; 24 + 25 25 import { RSSSmall } from "components/Icons/RSSSmall"; 26 26 import { OAuthErrorMessage, isOAuthSessionError } from "components/OAuthError"; 27 27 import { RSSTiny } from "components/Icons/RSSTiny"; 28 + import { LoginModal } from "components/LoginButton"; 29 + import { Avatar } from "components/Avatar"; 30 + import { useRecordFromDid } from "src/utils/useRecordFromDid"; 28 31 29 32 export const SubscribeWithBluesky = (props: { 30 33 compact?: boolean; ··· 188 191 props.setLocalSubscribeState(); 189 192 }, null); 190 193 191 - let [isClient, setIsClient] = useState(false); 192 - useEffect(() => { 193 - setIsClient(true); 194 - }, []); 194 + let { data: profile } = useRecordFromDid(identity?.atp_did); 195 195 196 196 if (!identity?.atp_did) { 197 197 return ( 198 - <Popover 199 - asChild 200 - className="max-w-xs" 201 - trigger={ 202 - <ButtonPrimary 203 - compact={props.compact} 204 - className={`place-self-center ${props.compact && "text-sm"}`} 205 - > 206 - <BlueskyTiny /> Subscribe with Bluesky 207 - </ButtonPrimary> 208 - } 209 - > 210 - {isClient && ( 211 - <LoginForm 212 - text="Log in to subscribe to this publication!" 213 - noEmail 214 - redirectRoute={window?.location.href + "?refreshAuth"} 215 - action={{ action: "subscribe", publication: props.pub_uri }} 216 - /> 217 - )} 218 - </Popover> 198 + <LoginModal 199 + trigger={<ButtonPrimary>Log in to Subscribe!</ButtonPrimary>} 200 + noEmailLogin 201 + /> 219 202 ); 220 203 } 221 - 222 204 return ( 223 205 <div className="flex flex-col gap-2 place-self-center"> 224 206 <form 225 207 action={subscribe} 226 208 className="place-self-center flex flex-row gap-1" 227 209 > 228 - <ButtonPrimary 229 - compact={props.compact} 230 - className={props.compact ? "text-sm" : ""} 231 - > 210 + <ButtonPrimary type="button" className="mx-auto max-w-full"> 232 211 {subscribePending ? ( 233 212 <DotLoader /> 234 213 ) : ( 235 214 <> 236 - <BlueskyTiny /> Subscribe with Bluesky 215 + <span className="shrink-0">Subscribe as</span> 216 + <span className="flex gap-1 items-center max-w-full grow min-w-0"> 217 + <Avatar 218 + src={profile?.avatar} 219 + displayName={profile?.displayName} 220 + size="small" 221 + /> 222 + 223 + <div className="grow truncate"> 224 + {profile?.displayName ? profile.displayName : profile?.handle} 225 + </div> 226 + </span> 237 227 </> 238 228 )} 239 229 </ButtonPrimary>
+4 -11
app/lish/[did]/[publication]/[rkey]/Blocks/PublishedPollBlock.tsx
··· 12 12 import { voteOnPublishedPoll } from "../voteOnPublishedPoll"; 13 13 import { PollData } from "../fetchPollData"; 14 14 import { Popover } from "components/Popover"; 15 - import LoginForm from "app/login/LoginForm"; 16 15 import { BlueskyTiny } from "components/Icons/BlueskyTiny"; 17 16 import { getVoterIdentities, VoterIdentity } from "../getVoterIdentities"; 18 17 import { Json } from "supabase/database.types"; 19 18 import { InfoSmall } from "components/Icons/InfoSmall"; 19 + import { LoginModal } from "components/LoginButton"; 20 20 21 21 // Helper function to extract the first option from a vote record 22 22 const getVoteOption = (voteRecord: any): string | null => { ··· 149 149 {isVoting ? "Voting..." : "Vote!"} 150 150 </ButtonPrimary> 151 151 ) : ( 152 - <Popover 152 + <LoginModal 153 153 asChild 154 + noEmailLogin 154 155 trigger={ 155 156 <ButtonPrimary className="place-self-center"> 156 157 <BlueskyTiny /> Login to vote 157 158 </ButtonPrimary> 158 159 } 159 - > 160 - {isClient && ( 161 - <LoginForm 162 - text="Log in to vote on this poll!" 163 - noEmail 164 - redirectRoute={window?.location.href + "?refreshAuth"} 165 - /> 166 - )} 167 - </Popover> 160 + /> 168 161 )} 169 162 </div> 170 163 </div>
+11 -4
app/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx
··· 13 13 import { BlueskyTiny } from "components/Icons/BlueskyTiny"; 14 14 import { Popover } from "components/Popover"; 15 15 import { AppBskyActorProfile, AtUri } from "@atproto/api"; 16 - import { BlueskyLogin } from "app/login/LoginForm"; 17 16 import { usePathname } from "next/navigation"; 18 17 import { QuoteContent } from "../Quotes"; 19 18 import { timeAgo } from "src/utils/timeAgo"; 20 19 import { useLocalizedDate } from "src/hooks/useLocalizedDate"; 21 20 import { ProfilePopover } from "components/ProfilePopover"; 21 + import { LoginModal } from "components/LoginButton"; 22 22 23 23 export type Comment = { 24 24 record: Json; ··· 61 61 {identity?.atp_did ? ( 62 62 <CommentBox doc_uri={props.document_uri} pageId={props.pageId} /> 63 63 ) : ( 64 - <div className="w-full accent-container text-tertiary text-center italic p-3 flex flex-col gap-2"> 65 - Connect a Bluesky account to comment 66 - <BlueskyLogin redirectRoute={redirectRoute} /> 64 + <div className="w-full accent-container text-tertiary text-center italic p-3 gap-2"> 65 + <span className="text-accent-contrast font-bold"> 66 + <LoginModal 67 + noEmailLogin 68 + trigger={identity ? "Link" : "Log in"} 69 + redirectRoute={redirectRoute} 70 + /> 71 + </span>{" "} 72 + {identity ? " " : "with "} 73 + an Atmosphere account to comment 67 74 </div> 68 75 )} 69 76 <hr className="border-border-light" />
+3 -2
app/lish/[did]/[publication]/[rkey]/LinearDocumentPage.tsx
··· 17 17 import { SharedPageProps } from "./PostPages"; 18 18 import { PostPrevNextButtons } from "./PostPrevNextButtons"; 19 19 import { PostSubscribe } from "./PostSubscribe"; 20 + 20 21 import { 21 22 collectFootnotesFromBlocks, 22 23 buildFootnoteIndexMap, ··· 94 95 footnoteIndexMap={footnoteIndexMap} 95 96 /> 96 97 <PublishedFootnoteSection footnotes={footnotes} /> 97 - {/*<PostSubscribe />*/} 98 + <PostSubscribe /> 98 99 <PostPrevNextButtons 99 100 showPrevNext={preferences.showPrevNext !== false && !isSubpage} 100 101 /> ··· 110 111 recommendsCount={document.recommendsCount} 111 112 /> 112 113 <div className={`spacer h-4 w-full`} /> 113 - <PostPubInfo {...dummy} /> 114 + {/*<PostPubInfo {...dummy} />*/} 114 115 115 116 {!hasPageBackground && <div className={`spacer h-8 w-full`} />} 116 117 </PageWrapper>
+3 -8
app/lish/createPub/page.tsx
··· 2 2 import { CreatePubForm } from "./CreatePubForm"; 3 3 import { getIdentityData } from "actions/getIdentityData"; 4 4 import LoginForm from "app/login/LoginForm"; 5 + import { LoginContent } from "components/LoginButton"; 5 6 6 7 export default async function CreatePub() { 7 8 let identity = await getIdentityData(); 8 9 if (!identity) 9 10 return ( 10 11 <div className="createPubPage relative w-full h-full flex items-stretch bg-bg-leaflet p-4"> 11 - <div className="createPubContent h-full flex items-center max-w-sm w-full mx-auto"> 12 - <div className="frosted-container w-full p-3 justify-items-center text-center"> 13 - <LoginForm 14 - text="Log in to create a publication!" 15 - noEmail 16 - redirectRoute={"/lish/createPub"} 17 - /> 18 - </div> 12 + <div className="createPubContent h-full flex items-center w-fit mx-auto"> 13 + <LoginContent pageView noEmailLogin /> 19 14 </div> 20 15 </div> 21 16 );
+8 -2
app/lish/sorry-boris/page.tsx
··· 1 1 import { getIdentityData } from "actions/getIdentityData"; 2 - import { BlueskyLogin } from "app/login/LoginForm"; 3 2 import { codeToHtml } from "shiki"; 4 3 import { FixFeedsButton } from "./fixFeedsButton"; 4 + import { LoginModal } from "components/LoginButton"; 5 + import { ButtonPrimary } from "components/Buttons"; 5 6 6 7 export default async function Page() { 7 8 let identity = await getIdentityData(); 8 9 if (!identity) { 9 10 return ( 10 11 <Layout> 11 - <BlueskyLogin redirectRoute="/lish/sorry-boris" /> 12 + <LoginModal 13 + noEmailLogin 14 + redirectRoute="/lish/sorry-boris" 15 + asChild 16 + trigger={<ButtonPrimary>Sorry Boris!</ButtonPrimary>} 17 + /> 12 18 </Layout> 13 19 ); 14 20 }
-150
app/login/LoginForm.tsx
··· 15 15 import { mutate } from "swr"; 16 16 import { BlueskyTiny } from "components/Icons/BlueskyTiny"; 17 17 18 - export default function LoginForm(props: { 19 - noEmail?: boolean; 20 - redirectRoute?: string; 21 - action?: ActionAfterSignIn; 22 - text: React.ReactNode; 23 - }) { 24 - type FormState = 25 - | { 26 - stage: "email"; 27 - email: string; 28 - } 29 - | { 30 - stage: "code"; 31 - email: string; 32 - tokenId: string; 33 - confirmationCode: string; 34 - }; 35 - 36 - const [formState, setFormState] = useState<FormState>({ 37 - stage: "email", 38 - email: "", 39 - }); 40 - 41 - const handleSubmitEmail = async (e: React.FormEvent) => { 42 - e.preventDefault(); 43 - const tokenId = await requestAuthEmailToken(formState.email); 44 - setFormState({ 45 - stage: "code", 46 - email: formState.email, 47 - tokenId, 48 - confirmationCode: "", 49 - }); 50 - }; 51 - 52 - let smoker = useSmoker(); 53 - let toaster = useToaster(); 54 - 55 - const handleSubmitCode = async (e: React.FormEvent) => { 56 - e.preventDefault(); 57 - let rect = e.currentTarget.getBoundingClientRect(); 58 - 59 - if (formState.stage !== "code") return; 60 - const confirmedToken = await confirmEmailAuthToken( 61 - formState.tokenId, 62 - formState.confirmationCode, 63 - ); 64 - 65 - if (!confirmedToken) { 66 - smoker({ 67 - error: true, 68 - text: "incorrect code!", 69 - position: { 70 - y: rect.bottom - 16, 71 - x: rect.right - 220, 72 - }, 73 - }); 74 - } else { 75 - let localLeaflets = getHomeDocs(); 76 - 77 - await loginWithEmailToken(localLeaflets.filter((l) => !l.hidden)); 78 - mutate("identity"); 79 - toaster({ 80 - content: <div className="font-bold">Logged in! Welcome!</div>, 81 - type: "success", 82 - }); 83 - } 84 - }; 85 - 86 - if (formState.stage === "code") { 87 - return ( 88 - <div className="w-full max-w-md flex flex-col gap-3 py-1"> 89 - <div className=" text-secondary font-bold"> 90 - Please enter the code we sent to 91 - <div className="italic truncate">{formState.email}</div> 92 - </div> 93 - <form onSubmit={handleSubmitCode} className="flex flex-col gap-2 "> 94 - <Input 95 - type="text" 96 - className="input-with-border" 97 - placeholder="000000" 98 - value={formState.confirmationCode} 99 - onChange={(e) => 100 - setFormState({ 101 - ...formState, 102 - confirmationCode: e.target.value, 103 - }) 104 - } 105 - required 106 - /> 107 - 108 - <ButtonPrimary 109 - type="submit" 110 - className="place-self-end" 111 - disabled={formState.confirmationCode === ""} 112 - onMouseDown={(e) => {}} 113 - > 114 - Confirm 115 - </ButtonPrimary> 116 - </form> 117 - </div> 118 - ); 119 - } 120 - 121 - return ( 122 - <div className="flex flex-col gap-3 w-full max-w-xs pb-1"> 123 - <div className="flex flex-col"> 124 - <h4 className="text-primary">Log In or Sign Up</h4> 125 - <div className=" text-tertiary text-sm">{props.text}</div> 126 - </div> 127 - 128 - <BlueskyLogin {...props} /> 129 - 130 - {props.noEmail ? null : ( 131 - <> 132 - <div className="flex gap-2 text-border italic w-full items-center"> 133 - <hr className="border-border-light w-full" /> 134 - <div>or</div> 135 - <hr className="border-border-light w-full" /> 136 - </div> 137 - <form 138 - onSubmit={handleSubmitEmail} 139 - className="flex flex-col gap-2 relative" 140 - > 141 - <Input 142 - type="email" 143 - placeholder="email@example.com" 144 - value={formState.email} 145 - className="input-with-border p-7" 146 - onChange={(e) => 147 - setFormState({ 148 - ...formState, 149 - email: e.target.value, 150 - }) 151 - } 152 - required 153 - /> 154 - 155 - <ButtonPrimary 156 - type="submit" 157 - className="place-self-end px-[2px]! absolute right-1 bottom-1" 158 - > 159 - <ArrowRightTiny />{" "} 160 - </ButtonPrimary> 161 - </form> 162 - </> 163 - )} 164 - </div> 165 - ); 166 - } 167 - 168 18 export function BlueskyLogin(props: { 169 19 redirectRoute?: string; 170 20 action?: ActionAfterSignIn;
+2 -2
components/ActionBar/ActionButton.tsx
··· 57 57 className={` 58 58 actionButton relative font-bold 59 59 rounded-md border 60 - flex gap-2 items-center justify-start 60 + flex gap-2 items-start justify-start 61 61 sm:w-full sm:max-w-full p-1 62 62 w-max 63 - ${smallOnMobile && "sm:text-base text-sm py-0! sm:py-1! sm:h-fit h-6"} 63 + ${smallOnMobile && "sm:text-base text-sm py-0! sm:py-1! sm:h-fit h-6 items-center!"} 64 64 ${ 65 65 primary 66 66 ? "bg-accent-1 border-accent-1 text-accent-2 transparent-outline sm:hover:outline-accent-contrast focus:outline-accent-1 outline-offset-1 "
+13 -2
components/ActionBar/DesktopNavigation.tsx
··· 8 8 } from "./NavigationButtons"; 9 9 import { PublicationButtons } from "./Publications"; 10 10 import { Sidebar } from "./Sidebar"; 11 - import { LoginActionButton, LoginButton } from "components/LoginButton"; 11 + import { LoginModal } from "components/LoginButton"; 12 12 import { ProfileButton } from "./ProfileButton"; 13 + import { ActionButton } from "./ActionButton"; 14 + import { AccountSmall } from "components/Icons/AccountSmall"; 13 15 14 16 export const DesktopNavigation = (props: { 15 17 currentPage: navPages; ··· 35 37 /> 36 38 </> 37 39 ) : ( 38 - <LoginActionButton /> 40 + <LoginModal 41 + asChild 42 + trigger={ 43 + <ActionButton 44 + secondary 45 + icon={<AccountSmall />} 46 + label="Log In/Sign Up" 47 + /> 48 + } 49 + /> 39 50 )} 40 51 </Sidebar> 41 52
+13 -2
components/ActionBar/MobileNavigation.tsx
··· 7 7 WriterButton, 8 8 } from "./NavigationButtons"; 9 9 import { PublicationNavigation } from "./PublicationNavigation"; 10 - import { LoginActionButton } from "components/LoginButton"; 10 + import { LoginModal } from "components/LoginButton"; 11 11 import { ProfileButton } from "./ProfileButton"; 12 + import { ActionButton } from "./ActionButton"; 13 + import { AccountSmall } from "components/Icons/AccountSmall"; 12 14 13 15 export const MobileNavigation = (props: { 14 16 currentPage: navPages; ··· 56 58 <ProfileButton /> 57 59 </div> 58 60 ) : ( 59 - <LoginActionButton /> 61 + <LoginModal 62 + asChild 63 + trigger={ 64 + <ActionButton 65 + secondary 66 + icon={<AccountSmall />} 67 + label="Log In/Sign Up" 68 + /> 69 + } 70 + /> 60 71 )} 61 72 </div> 62 73 );
+28 -20
components/ActionBar/Publications.tsx
··· 15 15 import { PublishSmall } from "components/Icons/PublishSmall"; 16 16 import { Popover } from "components/Popover"; 17 17 import { BlueskyLogin } from "app/login/LoginForm"; 18 - import { ButtonSecondary } from "components/Buttons"; 18 + import { ButtonPrimary, ButtonSecondary } from "components/Buttons"; 19 19 import { useIsMobile } from "src/hooks/isMobile"; 20 20 import { useState } from "react"; 21 21 import { LooseLeafSmall } from "components/Icons/LooseleafSmall"; 22 22 import { type navPages } from "./NavigationButtons"; 23 + import { LoginModal } from "components/LoginButton"; 23 24 24 25 export const PublicationButtons = (props: { 25 26 currentPage: navPages; ··· 67 68 )} 68 69 69 70 {identity.publications?.map((d) => { 70 - return ( 71 - <PublicationOption 72 - {...d} 73 - key={d.uri} 74 - record={d.record} 75 - current={d.uri === props.currentPubUri} 76 - /> 77 - ); 78 - })} 71 + return ( 72 + <PublicationOption 73 + {...d} 74 + key={d.uri} 75 + record={d.record} 76 + current={d.uri === props.currentPubUri} 77 + /> 78 + ); 79 + })} 79 80 <Link 80 81 href={"/lish/createPub"} 81 82 className={`pubListCreateNew group/new-pub text-tertiary hover:text-accent-contrast flex gap-2 items-center p-1 no-underline! ${props.optionClassName}`} ··· 135 136 <Popover 136 137 side="right" 137 138 align="start" 138 - className="p-1! max-w-56" 139 + className="p-1! max-w-full sm:max-w-xs w-[1000px] " 139 140 asChild 140 141 trigger={ 141 142 <ActionButton ··· 156 157 157 158 return ( 158 159 <div 159 - className={`bg-[var(--accent-light)] w-full rounded-md flex flex-col text-center justify-center p-2 pb-4 text-sm`} 160 + className={`accent-container w-full rounded-md flex flex-col text-center justify-center p-2 pb-4`} 160 161 > 161 162 <div className="mx-auto pt-2 scale-90"> 162 163 <PubListEmptyIllo /> 163 164 </div> 164 - <div className="pt-1 font-bold">Publish on AT Proto</div> 165 + <div className="pt-1 font-bold">Publish on the Atmosphere</div> 165 166 {identity && identity.atp_did ? ( 166 167 // has ATProto account and no pubs 167 168 <> 168 - <div className="pb-2 text-secondary text-xs"> 169 - Start a new publication <br /> 170 - on AT Proto 169 + <div className="pb-3 text-secondary text-sm"> 170 + Start a new publication, newsletter, or blog on the Atmosphere! 171 171 </div> 172 172 <SpeedyLink href={`lish/createPub`} className=" hover:no-underline!"> 173 - <ButtonSecondary className="text-sm mx-auto" compact> 173 + <ButtonSecondary className=" mx-auto " compact> 174 174 Start a Publication! 175 175 </ButtonSecondary> 176 176 </SpeedyLink> ··· 178 178 ) : ( 179 179 // no ATProto account and no pubs 180 180 <> 181 - <div className="pb-2 text-secondary text-xs"> 182 - Link a Bluesky account to start <br /> a new publication on AT Proto 181 + <div className="pb-3 text-secondary text-sm"> 182 + Start a new publication, newsletter, or blog on the Atmosphere! 183 183 </div> 184 184 185 - <BlueskyLogin compact /> 185 + <LoginModal 186 + noEmailLogin 187 + asChild 188 + trigger={ 189 + <ButtonPrimary compact className="mx-auto"> 190 + {identity ? "Link to" : "Log in with"} Atmosphere 191 + </ButtonPrimary> 192 + } 193 + /> 186 194 </> 187 195 )} 188 196 </div>
+133 -129
components/LoginButton.tsx
··· 2 2 import { useIdentityData } from "./IdentityProvider"; 3 3 import { Popover } from "./Popover"; 4 4 import { Modal } from "./Modal"; 5 - import LoginForm from "app/login/LoginForm"; 5 + 6 6 import { ButtonPrimary } from "./Buttons"; 7 7 import { ActionButton } from "./ActionBar/ActionButton"; 8 8 import { AccountSmall } from "./Icons/AccountSmall"; ··· 22 22 import { getHomeDocs } from "app/(home-pages)/home/storage"; 23 23 import { mutate } from "swr"; 24 24 25 - export function LoginButton() { 26 - let identityData = useIdentityData(); 27 - if (identityData.identity) return null; 25 + export const LoginModal = (props: { 26 + noEmailLogin?: boolean; 27 + trigger: React.ReactNode; 28 + asChild?: boolean; 29 + redirectRoute?: string; 30 + }) => { 28 31 return ( 29 - <Popover 30 - asChild 31 - trigger={ 32 - <ButtonPrimary className="place-self-start text-sm"> 33 - Log In! 34 - </ButtonPrimary> 35 - } 36 - > 37 - <LoginForm text="Save your Leaflets and access them on multiple devices!" /> 38 - </Popover> 32 + <Modal asChild={props.asChild} trigger={props.trigger}> 33 + <LoginContent 34 + noEmailLogin={props.noEmailLogin} 35 + redirectRoute={props.redirectRoute} 36 + /> 37 + </Modal> 39 38 ); 40 - } 39 + }; 41 40 42 - export function LoginActionButton() { 41 + export const LoginContent = (props: { 42 + pageView?: boolean; 43 + noEmailLogin?: boolean; 44 + redirectRoute?: string; 45 + }) => { 43 46 let identityData = useIdentityData(); 44 47 if (identityData.identity) return null; 45 48 let [state, setState] = useState< ··· 74 77 mutate("identity"); 75 78 } 76 79 }; 80 + 77 81 return ( 78 - <Modal 79 - asChild 80 - trigger={ 81 - <ActionButton 82 - secondary 83 - icon={<AccountSmall />} 84 - label="Log In/Sign Up" 85 - /> 86 - } 87 - > 88 - <div className="flex flex-col gap-2 w-xs"> 89 - <ToggleGroup 90 - value={ 91 - state === "email log in" || state === "email confirm" 92 - ? "log in" 93 - : state 94 - } 95 - onChange={setState} 96 - options={[ 97 - { value: "log in", label: "Log In" }, 98 - { value: "sign up", label: "Sign Up" }, 99 - ]} 100 - fullWidth 101 - /> 102 - <div className="accent-container flex flex-col gap-1 p-3 pt-4"> 103 - {state === "log in" ? ( 104 - <> 105 - <div className="flex flex-col gap-1 text-center mx-auto leading-tight pb-2"> 106 - <h3> 107 - Log in with <br /> 108 - Atmosphere account 109 - </h3> 110 - <AtmosphericHandleInfo 111 - trigger={ 112 - <div className="text-sm text-accent-contrast"> 113 - What's that? 114 - </div> 115 - } 116 - /> 117 - </div> 118 - <HandleInput 119 - large 120 - action={<GoToArrow className="text-accent-contrast" />} 121 - loading={loading} 122 - onSubmit={(handle) => { 123 - setLoading(true); 124 - window.location.href = `/api/oauth/login?handle=${encodeURIComponent(handle)}&redirect_url=/`; 125 - }} 82 + <div className="flex flex-col gap-2 w-xs"> 83 + <ToggleGroup 84 + value={ 85 + state === "email log in" || state === "email confirm" 86 + ? "log in" 87 + : state 88 + } 89 + onChange={setState} 90 + options={[ 91 + { value: "log in", label: "Log In" }, 92 + { value: "sign up", label: "Sign Up" }, 93 + ]} 94 + fullWidth 95 + /> 96 + <div 97 + className={`accent-container flex flex-col gap-1 ${props.pageView ? " p-4 py-5" : "px-3 py-4 "}`} 98 + > 99 + {state === "log in" ? ( 100 + <> 101 + <div className="flex flex-col gap-1 text-center mx-auto leading-tight pb-2"> 102 + <h3> 103 + Log in with <br /> 104 + Atmosphere account 105 + </h3> 106 + <AtmosphericHandleInfo 107 + trigger={ 108 + <div className="text-sm text-accent-contrast"> 109 + What's that? 110 + </div> 111 + } 126 112 /> 127 - <hr className="border-border-light mt-2 mb-1" /> 128 - <button 129 - className="text-sm text-accent-contrast" 130 - onClick={() => { 131 - setState("email log in"); 132 - }} 133 - > 134 - or log in with email 135 - </button> 136 - </> 137 - ) : state === "email log in" ? ( 138 - <form 139 - className="flex flex-col gap-1" 140 - onSubmit={(e) => { 141 - e.preventDefault(); 142 - handleEmailSubmit(); 113 + </div> 114 + <HandleInput 115 + large 116 + action={<GoToArrow className="text-accent-contrast" />} 117 + loading={loading} 118 + onSubmit={(handle) => { 119 + setLoading(true); 120 + const redirectUrl = props.redirectRoute || "/"; 121 + window.location.href = `/api/oauth/login?handle=${encodeURIComponent(handle)}&redirect_url=${encodeURIComponent(redirectUrl)}`; 143 122 }} 144 - > 145 - <h3 className="text-center">Log in with Email</h3> 123 + /> 124 + {props.noEmailLogin ? null : ( 125 + <> 126 + <hr className="border-border-light mt-2 mb-1" /> 127 + <button 128 + className="text-sm text-accent-contrast" 129 + onClick={() => { 130 + setState("email log in"); 131 + }} 132 + > 133 + or log in with email 134 + </button> 135 + </> 136 + )} 137 + </> 138 + ) : state === "email log in" ? ( 139 + <form 140 + className="flex flex-col gap-1" 141 + onSubmit={(e) => { 142 + e.preventDefault(); 143 + handleEmailSubmit(); 144 + }} 145 + > 146 + <h3 className="text-center">Log in with Email</h3> 146 147 147 - <EmailInput 148 - large 149 - value={loginEmail} 150 - onChange={setLoginEmail} 151 - loading={loading} 152 - action={ 153 - <button type="submit"> 154 - <GoToArrow className="h-fit" /> 155 - </button> 156 - } 157 - /> 158 - <hr className="border-border-light my-2" /> 159 - <button 160 - type="button" 161 - className="text-accent-contrast text-sm" 162 - onClick={() => { 163 - setState("log in"); 164 - }} 165 - > 166 - or log in with Atmosphere account 167 - </button> 168 - </form> 169 - ) : state === "email confirm" ? ( 170 - <EmailConfirm 171 - emailValue={loginEmail} 148 + <EmailInput 149 + large 150 + value={loginEmail} 151 + onChange={setLoginEmail} 172 152 loading={loading} 173 - onSubmit={handleCodeSubmit} 153 + action={ 154 + <button type="submit"> 155 + <GoToArrow className="h-fit" /> 156 + </button> 157 + } 174 158 /> 175 - ) : ( 176 - <div className="text-center text-sm"> 177 - <h3 className="pb-1"> 178 - Leaflet is part of <br /> 179 - the Atmosphere. 180 - </h3> 181 - <div className="text-secondary pb-3"> 182 - Create an Atmosphere account on Bluesky to get started! 183 - </div> 184 - <form action="/api/oauth/login" method="GET"> 185 - <input type="hidden" name="redirect_url" value="/" /> 186 - <input type="hidden" name="signup" value="true" /> 187 - <ButtonPrimary className="mx-auto mb-1"> 188 - <BlueskyTiny /> Sign up via Bluesky 189 - </ButtonPrimary> 190 - </form> 191 - <AtmosphericHandleInfo /> 159 + <hr className="border-border-light my-2" /> 160 + <button 161 + type="button" 162 + className="text-accent-contrast text-sm" 163 + onClick={() => { 164 + setState("log in"); 165 + }} 166 + > 167 + or log in with Atmosphere account 168 + </button> 169 + </form> 170 + ) : state === "email confirm" ? ( 171 + <EmailConfirm 172 + emailValue={loginEmail} 173 + loading={loading} 174 + onSubmit={handleCodeSubmit} 175 + /> 176 + ) : ( 177 + <div className="text-center text-sm"> 178 + <h3 className="pb-1"> 179 + Leaflet is part of <br /> 180 + the Atmosphere. 181 + </h3> 182 + <div className="text-secondary pb-3"> 183 + Create an Atmosphere account on Bluesky to get started! 192 184 </div> 193 - )} 194 - </div> 185 + <form action="/api/oauth/login" method="GET"> 186 + <input 187 + type="hidden" 188 + name="redirect_url" 189 + value={props.redirectRoute || "/"} 190 + /> 191 + <input type="hidden" name="signup" value="true" /> 192 + <ButtonPrimary className="mx-auto mb-1"> 193 + <BlueskyTiny /> Sign up via Bluesky 194 + </ButtonPrimary> 195 + </form> 196 + <AtmosphericHandleInfo /> 197 + </div> 198 + )} 195 199 </div> 196 - </Modal> 200 + </div> 197 201 ); 198 - } 202 + };