Something went wrong. Try again.
a tool for shared writing and social publishing
Something went wrong. Try again.
13 kB · 426 lines
TSX
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427"use client";import { useState } from "react";import { useDomainStatus } from "./useDomainStatus";import { DotLoader } from "components/utils/DotLoader";import { deleteDomain, removeDomainAssignment, removeDomainRoute,} from "actions/domains";import { useIdentityData, mutateIdentityData,} from "components/IdentityProvider";import { ButtonPrimary, ButtonTertiary } from "components/Buttons";import { getDomainAssignment } from "./domainAssignment";import { GoToArrow } from "components/Icons/GoToArrow";import { useSmoker } from "components/Toast";import type { CustomDomain } from "./DomainList";import { UnlinkTiny } from "components/Icons/UnlinkTiny";
export function DomainSettingsView(props: { domain: string; onBack: () => void; onRemoveAssignment?: () => void; onDeleteDomain?: () => void;}) { let { data, pending: needsSetup, mutate: mutateDomainStatus, } = useDomainStatus(props.domain); let { identity, mutate: mutateIdentity } = useIdentityData(); let isSubdomain = props.domain.split(".").length > 2;
let domainData = identity?.custom_domains.find( (d) => d.domain === props.domain, ); let assignment = domainData ? getDomainAssignment(domainData) : null; let isAssigned = assignment && assignment.type !== "unassigned";
return ( <div className="flex flex-col gap-[6px] text-sm text-primary max-w-full"> <div className="w-full flex gap-2 items-center"> <h3 className="w-full grow min-w-0 truncate"> {needsSetup ? `Verify ${props.domain}` : props.domain} </h3> <button className="text-accent-contrast rotate-180 shrink-0" onMouseDown={() => props.onBack()} type="button" > <GoToArrow /> </button> </div>
{needsSetup ? ( <> <div> To verify this domain, add the following record to your DNS provider for <strong>{props.domain}</strong>. </div> <div>Verfication may take up to a few hours to process.</div> <table className="border border-border-light rounded-md text-left mt-2"> <thead> <tr> <th className="px-2 pt-1 text-tertiary">Type</th> <th className="px-2 pt-1 text-tertiary">Name</th> <th className="px-2 pt-1 text-tertiary">Value</th> </tr> </thead> <tbody> {data?.verification && ( <tr> <td className="px-2 pb-1"> <div>{data.verification[0].type}</div> </td> <td className="px-2 pb-1"> <div style={{ wordBreak: "break-word" }}> {data.verification[0].domain} </div> </td> <td className="px-2 pb-1"> <div style={{ wordBreak: "break-word" }}> {data.verification[0].value} </div> </td> </tr> )} {data?.config && (isSubdomain ? ( <tr> <td className="px-2 pb-1"> <div>CNAME</div> </td> <td className="px-2 pb-1"> <div style={{ wordBreak: "break-word" }}> {props.domain.split(".").slice(0, -2).join(".")} </div> </td> <td className="px-2 pb-1"> <div style={{ wordBreak: "break-word" }}> { data.config.recommendedCNAME.sort( (a, b) => a.rank - b.rank, )[0].value } </div> </td> </tr> ) : ( <tr> <td className="px-2 pb-1"> <div>A</div> </td> <td className="px-2 pb-1"> <div style={{ wordBreak: "break-word" }}>@</div> </td> <td className="px-2 pb-1"> <div style={{ wordBreak: "break-word" }}> { data.config.recommendedIPv4.sort( (a, b) => a.rank - b.rank, )[0].value[0] } </div> </td> </tr> ))} </tbody> </table> <VerifyButton verify={() => mutateDomainStatus()} /> </> ) : null} {/* Assignment list */} {!isAssigned && ( <div className="text-secondary"> <div className="font-bold"> You haven't assigned this domain to anything yet! </div> You can assign it to either a publication from your pub settings, or to any leaflet! </div> )} {props.onRemoveAssignment && isAssigned && domainData && ( <div className="flex flex-col gap-1 mt-1"> <h4 className="text-tertiary text-sm"> Assigned{" "} {domainData.publication_domains.length > 0 ? "Publications" : "Leaflets"} </h4> {domainData.publication_domains.length > 0 ? ( <AssignedPublication domainData={domainData} domain={props.domain} onRemoveAssignment={props.onRemoveAssignment} mutateIdentity={mutateIdentity} isLastAssignment /> ) : ( domainData.custom_domain_routes.map((route) => ( <AssignedLeaflet key={route.id} route={route} domain={props.domain} onRemoveAssignment={props.onRemoveAssignment} mutateIdentity={mutateIdentity} isLastAssignment={domainData.custom_domain_routes.length === 1} /> )) )} </div> )} <div className="flex flex-col gap-2 mt-2"> <hr className="border-border-light" />
<DeleteDomainButton domain={props.domain} onDeleteDomain={props.onDeleteDomain} mutateIdentity={mutateIdentity} /> </div> </div> );}let containerClassName = "flex items-center justify-between px-[6px] py-1 border rounded-md border-border-light";
const AssignedPublication = (props: { domainData: CustomDomain; domain: string; onRemoveAssignment?: () => void; mutateIdentity: ReturnType<typeof useIdentityData>["mutate"]; isLastAssignment: boolean;}) => { let [areYouSure, setAreYouSure] = useState(false);
if (areYouSure) { return ( <UnassignAreYouSure {...props} closeAreYouSure={() => { setAreYouSure(false); }} /> ); } return ( <div className={containerClassName}> <span className="text-secondary truncate"> {props.domainData.publication_domains[0]?.publications?.name ?? "publication"} </span> <button className="text-tertiary hover:text-accent-contrast text-sm" type="button" onMouseDown={() => { setAreYouSure(true); }} > <UnlinkTiny /> </button> </div> );};
const AssignedLeaflet = (props: { route: { id: string; edit_permission_token: string; route: string; }; domain: string; onRemoveAssignment?: () => void; mutateIdentity: ReturnType<typeof useIdentityData>["mutate"]; isLastAssignment: boolean;}) => { let [areYouSure, setAreYouSure] = useState(false);
if (areYouSure) { return ( <UnassignAreYouSure {...props} routeId={props.route.id} closeAreYouSure={() => { setAreYouSure(false); }} /> ); } return ( <div className={containerClassName}> <a href={`/${props.route.edit_permission_token}`} className="text-accent-contrast no-underline! truncate" > {props.route.route} </a> <button className="text-tertiary hover:text-accent-contrast " type="button" onMouseDown={() => { setAreYouSure(true); }} > <UnlinkTiny /> </button> </div> );};
const UnassignAreYouSure = (props: { domain: string; routeId?: string; isLastAssignment: boolean; onRemoveAssignment?: () => void; mutateIdentity: ReturnType<typeof useIdentityData>["mutate"]; closeAreYouSure: () => void;}) => { let { domain, routeId, isLastAssignment, onRemoveAssignment, mutateIdentity, } = props; return ( <div className={`${containerClassName}`}> <p className="text-secondary text-sm font-bold">Are You Sure?</p> <div className="flex gap-2 items-center"> <ButtonPrimary compact type="button" className="text-sm -my-px" onMouseDown={async () => { if (routeId) { mutateIdentityData(mutateIdentity, (draft) => { let domainData = draft.custom_domains.find( (d) => d.domain === domain, ); if (domainData) { domainData.custom_domain_routes = domainData.custom_domain_routes.filter( (r) => r.id !== routeId, ); } }); if (isLastAssignment) onRemoveAssignment?.(); await removeDomainRoute({ routeId }); } else { mutateIdentityData(mutateIdentity, (draft) => { let domainData = draft.custom_domains.find( (d) => d.domain === domain, ); if (domainData) { domainData.publication_domains = []; } }); onRemoveAssignment?.(); await removeDomainAssignment({ domain }); } }} > Confirm </ButtonPrimary> <button className="text-accent-contrast font-bold" type="button" onMouseDown={() => props.closeAreYouSure()} > Nevermind </button> </div> </div> );};
function DeleteDomainButton(props: { domain: string; onDeleteDomain?: () => void; mutateIdentity: ReturnType<typeof useIdentityData>["mutate"];}) { let [confirming, setConfirming] = useState(false); let [loading, setLoading] = useState(false);
if (!props.onDeleteDomain) return null;
if (!confirming) { return ( <ButtonTertiary fullWidth compact type="button" onMouseDown={() => setConfirming(true)} > Delete Domain </ButtonTertiary> ); }
return ( <div className="flex flex-col gap-1 text-sm accent-container rounded-md p-2"> <p className="text-secondary text-center"> Are you sure you want to delete <strong>{props.domain}</strong>? This will remove all assignments and cannot be undone. </p> <div className="flex gap-2 justify-center"> <button className="text-accent-contrast font-bold" onMouseDown={() => setConfirming(false)} type="button" > Cancel </button> <ButtonPrimary compact className="text-sm" disabled={loading} onMouseDown={async () => { setLoading(true); mutateIdentityData(props.mutateIdentity, (draft) => { draft.custom_domains = draft.custom_domains.filter( (d) => d.domain !== props.domain, ); }); props.onDeleteDomain?.(); await deleteDomain({ domain: props.domain }); }} > {loading ? <DotLoader /> : "Delete"} </ButtonPrimary> </div> </div> );}
function VerifyButton(props: { verify: () => Promise<any> }) { let [loading, setLoading] = useState(false); let smoker = useSmoker(); return ( <ButtonPrimary fullWidth className="mt-2" type="button" onClick={async (e) => { e.preventDefault(); setLoading(true); let result = await props.verify(); setLoading(false); let stillPending = result?.config?.misconfigured || result?.verification; if (stillPending) { smoker({ position: { x: e.clientX, y: e.clientY - 5, }, text: "Still processing", }); } }} > {loading ? <DotLoader /> : "Check Status"} </ButtonPrimary> );}