a tool for shared writing and social publishing

set notification action button to direct to notification page rather than popover a panel

+163 -232
+1 -1
app/(home-pages)/home/Actions/AccountSettings.tsx
··· 111 111 backToMenuAction?: () => void; 112 112 }) => { 113 113 return ( 114 - <div className="flex justify-between font-bold text-secondary bg-border-light -mx-3 -mt-2 px-3 py-1 mb-1"> 114 + <div className="flex justify-between font-bold text-secondary bg-border-light -mx-3 -mt-2 px-3 py-2 mb-1"> 115 115 {props.state === "menu" 116 116 ? "Settings" 117 117 : props.state === "general"
+3 -13
app/(home-pages)/notifications/Notification.tsx
··· 4 4 import { PubLeafletPublication, PubLeafletRichtextFacet } from "lexicons/api"; 5 5 import { timeAgo } from "src/utils/timeAgo"; 6 6 import { useReplicache, useEntity } from "src/replicache"; 7 - import { useContext } from "react"; 8 - import { NotificationContext } from "./NotificationList"; 9 7 10 8 export const Notification = (props: { 11 9 icon: React.ReactNode; ··· 15 13 href: string; 16 14 }) => { 17 15 let { rootEntity } = useReplicache(); 18 - let { compact } = useContext(NotificationContext); 19 16 let cardBorderHidden = useEntity(rootEntity, "theme/card-border-hidden")?.data 20 17 .value; 21 18 22 19 // If compact mode, always hide border 23 - if (compact) { 24 - cardBorderHidden = true; 25 - } 26 20 27 21 return ( 28 22 <div 29 - className={`relative flex flex-col w-full py-3 sm:py-4 pt-2 sm:pt-3! ${ 23 + className={`relative flex flex-col w-full pb-3 sm:pb-4 pt-2 ${ 30 24 cardBorderHidden 31 25 ? " first:pt-0! " 32 26 : " block-border border-border! hover:outline-border sm:px-4 px-3 pl-2 sm:pl-3 " ··· 44 38 <div className="flex justify-between items-center gap-3 w-full "> 45 39 <div className={`flex flex-row gap-2 items-center grow w-full min-w-0`}> 46 40 <div className="text-secondary shrink-0">{props.icon}</div> 47 - <div 48 - className={`text-secondary font-bold grow truncate min-w-0 ${compact ? "text-sm" : ""}`} 49 - > 41 + <div className={`text-secondary font-bold grow truncate min-w-0 }`}> 50 42 {props.actionText} 51 43 </div> 52 44 </div> 53 - <div 54 - className={`text-tertiary shrink-0 min-w-8 ${compact ? "text-xs" : "text-sm"}`} 55 - > 45 + <div className={`text-tertiary shrink-0 min-w-8 text-sm`}> 56 46 {timeAgo(props.timestamp)} 57 47 </div> 58 48 </div>
+9 -14
app/(home-pages)/notifications/NotificationList.tsx
··· 2 2 3 3 import { HydratedNotification } from "src/notifications"; 4 4 import { CommentNotification } from "./CommentNotication"; 5 - import { useEntity, useReplicache } from "src/replicache"; 6 5 import { useEffect, createContext } from "react"; 7 6 import { markAsRead } from "./getNotifications"; 8 7 import { ReplyNotification } from "./ReplyNotification"; 9 - 10 - export const NotificationContext = createContext({ compact: false }); 11 8 12 9 export function NotificationList({ 13 10 notifications, ··· 29 26 </div> 30 27 ); 31 28 return ( 32 - <NotificationContext.Provider value={{ compact: compact ?? false }}> 33 - <div className="max-w-prose mx-auto w-full"> 34 - <div className={`flex flex-col gap-2`}> 35 - {notifications.map((n) => { 36 - if (n.type === "comment") { 37 - if (n.parentData) return <ReplyNotification key={n.id} {...n} />; 38 - return <CommentNotification key={n.id} {...n} />; 39 - } 40 - })} 41 - </div> 29 + <div className="max-w-prose mx-auto w-full"> 30 + <div className={`flex flex-col gap-2`}> 31 + {notifications.map((n) => { 32 + if (n.type === "comment") { 33 + if (n.parentData) return <ReplyNotification key={n.id} {...n} />; 34 + return <CommentNotification key={n.id} {...n} />; 35 + } 36 + })} 42 37 </div> 43 - </NotificationContext.Provider> 38 + </div> 44 39 ); 45 40 }
+1 -133
app/lish/[did]/[publication]/dashboard/Actions.tsx
··· 1 1 "use client"; 2 2 3 - import { Media } from "components/Media"; 4 3 import { NewDraftActionButton } from "./NewDraftButton"; 4 + import { PublicationSettingsButton } from "./PublicationSettings"; 5 5 import { ActionButton } from "components/ActionBar/ActionButton"; 6 - import { useRouter } from "next/navigation"; 7 - import { Popover } from "components/Popover"; 8 - import { SettingsSmall } from "components/Icons/SettingsSmall"; 9 6 import { ShareSmall } from "components/Icons/ShareSmall"; 10 7 import { Menu } from "components/Layout"; 11 8 import { MenuItem } from "components/Layout"; 12 - import { HomeSmall } from "components/Icons/HomeSmall"; 13 - import { EditPubForm } from "app/lish/createPub/UpdatePubForm"; 14 9 import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; 15 10 import { usePublicationData } from "./PublicationSWRProvider"; 16 11 import { useSmoker } from "components/Toast"; 17 - import { PaintSmall } from "components/Icons/PaintSmall"; 18 - import { PubThemeSetter } from "components/ThemeManager/PubThemeSetter"; 19 12 import { useIsMobile } from "src/hooks/isMobile"; 20 13 import { SpeedyLink } from "components/SpeedyLink"; 21 - import { FormEvent, useState } from "react"; 22 - import { GoBackSmall } from "components/Icons/GoBackSmall"; 23 - import { theme } from "tailwind.config"; 24 - import { ButtonPrimary } from "components/Buttons"; 25 - import { DotLoader } from "components/utils/DotLoader"; 26 - import { GoToArrow } from "components/Icons/GoToArrow"; 27 - import { ArrowRightTiny } from "components/Icons/ArrowRightTiny"; 28 14 29 15 export const Actions = (props: { publication: string }) => { 30 16 return ( ··· 91 77 </Menu> 92 78 ); 93 79 } 94 - 95 - function PublicationSettingsButton(props: { publication: string }) { 96 - let isMobile = useIsMobile(); 97 - let [state, setState] = useState<"menu" | "general" | "theme">("menu"); 98 - let [loading, setLoading] = useState(false); 99 - 100 - return ( 101 - <Popover 102 - asChild 103 - onOpenChange={() => setState("menu")} 104 - side={isMobile ? "top" : "right"} 105 - align={isMobile ? "center" : "start"} 106 - className={`max-w-xs w-[1000px] ${state === "theme" && "bg-white!"}`} 107 - arrowFill={theme.colors["border-light"]} 108 - trigger={ 109 - <ActionButton 110 - id="pub-settings-button" 111 - icon=<SettingsSmall /> 112 - label="Settings" 113 - /> 114 - } 115 - > 116 - {state === "general" ? ( 117 - <EditPubForm 118 - backToMenuAction={() => setState("menu")} 119 - loading={loading} 120 - setLoadingAction={setLoading} 121 - /> 122 - ) : state === "theme" ? ( 123 - <PubThemeSetter 124 - backToMenu={() => setState("menu")} 125 - loading={loading} 126 - setLoading={setLoading} 127 - /> 128 - ) : ( 129 - <PubSettingsMenu 130 - state={state} 131 - setState={setState} 132 - loading={loading} 133 - setLoading={setLoading} 134 - /> 135 - )} 136 - </Popover> 137 - ); 138 - } 139 - 140 - const PubSettingsMenu = (props: { 141 - state: "menu" | "general" | "theme"; 142 - setState: (s: typeof props.state) => void; 143 - loading: boolean; 144 - setLoading: (l: boolean) => void; 145 - }) => { 146 - let menuItemClassName = 147 - "menuItem -mx-[8px] text-left flex items-center justify-between hover:no-underline!"; 148 - 149 - return ( 150 - <div className="flex flex-col gap-0.5"> 151 - <PubSettingsHeader 152 - loading={props.loading} 153 - setLoadingAction={props.setLoading} 154 - state={"menu"} 155 - /> 156 - <button 157 - className={menuItemClassName} 158 - type="button" 159 - onClick={() => { 160 - props.setState("general"); 161 - }} 162 - > 163 - Publication Settings 164 - <ArrowRightTiny /> 165 - </button> 166 - <button 167 - className={menuItemClassName} 168 - type="button" 169 - onClick={() => props.setState("theme")} 170 - > 171 - Publication Theme 172 - <ArrowRightTiny /> 173 - </button> 174 - </div> 175 - ); 176 - }; 177 - 178 - export const PubSettingsHeader = (props: { 179 - state: "menu" | "general" | "theme"; 180 - backToMenuAction?: () => void; 181 - loading: boolean; 182 - setLoadingAction: (l: boolean) => void; 183 - }) => { 184 - return ( 185 - <div className="flex justify-between font-bold text-secondary bg-border-light -mx-3 -mt-2 px-3 py-2 mb-1"> 186 - {props.state === "menu" 187 - ? "Settings" 188 - : props.state === "general" 189 - ? "General" 190 - : props.state === "theme" 191 - ? "Publication Theme" 192 - : ""} 193 - {props.state !== "menu" && ( 194 - <div className="flex gap-2"> 195 - <button 196 - type="button" 197 - onClick={() => { 198 - props.backToMenuAction && props.backToMenuAction(); 199 - }} 200 - > 201 - <GoBackSmall className="text-accent-contrast" /> 202 - </button> 203 - 204 - <ButtonPrimary compact type="submit"> 205 - {props.loading ? <DotLoader /> : "Update"} 206 - </ButtonPrimary> 207 - </div> 208 - )} 209 - </div> 210 - ); 211 - };
+132
app/lish/[did]/[publication]/dashboard/PublicationSettings.tsx
··· 1 + "use client"; 2 + 3 + import { ActionButton } from "components/ActionBar/ActionButton"; 4 + import { Popover } from "components/Popover"; 5 + import { SettingsSmall } from "components/Icons/SettingsSmall"; 6 + import { EditPubForm } from "app/lish/createPub/UpdatePubForm"; 7 + import { PubThemeSetter } from "components/ThemeManager/PubThemeSetter"; 8 + import { useIsMobile } from "src/hooks/isMobile"; 9 + import { useState } from "react"; 10 + import { GoBackSmall } from "components/Icons/GoBackSmall"; 11 + import { theme } from "tailwind.config"; 12 + import { ButtonPrimary } from "components/Buttons"; 13 + import { DotLoader } from "components/utils/DotLoader"; 14 + import { ArrowRightTiny } from "components/Icons/ArrowRightTiny"; 15 + 16 + export function PublicationSettingsButton(props: { publication: string }) { 17 + let isMobile = useIsMobile(); 18 + let [state, setState] = useState<"menu" | "general" | "theme">("menu"); 19 + let [loading, setLoading] = useState(false); 20 + 21 + return ( 22 + <Popover 23 + asChild 24 + onOpenChange={() => setState("menu")} 25 + side={isMobile ? "top" : "right"} 26 + align={isMobile ? "center" : "start"} 27 + className={`max-w-xs w-[1000px] ${state === "theme" && "bg-white!"}`} 28 + arrowFill={theme.colors["border-light"]} 29 + trigger={ 30 + <ActionButton 31 + id="pub-settings-button" 32 + icon=<SettingsSmall /> 33 + label="Settings" 34 + /> 35 + } 36 + > 37 + {state === "general" ? ( 38 + <EditPubForm 39 + backToMenuAction={() => setState("menu")} 40 + loading={loading} 41 + setLoadingAction={setLoading} 42 + /> 43 + ) : state === "theme" ? ( 44 + <PubThemeSetter 45 + backToMenu={() => setState("menu")} 46 + loading={loading} 47 + setLoading={setLoading} 48 + /> 49 + ) : ( 50 + <PubSettingsMenu 51 + state={state} 52 + setState={setState} 53 + loading={loading} 54 + setLoading={setLoading} 55 + /> 56 + )} 57 + </Popover> 58 + ); 59 + } 60 + 61 + const PubSettingsMenu = (props: { 62 + state: "menu" | "general" | "theme"; 63 + setState: (s: typeof props.state) => void; 64 + loading: boolean; 65 + setLoading: (l: boolean) => void; 66 + }) => { 67 + let menuItemClassName = 68 + "menuItem -mx-[8px] text-left flex items-center justify-between hover:no-underline!"; 69 + 70 + return ( 71 + <div className="flex flex-col gap-0.5"> 72 + <PubSettingsHeader 73 + loading={props.loading} 74 + setLoadingAction={props.setLoading} 75 + state={"menu"} 76 + /> 77 + <button 78 + className={menuItemClassName} 79 + type="button" 80 + onClick={() => { 81 + props.setState("general"); 82 + }} 83 + > 84 + Publication Settings 85 + <ArrowRightTiny /> 86 + </button> 87 + <button 88 + className={menuItemClassName} 89 + type="button" 90 + onClick={() => props.setState("theme")} 91 + > 92 + Publication Theme 93 + <ArrowRightTiny /> 94 + </button> 95 + </div> 96 + ); 97 + }; 98 + 99 + export const PubSettingsHeader = (props: { 100 + state: "menu" | "general" | "theme"; 101 + backToMenuAction?: () => void; 102 + loading: boolean; 103 + setLoadingAction: (l: boolean) => void; 104 + }) => { 105 + return ( 106 + <div className="flex justify-between font-bold text-secondary bg-border-light -mx-3 -mt-2 px-3 py-2 mb-1"> 107 + {props.state === "menu" 108 + ? "Settings" 109 + : props.state === "general" 110 + ? "General" 111 + : props.state === "theme" 112 + ? "Publication Theme" 113 + : ""} 114 + {props.state !== "menu" && ( 115 + <div className="flex gap-2"> 116 + <button 117 + type="button" 118 + onClick={() => { 119 + props.backToMenuAction && props.backToMenuAction(); 120 + }} 121 + > 122 + <GoBackSmall className="text-accent-contrast" /> 123 + </button> 124 + 125 + <ButtonPrimary compact type="submit"> 126 + {props.loading ? <DotLoader /> : "Update"} 127 + </ButtonPrimary> 128 + </div> 129 + )} 130 + </div> 131 + ); 132 + };
+1 -1
app/lish/createPub/UpdatePubForm.tsx
··· 20 20 import Link from "next/link"; 21 21 import { Checkbox } from "components/Checkbox"; 22 22 import type { GetDomainConfigResponseBody } from "@vercel/sdk/esm/models/getdomainconfigop"; 23 - import { PubSettingsHeader } from "../[did]/[publication]/dashboard/Actions"; 23 + import { PubSettingsHeader } from "../[did]/[publication]/dashboard/PublicationSettings"; 24 24 25 25 export const EditPubForm = (props: { 26 26 backToMenuAction: () => void;
+15 -67
components/ActionBar/Navigation.tsx
··· 16 16 NotificationsUnreadSmall, 17 17 } from "components/Icons/NotificationSmall"; 18 18 import { SpeedyLink } from "components/SpeedyLink"; 19 - 20 - import { CommentNotification } from "app/(home-pages)/notifications/CommentNotication"; 21 19 import { Separator } from "components/Layout"; 22 - import { useIsMobile } from "src/hooks/isMobile"; 23 - import useSWR from "swr"; 24 - import { 25 - getNotifications, 26 - markAsRead, 27 - } from "app/(home-pages)/notifications/getNotifications"; 28 - import { DotLoader } from "components/utils/DotLoader"; 29 - import { NotificationList } from "app/(home-pages)/notifications/NotificationList"; 30 20 31 21 export type navPages = "home" | "reader" | "pub" | "discover" | "notifications"; 32 22 ··· 162 152 }; 163 153 164 154 export function NotificationButton(props: { current?: boolean }) { 165 - let { identity, mutate } = useIdentityData(); 155 + let { identity } = useIdentityData(); 166 156 let unreads = identity?.notifications[0]?.count; 167 - let isMobile = useIsMobile(); 168 - let { data: notifications, isLoading } = useSWR("notifications", () => 169 - getNotifications(3), 170 - ); 171 - // let identity = await getIdentityData(); 172 - // if (!identity?.atp_did) return; 173 - // let { data } = await supabaseServerClient 174 - // .from("notifications") 175 - // .select("*") 176 - // .eq("recipient", identity.atp_did); 177 - // let notifications = await hydrateNotifications(data || []); 178 157 179 158 return ( 180 - <Popover 181 - onOpenChange={async (open) => { 182 - if (open) { 183 - await markAsRead(); 184 - mutate(); 159 + <SpeedyLink href={"/notifications"} className="hover:no-underline!"> 160 + <ActionButton 161 + nav 162 + labelOnMobile={false} 163 + icon={ 164 + unreads ? ( 165 + <NotificationsUnreadSmall className="text-accent-contrast" /> 166 + ) : ( 167 + <NotificationsReadSmall /> 168 + ) 185 169 } 186 - }} 187 - asChild 188 - side={isMobile ? "top" : "right"} 189 - align={isMobile ? "center" : "start"} 190 - className="sm:max-w-sm sm:w-max w-full pt-3! pb-3!" 191 - trigger={ 192 - <ActionButton 193 - nav 194 - labelOnMobile={false} 195 - icon={ 196 - unreads ? ( 197 - <NotificationsUnreadSmall className="text-accent-contrast" /> 198 - ) : ( 199 - <NotificationsReadSmall /> 200 - ) 201 - } 202 - label="Notifications" 203 - className={`${props.current ? "bg-bg-page! border-border-light!" : ""} ${unreads ? "text-accent-contrast!" : ""}`} 204 - /> 205 - } 206 - > 207 - {isLoading ? ( 208 - <div className="flex items-center justify-center gap-1 text-tertiary italic text-sm p-3 sm:p-4"> 209 - <span>loading</span> 210 - <DotLoader /> 211 - </div> 212 - ) : ( 213 - <> 214 - <NotificationList compact notifications={notifications!} /> 215 - {notifications && notifications.length > 0 && ( 216 - <SpeedyLink 217 - className="flex justify-end pt-2 text-sm" 218 - href={"/notifications"} 219 - > 220 - See All 221 - </SpeedyLink> 222 - )} 223 - </> 224 - )} 225 - </Popover> 170 + label="Notifications" 171 + className={`${props.current ? "bg-bg-page! border-border-light!" : ""} ${unreads ? "text-accent-contrast!" : ""}`} 172 + /> 173 + </SpeedyLink> 226 174 ); 227 175 }
+1 -3
components/ThemeManager/PubThemeSetter.tsx
··· 10 10 import { useLocalPubTheme } from "./PublicationThemeProvider"; 11 11 import { BaseThemeProvider } from "./ThemeProvider"; 12 12 import { blobRefToSrc } from "src/utils/blobRefToSrc"; 13 - import { ButtonSecondary } from "components/Buttons"; 14 13 import { updatePublicationTheme } from "app/lish/createPub/updatePublication"; 15 - import { DotLoader } from "components/utils/DotLoader"; 16 14 import { PagePickers } from "./PubPickers/PubTextPickers"; 17 15 import { BackgroundPicker } from "./PubPickers/PubBackgroundPickers"; 18 16 import { PubAccentPickers } from "./PubPickers/PubAcccentPickers"; 19 17 import { Separator } from "components/Layout"; 20 - import { PubSettingsHeader } from "app/lish/[did]/[publication]/dashboard/Actions"; 18 + import { PubSettingsHeader } from "app/lish/[did]/[publication]/dashboard/PublicationSettings"; 21 19 22 20 export type ImageState = { 23 21 src: string;