a tool for shared writing and social publishing
at feature/profiles 262 lines 7.8 kB view raw
1"use client"; 2import { ButtonPrimary } from "components/Buttons"; 3import { useActionState, useEffect, useState } from "react"; 4import { Input } from "components/Input"; 5import { useIdentityData } from "components/IdentityProvider"; 6import { 7 confirmEmailAuthToken, 8 requestAuthEmailToken, 9} from "actions/emailAuth"; 10import { subscribeToPublicationWithEmail } from "actions/subscribeToPublicationWithEmail"; 11import { ArrowRightTiny } from "components/Icons/ArrowRightTiny"; 12import { ShareSmall } from "components/Icons/ShareSmall"; 13import { Popover } from "components/Popover"; 14import { BlueskyTiny } from "components/Icons/BlueskyTiny"; 15import { useToaster } from "components/Toast"; 16import * as Dialog from "@radix-ui/react-dialog"; 17import { 18 subscribeToPublication, 19 unsubscribeToPublication, 20} from "./subscribeToPublication"; 21import { DotLoader } from "components/utils/DotLoader"; 22import { addFeed } from "./addFeed"; 23import { useSearchParams } from "next/navigation"; 24import LoginForm from "app/login/LoginForm"; 25import { RSSSmall } from "components/Icons/RSSSmall"; 26 27export const SubscribeWithBluesky = (props: { 28 pubName: string; 29 pub_uri: string; 30 base_url: string; 31 subscribers: { identity: string }[]; 32}) => { 33 let { identity } = useIdentityData(); 34 let searchParams = useSearchParams(); 35 let [successModalOpen, setSuccessModalOpen] = useState( 36 !!searchParams.has("showSubscribeSuccess"), 37 ); 38 let subscribed = 39 identity?.atp_did && 40 props.subscribers.find((s) => s.identity === identity.atp_did); 41 42 if (successModalOpen) 43 return ( 44 <SubscribeSuccessModal 45 open={successModalOpen} 46 setOpen={setSuccessModalOpen} 47 /> 48 ); 49 if (subscribed) { 50 return <ManageSubscription {...props} />; 51 } 52 return ( 53 <div className="flex flex-col gap-2 text-center justify-center"> 54 <div className="flex flex-row gap-2 place-self-center"> 55 <BlueskySubscribeButton 56 pub_uri={props.pub_uri} 57 setSuccessModalOpen={setSuccessModalOpen} 58 /> 59 <a 60 href={`${props.base_url}/rss`} 61 className="flex" 62 target="_blank" 63 aria-label="Subscribe to RSS" 64 > 65 <RSSSmall className="self-center" aria-hidden /> 66 </a> 67 </div> 68 </div> 69 ); 70}; 71 72export const ManageSubscription = (props: { 73 pub_uri: string; 74 subscribers: { identity: string }[]; 75 base_url: string; 76}) => { 77 let toaster = useToaster(); 78 let [hasFeed] = useState(false); 79 let [, unsubscribe, unsubscribePending] = useActionState(async () => { 80 await unsubscribeToPublication(props.pub_uri); 81 toaster({ 82 content: "You unsubscribed.", 83 type: "success", 84 }); 85 }, null); 86 return ( 87 <Popover 88 trigger={ 89 <div className="text-accent-contrast text-sm">Manage Subscription</div> 90 } 91 > 92 <div className="max-w-sm flex flex-col gap-1"> 93 <h4>Update Options</h4> 94 95 {!hasFeed && ( 96 <a 97 href="https://bsky.app/profile/leaflet.pub/feed/subscribedPublications" 98 target="_blank" 99 className=" place-self-center" 100 > 101 <ButtonPrimary fullWidth compact className="!px-4"> 102 View Bluesky Custom Feed 103 </ButtonPrimary> 104 </a> 105 )} 106 107 <a 108 href={`https://${props.base_url}/rss`} 109 className="flex" 110 target="_blank" 111 aria-label="Subscribe to RSS" 112 > 113 <ButtonPrimary fullWidth compact> 114 Get RSS 115 </ButtonPrimary> 116 </a> 117 118 <hr className="border-border-light my-1" /> 119 120 <form action={unsubscribe}> 121 <button className="font-bold text-accent-contrast w-max place-self-center"> 122 {unsubscribePending ? <DotLoader /> : "Unsubscribe"} 123 </button> 124 </form> 125 </div> 126 </Popover> 127 ); 128}; 129 130let BlueskySubscribeButton = (props: { 131 pub_uri: string; 132 setSuccessModalOpen: (open: boolean) => void; 133}) => { 134 let { identity } = useIdentityData(); 135 let toaster = useToaster(); 136 let [, subscribe, subscribePending] = useActionState(async () => { 137 let result = await subscribeToPublication( 138 props.pub_uri, 139 window.location.href + "?refreshAuth", 140 ); 141 if (result.hasFeed === false) { 142 props.setSuccessModalOpen(true); 143 } 144 toaster({ content: <div>You're Subscribed!</div>, type: "success" }); 145 }, null); 146 147 let [isClient, setIsClient] = useState(false); 148 useEffect(() => { 149 setIsClient(true); 150 }, []); 151 152 if (!identity?.atp_did) { 153 return ( 154 <Popover 155 asChild 156 trigger={ 157 <ButtonPrimary className="place-self-center"> 158 <BlueskyTiny /> Subscribe with Bluesky 159 </ButtonPrimary> 160 } 161 > 162 {isClient && ( 163 <LoginForm 164 text="Log in to subscribe to this publication!" 165 noEmail 166 redirectRoute={window?.location.href + "?refreshAuth"} 167 action={{ action: "subscribe", publication: props.pub_uri }} 168 /> 169 )} 170 </Popover> 171 ); 172 } 173 174 return ( 175 <> 176 <form 177 action={subscribe} 178 className="place-self-center flex flex-row gap-1" 179 > 180 <ButtonPrimary> 181 {subscribePending ? ( 182 <DotLoader /> 183 ) : ( 184 <> 185 <BlueskyTiny /> Subscribe with Bluesky 186 </> 187 )} 188 </ButtonPrimary> 189 </form> 190 </> 191 ); 192}; 193 194const SubscribeSuccessModal = ({ 195 open, 196 setOpen, 197}: { 198 open: boolean; 199 setOpen: (open: boolean) => void; 200}) => { 201 let searchParams = useSearchParams(); 202 let [loading, setLoading] = useState(false); 203 let toaster = useToaster(); 204 return ( 205 <Dialog.Root open={open} onOpenChange={setOpen}> 206 <Dialog.Trigger asChild></Dialog.Trigger> 207 <Dialog.Portal> 208 <Dialog.Overlay className="fixed inset-0 bg-primary data-[state=open]:animate-overlayShow opacity-10 blur-xs" /> 209 <Dialog.Content 210 className={` 211 z-20 opaque-container 212 fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 213 w-96 px-3 py-4 214 max-w-(--radix-popover-content-available-width) 215 max-h-(--radix-popover-content-available-height) 216 overflow-y-scroll no-scrollbar 217 flex flex-col gap-1 text-center justify-center 218 `} 219 > 220 <Dialog.Title asChild={true}> 221 <h3>Subscribed!</h3> 222 </Dialog.Title> 223 <Dialog.Description className="w-full flex flex-col"> 224 You'll get updates about this publication via a Feed just for you. 225 <ButtonPrimary 226 className="place-self-center mt-4" 227 onClick={async () => { 228 if (loading) return; 229 230 setLoading(true); 231 let feedurl = 232 "https://bsky.app/profile/leaflet.pub/feed/subscribedPublications"; 233 await addFeed(); 234 toaster({ content: "Feed added!", type: "success" }); 235 setLoading(false); 236 window.open(feedurl, "_blank"); 237 }} 238 > 239 {loading ? <DotLoader /> : "Add Bluesky Feed"} 240 </ButtonPrimary> 241 <button 242 className="text-accent-contrast mt-1" 243 onClick={() => { 244 const newUrl = new URL(window.location.href); 245 newUrl.searchParams.delete("showSubscribeSuccess"); 246 window.history.replaceState({}, "", newUrl.toString()); 247 setOpen(false); 248 }} 249 > 250 No thanks 251 </button> 252 </Dialog.Description> 253 <Dialog.Close /> 254 </Dialog.Content> 255 </Dialog.Portal> 256 </Dialog.Root> 257 ); 258}; 259 260export const SubscribeOnPost = () => { 261 return <div></div>; 262};