a tool for shared writing and social publishing

added a publish button if you don't have pubs yet

Changed files
+103 -44
app
components
+2
app/(home-pages)/home/Actions/CreateNewButton.tsx
··· 51 51 return ( 52 52 <Menu 53 53 asChild 54 + side="right" 55 + align="start" 54 56 trigger={ 55 57 <ActionButton 56 58 id="new-leaflet-button"
+3 -24
app/(home-pages)/home/HomeLayout.tsx
··· 1 1 "use client"; 2 2 3 - import { getHomeDocs, HomeDoc } from "./storage"; 3 + import { getHomeDocs } from "./storage"; 4 4 import useSWR from "swr"; 5 5 import { 6 6 Fact, ··· 13 13 import type { Attribute } from "src/replicache/attributes"; 14 14 import { callRPC } from "app/api/rpc/client"; 15 15 import { StaticLeafletDataContext } from "components/PageSWRDataProvider"; 16 - import { HomeSmall } from "components/Icons/HomeSmall"; 17 16 import { 18 17 HomeDashboardControls, 19 18 DashboardLayout, ··· 22 21 } from "components/PageLayouts/DashboardLayout"; 23 22 import { Actions } from "./Actions/Actions"; 24 23 import { useCardBorderHidden } from "components/Pages/useCardBorderHidden"; 25 - import { Json } from "supabase/database.types"; 26 24 import { useTemplateState } from "./Actions/CreateNewButton"; 27 - import { CreateNewLeafletButton } from "./Actions/CreateNewButton"; 28 - import { ActionButton } from "components/ActionBar/ActionButton"; 29 - import { AddTiny } from "components/Icons/AddTiny"; 30 - import { 31 - get_leaflet_data, 32 - GetLeafletDataReturnType, 33 - } from "app/api/rpc/[command]/get_leaflet_data"; 34 - import { useEffect, useRef, useState } from "react"; 35 - import { Input } from "components/Input"; 25 + import { GetLeafletDataReturnType } from "app/api/rpc/[command]/get_leaflet_data"; 26 + import { useState } from "react"; 36 27 import { useDebouncedEffect } from "src/hooks/useDebouncedEffect"; 37 - import { 38 - ButtonPrimary, 39 - ButtonSecondary, 40 - ButtonTertiary, 41 - } from "components/Buttons"; 42 - import { AddSmall } from "components/Icons/AddSmall"; 43 - import { PublishIllustration } from "app/[leaflet_id]/publish/PublishIllustration/PublishIllustration"; 44 - import { PubListEmptyIllo } from "components/ActionBar/Publications"; 45 - import { theme } from "tailwind.config"; 46 - import Link from "next/link"; 47 - import { DiscoverIllo } from "./HomeEmpty/DiscoverIllo"; 48 - import { WelcomeToLeafletIllo } from "./HomeEmpty/WelcomeToLeafletIllo"; 49 28 import { 50 29 DiscoverBanner, 51 30 HomeEmptyState,
+5
app/globals.css
··· 96 96 --accent-2: 255, 255, 255; 97 97 --accent-contrast: 0, 0, 225; 98 98 --accent-1-is-contrast: "true"; 99 + --accent-light: color-mix( 100 + in oklab, 101 + rgb(var(--accent-contrast)), 102 + rgb(var(--bg-page)) 85% 103 + ); 99 104 100 105 --highlight-1: 255, 177, 177; 101 106 --highlight-2: 253, 245, 203;
+14 -11
app/login/LoginForm.tsx
··· 13 13 import { useSmoker, useToaster } from "components/Toast"; 14 14 import React, { useState } from "react"; 15 15 import { mutate } from "swr"; 16 + import { BlueskyTiny } from "components/Icons/BlueskyTiny"; 16 17 17 18 export default function LoginForm(props: { 18 19 noEmail?: boolean; ··· 167 168 export function BlueskyLogin(props: { 168 169 redirectRoute?: string; 169 170 action?: ActionAfterSignIn; 171 + compact?: boolean; 170 172 }) { 171 173 const [signingWithHandle, setSigningWithHandle] = useState(false); 172 174 const [handle, setHandle] = useState(""); ··· 186 188 /> 187 189 )} 188 190 {signingWithHandle ? ( 189 - <div className="w-full flex flex-col gap-2"> 191 + <div className="w-full flex gap-1"> 190 192 <Input 191 193 type="text" 192 194 name="handle" ··· 197 199 onChange={(e) => setHandle(e.target.value)} 198 200 required 199 201 /> 200 - <ButtonPrimary type="submit" fullWidth className="py-2"> 201 - <BlueskySmall /> 202 - Sign In 203 - </ButtonPrimary> 202 + <ButtonPrimary type="submit">Sign In</ButtonPrimary> 204 203 </div> 205 204 ) : ( 206 - <div className="flex flex-col"> 207 - <ButtonPrimary fullWidth className="py-2"> 208 - <BlueskySmall /> 209 - Log In/Sign Up with Bluesky 205 + <div className="flex flex-col justify-center"> 206 + <ButtonPrimary 207 + fullWidth={!props.compact} 208 + compact={props.compact} 209 + className={`${props.compact ? "mx-auto text-sm" : "py-2"}`} 210 + > 211 + {props.compact ? <BlueskyTiny /> : <BlueskySmall />} 212 + {props.compact ? "Link" : "Log In/Sign Up with"} Bluesky 210 213 </ButtonPrimary> 211 214 <button 212 215 type="button" 213 - className="text-sm text-accent-contrast place-self-center mt-[6px]" 216 + className={`${props.compact ? "text-xs" : "text-sm"} text-accent-contrast place-self-center mt-[6px]`} 214 217 onClick={() => setSigningWithHandle(true)} 215 218 > 216 - or use an ATProto handle 219 + use an ATProto handle 217 220 </button> 218 221 </div> 219 222 )}
+4 -1
components/ActionBar/Navigation.tsx
··· 93 93 <HomeButton current={props.currentPage === "home"} /> 94 94 <ReaderButton 95 95 current={props.currentPage === "reader"} 96 - subs={identity?.publication_subscriptions?.length !== 0} 96 + subs={ 97 + identity?.publication_subscriptions?.length !== 0 && 98 + identity?.publication_subscriptions?.length !== undefined 99 + } 97 100 /> 98 101 <DiscoverButton current={props.currentPage === "discover"} /> 99 102
+73 -8
components/ActionBar/Publications.tsx
··· 10 10 import { ActionButton } from "./ActionButton"; 11 11 import { SpeedyLink } from "components/SpeedyLink"; 12 12 import { PublishSmall } from "components/Icons/PublishSmall"; 13 + import { Popover } from "components/Popover"; 14 + import { BlueskyLogin } from "app/login/LoginForm"; 15 + import { ButtonPrimary } from "components/Buttons"; 16 + import { useIsMobile } from "src/hooks/isMobile"; 17 + import { useState } from "react"; 13 18 14 19 export const PublicationButtons = (props: { 15 20 currentPubUri: string | undefined; ··· 18 23 19 24 // don't show pub list button if not logged in or no pub list 20 25 // we show a "start a pub" banner instead 21 - if (!identity || !identity.atp_did) return <PubListEmpty />; 26 + if (!identity || !identity.atp_did || identity.publications.length === 0) 27 + return <PubListEmpty />; 22 28 return ( 23 29 <div className="pubListWrapper w-full flex flex-col gap-1 sm:bg-transparent sm:border-0"> 24 30 {identity.publications?.map((d) => { 25 - // console.log("thisURI : " + d.uri); 26 - // console.log("currentURI : " + props.currentPubUri); 27 - 28 31 return ( 29 32 <PublicationOption 30 33 {...d} ··· 78 81 }; 79 82 80 83 const PubListEmpty = () => { 81 - return ( 82 - <SpeedyLink href={`lish/createPub`} className=" hover:no-underline!"> 84 + let { identity } = useIdentityData(); 85 + let isMobile = useIsMobile(); 86 + 87 + let [state, setState] = useState<"default" | "info">("default"); 88 + if (isMobile && state == "default") 89 + return ( 83 90 <ActionButton 84 91 label="Publish" 85 92 icon={<PublishSmall />} 86 93 nav 87 - subtext="Blog on ATProto!" 94 + subtext="Start a blog on ATProto!" 95 + onClick={() => { 96 + setState("info"); 97 + }} 88 98 /> 89 - </SpeedyLink> 99 + ); 100 + 101 + if (isMobile && state === "info") return <PublishPopoverContent />; 102 + else 103 + return ( 104 + <Popover 105 + side="right" 106 + align="start" 107 + className="p-1! max-w-56" 108 + trigger={ 109 + <ActionButton 110 + label="Publish" 111 + icon={<PublishSmall />} 112 + nav 113 + subtext="Start a blog on ATProto!" 114 + /> 115 + } 116 + > 117 + <PublishPopoverContent /> 118 + </Popover> 119 + ); 120 + }; 121 + 122 + const PublishPopoverContent = () => { 123 + let { identity } = useIdentityData(); 124 + 125 + return ( 126 + <div className="bg-[var(--accent-light)] w-full rounded-md flex flex-col text-center justify-center p-2 pb-4 text-sm"> 127 + <div className="mx-auto pt-2 scale-90"> 128 + <PubListEmptyIllo /> 129 + </div> 130 + <div className="pt-1 font-bold">Publish on AT Proto</div> 131 + {identity && identity.atp_did ? ( 132 + // has ATProto account and no pubs 133 + <> 134 + <div className="pb-2 text-secondary text-xs"> 135 + Start a new publication <br /> 136 + on AT Proto 137 + </div> 138 + <SpeedyLink href={`lish/createPub`} className=" hover:no-underline!"> 139 + <ButtonPrimary className="text-sm mx-auto" compact> 140 + Start a Publication! 141 + </ButtonPrimary> 142 + </SpeedyLink> 143 + </> 144 + ) : ( 145 + // no ATProto account and no pubs 146 + <> 147 + <div className="pb-2 text-secondary text-xs"> 148 + Link a Bluesky account to start a new publication on AT Proto 149 + </div> 150 + 151 + <BlueskyLogin compact /> 152 + </> 153 + )} 154 + </div> 90 155 ); 91 156 }; 92 157
+2
components/LoginButton.tsx
··· 29 29 return ( 30 30 <Popover 31 31 asChild 32 + align="start" 33 + side="right" 32 34 trigger={ 33 35 <ActionButton secondary icon={<AccountSmall />} label="Sign In" /> 34 36 }