a tool for shared writing and social publishing

add some loaders

+31 -2
+5 -1
app/lish/createPub/CreatePubForm.tsx
··· 11 11 import { theme } from "tailwind.config"; 12 12 import { getBasePublicationURL, getPublicationURL } from "./getPublicationURL"; 13 13 import { string } from "zod"; 14 + import { DotLoader } from "components/utils/DotLoader"; 14 15 15 16 type DomainState = 16 17 | { status: "empty" } ··· 20 21 | { status: "error"; message: string }; 21 22 22 23 export const CreatePubForm = () => { 24 + let [formState, setFormState] = useState<"normal" | "loading">("normal"); 23 25 let [nameValue, setNameValue] = useState(""); 24 26 let [descriptionValue, setDescriptionValue] = useState(""); 25 27 let [logoFile, setLogoFile] = useState<File | null>(null); ··· 37 39 onSubmit={async (e) => { 38 40 e.preventDefault(); 39 41 if (!subdomainValidator.safeParse(domainValue).success) return; 42 + setFormState("loading"); 40 43 let data = await createPublication({ 41 44 name: nameValue, 42 45 description: descriptionValue, ··· 45 48 }); 46 49 // Show a spinner while this is happening! Maybe a progress bar? 47 50 setTimeout(() => { 51 + setFormState("normal"); 48 52 if (data?.publication) 49 53 router.push(`${getBasePublicationURL(data.publication)}/dashboard`); 50 54 }, 500); ··· 121 125 !nameValue || !domainValue || domainState.status !== "valid" 122 126 } 123 127 > 124 - Create Publication! 128 + {formState === "loading" ? <DotLoader /> : "Create Publication!"} 125 129 </ButtonPrimary> 126 130 </div> 127 131 </form>
+8 -1
app/lish/createPub/UpdatePubForm.tsx
··· 10 10 import { PubLeafletPublication } from "lexicons/api"; 11 11 import { mutate } from "swr"; 12 12 import { AddTiny } from "components/Icons/AddTiny"; 13 + import { DotLoader } from "components/utils/DotLoader"; 14 + import { useToaster } from "components/Toast"; 13 15 14 16 export const EditPubForm = () => { 15 17 let pubData = usePublicationData(); 16 18 let record = pubData?.record as PubLeafletPublication.Record; 19 + let [formState, setFormState] = useState<"normal" | "loading">("normal"); 17 20 18 21 let [nameValue, setNameValue] = useState(record?.name || ""); 19 22 let [descriptionValue, setDescriptionValue] = useState( ··· 31 34 `url(https://bsky.social/xrpc/com.atproto.sync.getBlob?did=${pubData.identity_did}&cid=${(record.icon.ref as unknown as { $link: string })["$link"]})`, 32 35 ); 33 36 }, [pubData]); 37 + let toast = useToaster(); 34 38 35 39 return ( 36 40 <form ··· 38 42 onSubmit={async (e) => { 39 43 if (!pubData) return; 40 44 e.preventDefault(); 45 + setFormState("loading"); 41 46 let data = await updatePublication({ 42 47 uri: pubData.uri, 43 48 name: nameValue, 44 49 description: descriptionValue, 45 50 iconFile: iconFile, 46 51 }); 52 + toast({ type: "success", content: "Updated!" }); 53 + setFormState("normal"); 47 54 mutate("publication-data"); 48 55 }} 49 56 > ··· 108 115 /> 109 116 110 117 <ButtonPrimary className="place-self-end" type="submit"> 111 - Update Publication 118 + {formState === "loading" ? <DotLoader /> : "Update Publication"} 112 119 </ButtonPrimary> 113 120 </form> 114 121 );
+18
components/utils/DotLoader.tsx
··· 1 + import { useEffect, useState } from "react"; 2 + 3 + export function DotLoader() { 4 + let [dots, setDots] = useState(1); 5 + useEffect(() => { 6 + let id = setInterval(() => { 7 + setDots((count) => (count + 1) % 4); 8 + }, 250); 9 + return () => { 10 + clearInterval(id); 11 + }; 12 + }, []); 13 + return ( 14 + <div className="w-[26px] text-center text-sm"> 15 + {".".repeat(dots) + "\u00a0".repeat(3 - dots)} 16 + </div> 17 + ); 18 + }