Something went wrong. Try again.
a tool for shared writing and social publishing
Something went wrong. Try again.
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247"use client";import { callRPC } from "app/api/rpc/client";import { createPublication } from "./createPublication";import { ButtonPrimary } from "components/Buttons";import { AddSmall } from "components/Icons/AddSmall";import { useIdentityData } from "components/IdentityProvider";import { Input, InputWithLabel } from "components/Input";import { useRouter } from "next/navigation";import { useState, useRef, useEffect } from "react";import { useDebouncedEffect } from "src/hooks/useDebouncedEffect";import { theme } from "tailwind.config";import { getBasePublicationURL, getPublicationURL } from "./getPublicationURL";import { string } from "zod";import { DotLoader } from "components/utils/DotLoader";import { Checkbox } from "components/Checkbox";
type DomainState = | { status: "empty" } | { status: "valid" } | { status: "invalid" } | { status: "pending" } | { status: "error"; message: string };
export const CreatePubForm = () => { let [formState, setFormState] = useState<"normal" | "loading">("normal"); let [nameValue, setNameValue] = useState(""); let [descriptionValue, setDescriptionValue] = useState(""); let [showInDiscover, setShowInDiscover] = useState(true); let [logoFile, setLogoFile] = useState<File | null>(null); let [logoPreview, setLogoPreview] = useState<string | null>(null); let [domainValue, setDomainValue] = useState(""); let [domainState, setDomainState] = useState<DomainState>({ status: "empty", }); let fileInputRef = useRef<HTMLInputElement>(null);
let router = useRouter(); return ( <form className="flex flex-col gap-3" onSubmit={async (e) => { if (formState !== "normal") return; e.preventDefault(); if (!subdomainValidator.safeParse(domainValue).success) return; setFormState("loading"); let data = await createPublication({ name: nameValue, description: descriptionValue, iconFile: logoFile, subdomain: domainValue, preferences: { showInDiscover, showComments: true }, }); // Show a spinner while this is happening! Maybe a progress bar? setTimeout(() => { setFormState("normal"); if (data?.publication) router.push(`${getBasePublicationURL(data.publication)}/dashboard`); }, 500); }} > <div className="flex flex-col items-center mb-4 gap-2"> <div className="text-center text-secondary flex flex-col "> <h3 className="-mb-1">Logo</h3> <p className="italic text-tertiary">(optional)</p> </div> <div className="w-24 h-24 rounded-full border-2 border-dotted border-accent-1 flex items-center justify-center cursor-pointer hover:border-accent-contrast" onClick={() => fileInputRef.current?.click()} > {logoPreview ? ( <img src={logoPreview} alt="Logo preview" className="w-full h-full rounded-full object-cover" /> ) : ( <AddSmall className="text-accent-1" /> )} </div> <input type="file" accept="image/*" className="hidden" ref={fileInputRef} onChange={(e) => { const file = e.target.files?.[0]; if (file) { setLogoFile(file); const reader = new FileReader(); reader.onload = (e) => { setLogoPreview(e.target?.result as string); }; reader.readAsDataURL(file); } }} /> </div> <InputWithLabel type="text" id="pubName" label="Publication Name" value={nameValue} onChange={(e) => { setNameValue(e.currentTarget.value); }} />
<InputWithLabel label="Description (optional)" textarea rows={3} id="pubDescription" value={descriptionValue} onChange={(e) => { setDescriptionValue(e.currentTarget.value); }} /> <DomainInput domain={domainValue} setDomain={setDomainValue} domainState={domainState} setDomainState={setDomainState} /> <hr className="border-border-light" /> <Checkbox checked={showInDiscover} onChange={(e) => setShowInDiscover(e.target.checked)} > <div className=" pt-0.5 flex flex-col text-sm text-tertiary "> <p className="font-bold italic"> Show In{" "} <a href="/discover" target="_blank"> Discover </a> </p> <p className="text-sm text-tertiary font-normal"> You'll be able to change this later! </p> </div> </Checkbox> <hr className="border-border-light" />
<div className="flex w-full justify-center"> <ButtonPrimary type="submit" disabled={ !nameValue || !domainValue || domainState.status !== "valid" } > {formState === "loading" ? <DotLoader /> : "Create Publication!"} </ButtonPrimary> </div> </form> );};
let subdomainValidator = string() .min(3) .max(63) .regex(/^[a-z0-9-]+$/);function DomainInput(props: { domain: string; setDomain: (d: string) => void; domainState: DomainState; setDomainState: (s: DomainState) => void;}) { useEffect(() => { if (!props.domain) { props.setDomainState({ status: "empty" }); } else { let valid = subdomainValidator.safeParse(props.domain); if (!valid.success) { let reason = valid.error.errors[0].code; props.setDomainState({ status: "error", message: reason === "too_small" ? "Must be at least 3 characters long" : reason === "invalid_string" ? "Must contain only lowercase a-z, 0-9, and -" : "", }); return; } props.setDomainState({ status: "pending" }); } }, [props.domain]);
useDebouncedEffect( async () => { if (!props.domain) return props.setDomainState({ status: "empty" });
let valid = subdomainValidator.safeParse(props.domain); if (!valid.success) { return; } let status = await callRPC("get_leaflet_subdomain_status", { domain: props.domain, }); if (status.error === "Not Found") props.setDomainState({ status: "valid" }); else props.setDomainState({ status: "invalid" }); }, 500, [props.domain], );
return ( <div className="flex flex-col gap-1"> <label className=" input-with-border flex flex-col text-sm text-tertiary font-bold italic leading-tight py-1! px-[6px]!"> <div>Choose your domain</div> <div className="flex flex-row items-center"> <Input minLength={3} maxLength={63} placeholder="domain" className="appearance-none w-full font-normal bg-transparent text-base text-primary focus:outline-0 outline-hidden" value={props.domain} onChange={(e) => props.setDomain(e.currentTarget.value)} /> .leaflet.pub </div> </label> <div className={"text-sm italic "} style={{ fontWeight: props.domainState.status === "valid" ? "bold" : "normal", color: props.domainState.status === "valid" ? theme.colors["accent-contrast"] : theme.colors.tertiary, }} > {props.domainState.status === "valid" ? "Available!" : props.domainState.status === "error" ? props.domainState.message : props.domainState.status === "invalid" ? "Already Taken ):" : props.domainState.status === "pending" ? "Checking Availability..." : "a-z, 0-9, and - only!"} </div> </div> );}