a tool for shared writing and social publishing

adding profile button to nav, removing things where necessary

+87 -132
+6 -118
app/(home-pages)/home/Actions/AccountSettings.tsx
··· 1 1 "use client"; 2 2 3 3 import { ActionButton } from "components/ActionBar/ActionButton"; 4 - import { mutate } from "swr"; 5 - import { AccountSmall } from "components/Icons/AccountSmall"; 6 - import { LogoutSmall } from "components/Icons/LogoutSmall"; 7 4 import { Popover } from "components/Popover"; 8 - import { ArrowRightTiny } from "components/Icons/ArrowRightTiny"; 9 - import { SpeedyLink } from "components/SpeedyLink"; 10 - import { GoBackSmall } from "components/Icons/GoBackSmall"; 11 - import { useState } from "react"; 12 5 import { ThemeSetterContent } from "components/ThemeManager/ThemeSetter"; 13 6 import { useIsMobile } from "src/hooks/isMobile"; 7 + import { PaintSmall } from "components/Icons/PaintSmall"; 14 8 15 - export const AccountSettings = (props: { entityID: string }) => { 16 - let [state, setState] = useState<"menu" | "general" | "theme">("menu"); 9 + export const AccountTheme = (props: { entityID: string }) => { 17 10 let isMobile = useIsMobile(); 18 11 19 12 return ( 20 13 <Popover 21 14 asChild 22 - onOpenChange={() => setState("menu")} 23 15 side={isMobile ? "top" : "right"} 24 16 align={isMobile ? "center" : "start"} 25 - className={`max-w-xs w-[1000px] ${state === "theme" && "bg-white!"}`} 26 - trigger={ 27 - <ActionButton smallOnMobile icon=<AccountSmall /> label="Settings" /> 28 - } 17 + className={`w-xs bg-white!`} 18 + arrowFill="bg-white" 19 + trigger={<ActionButton smallOnMobile icon=<PaintSmall /> label="Theme" />} 29 20 > 30 - {state === "general" ? ( 31 - <GeneralSettings backToMenu={() => setState("menu")} /> 32 - ) : state === "theme" ? ( 33 - <AccountThemeSettings 34 - entityID={props.entityID} 35 - backToMenu={() => setState("menu")} 36 - /> 37 - ) : ( 38 - <SettingsMenu state={state} setState={setState} /> 39 - )} 40 - </Popover> 41 - ); 42 - }; 43 - 44 - const SettingsMenu = (props: { 45 - state: "menu" | "general" | "theme"; 46 - setState: (s: typeof props.state) => void; 47 - }) => { 48 - let menuItemClassName = 49 - "menuItem -mx-[8px] text-left flex items-center justify-between hover:no-underline!"; 50 - 51 - return ( 52 - <div className="flex flex-col gap-0.5"> 53 - <AccountSettingsHeader state={"menu"} /> 54 - <button 55 - className={menuItemClassName} 56 - type="button" 57 - onClick={() => { 58 - props.setState("general"); 59 - }} 60 - > 61 - General 62 - <ArrowRightTiny /> 63 - </button> 64 - <button 65 - className={menuItemClassName} 66 - type="button" 67 - onClick={() => props.setState("theme")} 68 - > 69 - Account Theme 70 - <ArrowRightTiny /> 71 - </button> 72 - </div> 73 - ); 74 - }; 75 - 76 - const GeneralSettings = (props: { backToMenu: () => void }) => { 77 - return ( 78 - <div className="flex flex-col gap-0.5"> 79 - <AccountSettingsHeader 80 - state={"general"} 81 - backToMenuAction={() => props.backToMenu()} 82 - /> 83 - 84 - <button 85 - className="flex gap-2 font-bold" 86 - onClick={async () => { 87 - await fetch("/api/auth/logout"); 88 - mutate("identity", null); 89 - }} 90 - > 91 - <LogoutSmall /> 92 - Logout 93 - </button> 94 - </div> 95 - ); 96 - }; 97 - const AccountThemeSettings = (props: { 98 - entityID: string; 99 - backToMenu: () => void; 100 - }) => { 101 - return ( 102 - <div className="flex flex-col gap-0.5"> 103 - <AccountSettingsHeader 104 - state={"theme"} 105 - backToMenuAction={() => props.backToMenu()} 106 - /> 107 21 <ThemeSetterContent entityID={props.entityID} home /> 108 - </div> 109 - ); 110 - }; 111 - export const AccountSettingsHeader = (props: { 112 - state: "menu" | "general" | "theme"; 113 - backToMenuAction?: () => void; 114 - }) => { 115 - return ( 116 - <div className="flex justify-between font-bold text-secondary bg-border-light -mx-3 -mt-2 px-3 py-2 mb-1"> 117 - {props.state === "menu" 118 - ? "Settings" 119 - : props.state === "general" 120 - ? "General" 121 - : props.state === "theme" 122 - ? "Account Theme" 123 - : ""} 124 - {props.backToMenuAction && ( 125 - <button 126 - type="button" 127 - onClick={() => { 128 - props.backToMenuAction && props.backToMenuAction(); 129 - }} 130 - > 131 - <GoBackSmall className="text-accent-contrast" /> 132 - </button> 133 - )} 134 - </div> 22 + </Popover> 135 23 ); 136 24 };
+2 -2
app/(home-pages)/home/Actions/Actions.tsx
··· 2 2 import { ThemePopover } from "components/ThemeManager/ThemeSetter"; 3 3 import { CreateNewLeafletButton } from "./CreateNewButton"; 4 4 import { HelpButton } from "app/[leaflet_id]/actions/HelpButton"; 5 - import { AccountSettings } from "./AccountSettings"; 5 + import { AccountTheme } from "./AccountSettings"; 6 6 import { useIdentityData } from "components/IdentityProvider"; 7 7 import { useReplicache } from "src/replicache"; 8 8 import { LoginActionButton } from "components/LoginButton"; ··· 13 13 return ( 14 14 <> 15 15 <CreateNewLeafletButton /> 16 - {identity && <AccountSettings entityID={rootEntity} />} 16 + {identity && <AccountTheme entityID={rootEntity} />} 17 17 </> 18 18 ); 19 19 };
+1 -1
app/(home-pages)/p/[didOrHandle]/PostsContent.tsx
··· 68 68 } 69 69 70 70 return ( 71 - <div className="flex flex-col gap-3 text-left relative"> 71 + <div className="profilePosts h-full flex py-4 flex-col gap-3 text-left relative"> 72 72 {allPosts.map((post) => ( 73 73 <PostListing key={post.documents.uri} {...post} /> 74 74 ))}
+2 -2
app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx
··· 72 72 </pre> 73 73 </div> 74 74 75 - <div className="profilePublicationCards w-full overflow-x-scroll"> 75 + <div className="profilePubCardContainer w-full overflow-x-scroll"> 76 76 <div 77 - className={`grid grid-flow-col gap-2 mx-auto w-fit px-3 sm:px-4 ${props.popover ? "auto-cols-[164px]" : "auto-cols-[164px] sm:auto-cols-[240px]"}`} 77 + className={`profilePubCards grid grid-flow-col gap-2 mx-auto w-fit ${props.popover ? "auto-cols-[164px]" : "auto-cols-[164px] sm:auto-cols-[240px]"}`} 78 78 > 79 79 {props.publications.map((p) => ( 80 80 <PublicationCard key={p.uri} record={p.record} uri={p.uri} />
+1 -1
app/(home-pages)/p/[didOrHandle]/ProfileTabs.tsx
··· 41 41 const bgColor = cardBorderHidden ? "var(--bg-leaflet)" : "var(--bg-page)"; 42 42 43 43 return ( 44 - <div className="flex flex-col w-full sticky top-3 sm:top-4 z-20 sm:px-4 px-3 pt-6"> 44 + <div className="flex flex-col w-full sticky top-3 sm:top-4 z-20 pt-6"> 45 45 <div 46 46 style={ 47 47 scrollPosWithinTabContent < 20
+1 -3
app/(home-pages)/p/[didOrHandle]/layout.tsx
··· 94 94 publications={publications || []} 95 95 /> 96 96 <ProfileTabs didOrHandle={params.didOrHandle} /> 97 - <div className="h-full pt-3 pb-4 px-3 sm:px-4 flex flex-col"> 98 - {props.children} 99 - </div> 97 + <>{props.children}</> 100 98 </ProfileLayout> 101 99 ), 102 100 },
+1 -1
app/(home-pages)/p/[didOrHandle]/subscriptions/SubscriptionsContent.tsx
··· 82 82 83 83 return ( 84 84 <div className="relative"> 85 - <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 gap-3"> 85 + <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 gap-3 py-4"> 86 86 {allSubscriptions.map((sub) => ( 87 87 <PubListing key={sub.uri} {...sub} /> 88 88 ))}
+7 -1
components/ActionBar/DesktopNavigation.tsx
··· 9 9 import { PublicationButtons } from "./Publications"; 10 10 import { Sidebar } from "./Sidebar"; 11 11 import { LoginActionButton, LoginButton } from "components/LoginButton"; 12 + import { ProfileButton } from "./ProfileButton"; 12 13 13 14 export const DesktopNavigation = (props: { 14 15 currentPage: navPages; ··· 27 28 <div className="flex flex-col gap-3"> 28 29 <Sidebar alwaysOpen> 29 30 {identity?.atp_did ? ( 30 - <NotificationButton current={props.currentPage === "notifications"} /> 31 + <> 32 + <ProfileButton /> 33 + <NotificationButton 34 + current={props.currentPage === "notifications"} 35 + /> 36 + </> 31 37 ) : ( 32 38 <LoginActionButton /> 33 39 )}
+4 -2
components/ActionBar/MobileNavigation.tsx
··· 8 8 } from "./NavigationButtons"; 9 9 import { PublicationNavigation } from "./PublicationNavigation"; 10 10 import { LoginActionButton } from "components/LoginButton"; 11 + import { ProfileButton } from "./ProfileButton"; 11 12 12 13 export const MobileNavigation = (props: { 13 14 currentPage: navPages; ··· 50 51 )} 51 52 </div> 52 53 {identity?.atp_did ? ( 53 - <> 54 + <div className="flex gap-2"> 54 55 <NotificationButton /> 55 - </> 56 + <ProfileButton /> 57 + </div> 56 58 ) : ( 57 59 <LoginActionButton /> 58 60 )}
+61
components/ActionBar/ProfileButton.tsx
··· 1 + import { Avatar } from "components/Avatar"; 2 + import { ActionButton } from "./ActionButton"; 3 + import { useIdentityData } from "components/IdentityProvider"; 4 + import { AccountSmall } from "components/Icons/AccountSmall"; 5 + import { useRecordFromDid } from "src/utils/useRecordFromDid"; 6 + import { Menu, MenuItem } from "components/Menu"; 7 + import { useIsMobile } from "src/hooks/isMobile"; 8 + import { LogoutSmall } from "components/Icons/LogoutSmall"; 9 + import { mutate } from "swr"; 10 + import { SpeedyLink } from "components/SpeedyLink"; 11 + 12 + export const ProfileButton = () => { 13 + let { identity } = useIdentityData(); 14 + let { data: record } = useRecordFromDid(identity?.atp_did); 15 + let isMobile = useIsMobile(); 16 + 17 + return ( 18 + <Menu 19 + asChild 20 + side={isMobile ? "top" : "right"} 21 + align={isMobile ? "center" : "start"} 22 + trigger={ 23 + <ActionButton 24 + nav 25 + labelOnMobile={false} 26 + icon={ 27 + record ? ( 28 + <Avatar 29 + src={record.avatar} 30 + displayName={record.displayName || record.handle} 31 + /> 32 + ) : ( 33 + <AccountSmall /> 34 + ) 35 + } 36 + label={record ? record.displayName || record.handle : "Account"} 37 + className={`w-full`} 38 + /> 39 + } 40 + > 41 + {record && ( 42 + <> 43 + <SpeedyLink href={`/p/${record.handle}`}> 44 + <MenuItem onSelect={() => {}}>View Profile</MenuItem> 45 + </SpeedyLink> 46 + 47 + <hr className="border-border-light border-dashed" /> 48 + </> 49 + )} 50 + <MenuItem 51 + onSelect={async () => { 52 + await fetch("/api/auth/logout"); 53 + mutate("identity", null); 54 + }} 55 + > 56 + <LogoutSmall /> 57 + Log Out 58 + </MenuItem> 59 + </Menu> 60 + ); 61 + };
+1 -1
src/utils/getRecordFromDid.ts src/utils/useRecordFromDid.ts
··· 2 2 import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; 3 3 import useSWR from "swr"; 4 4 5 - export function useProfileFromDid(did: string | undefined) { 5 + export function useRecordFromDid(did: string | undefined | null) { 6 6 return useSWR(did ? ["profile-data", did] : null, async () => { 7 7 const response = await callRPC("get_profile_data", { 8 8 didOrHandle: did!,