diff --git a/actions/getIdentityData.ts b/actions/getIdentityData.ts index 68ca4e09..a2c05347 100644 --- a/actions/getIdentityData.ts +++ b/actions/getIdentityData.ts @@ -20,7 +20,7 @@ export async function uncachedGetIdentityData() { bsky_profiles(*), notifications(count), publication_subscriptions(*), - custom_domains!custom_domains_identity_id_fkey(publication_domains(*), custom_domain_routes(*), *), + custom_domains!custom_domains_identity_id_fkey(publication_domains(*, publications(name)), custom_domain_routes(*), *), home_leaflet:permission_tokens!identities_home_page_fkey(*, permission_token_rights(*, entity_sets(entities(facts(*))) )), diff --git a/app/[leaflet_id]/actions/ShareOptions/DomainOptions.tsx b/app/[leaflet_id]/actions/ShareOptions/DomainOptions.tsx index fb3042d9..ae8af172 100644 --- a/app/[leaflet_id]/actions/ShareOptions/DomainOptions.tsx +++ b/app/[leaflet_id]/actions/ShareOptions/DomainOptions.tsx @@ -173,7 +173,7 @@ const DomainOptions = (props: { } return ( -
+

Choose a Domain


-
+
{linkedDomains.length > 0 && (
- Currently Linked {linkedDomains.map((domain: CustomDomain) => ( )} -
- Add or delete domains from your profile on{" "} +
+ You can add or delete domains from profile settings on{" "} home diff --git a/app/lish/Subscribe.tsx b/app/lish/Subscribe.tsx index 118292cb..3ce02990 100644 --- a/app/lish/Subscribe.tsx +++ b/app/lish/Subscribe.tsx @@ -262,10 +262,10 @@ const SubscribeSuccessModal = ({ - +

Create Your Publication!

-
+
diff --git a/components/Domains/AddDomainForm.tsx b/components/Domains/AddDomainForm.tsx index cf1d3720..4a042070 100644 --- a/components/Domains/AddDomainForm.tsx +++ b/components/Domains/AddDomainForm.tsx @@ -6,6 +6,7 @@ import { useSmoker } from "components/Toast"; import { useIdentityData } from "components/IdentityProvider"; import { addDomain } from "actions/domains/addDomain"; import { DotLoader } from "components/utils/DotLoader"; +import { GoToArrow } from "components/Icons/GoToArrow"; export function AddDomainForm(props: { onDomainAdded: (domain: string) => void; @@ -15,10 +16,47 @@ export function AddDomainForm(props: { let [loading, setLoading] = useState(false); let { mutate } = useIdentityData(); let smoker = useSmoker(); + let rect = document + .getElementById("add-domain-button") + ?.getBoundingClientRect(); return ( -
-

Add a Domain

+
{ + setLoading(true); + let { error } = await addDomain(value); + if (error) { + setLoading(false); + smoker({ + error: true, + text: + error === "invalid_domain" + ? "Invalid domain! Use just the base domain" + : error === "domain_already_in_use" + ? "That domain is already in use!" + : "An unknown error occured", + position: { + y: rect ? rect?.right - rect?.width / 2 : 0, + x: rect ? rect?.bottom - rect?.height / 2 : 0, + }, + }); + return; + } + mutate(); + props.onDomainAdded(value); + }} + > +
+

Add a Domain

+ +
Just include the base domain
Don't include the protocol{" "} @@ -28,48 +66,21 @@ export function AddDomainForm(props: { setValue(e.target.value)} /> -
- +
{ - setLoading(true); - let { error } = await addDomain(value); - if (error) { - setLoading(false); - smoker({ - error: true, - text: - error === "invalid_domain" - ? "Invalid domain! Use just the base domain" - : error === "domain_already_in_use" - ? "That domain is already in use!" - : "An unknown error occured", - position: { - y: e.clientY, - x: e.clientX - 5, - }, - }); - return; - } - mutate(); - props.onDomainAdded(value); - }} > - {loading ? : "Verify Domain"} + {loading ? : "Add Domain"}
-
+ ); } diff --git a/components/Domains/DomainList.tsx b/components/Domains/DomainList.tsx index 76436a9c..e3fc93a2 100644 --- a/components/Domains/DomainList.tsx +++ b/components/Domains/DomainList.tsx @@ -1,14 +1,9 @@ "use client"; -import { useState } from "react"; import { useIdentityData } from "components/IdentityProvider"; import { useDomainStatus } from "./useDomainStatus"; import { getDomainAssignment } from "./domainAssignment"; -import { AddTiny } from "components/Icons/AddTiny"; -import { UnlinkTiny } from "components/Icons/UnlinkTiny"; -import { ButtonPrimary, ButtonTertiary } from "components/Buttons"; -import { useToaster } from "components/Toast"; -import { Separator } from "components/Layout"; import { Identity } from "components/IdentityProvider"; +import { GoToArrow } from "components/Icons/GoToArrow"; export type CustomDomain = NonNullable["custom_domains"][number]; @@ -34,7 +29,7 @@ export function DomainList(props: {
{pubDomains.length > 0 && (
-
Publications
+
Publications
{pubDomains.map((domain) => ( 0 && (
-
Leaflets
+
Leaflets
{leafletDomains.map((domain) => ( 0 && ( <> {(pubDomains.length > 0 || leafletDomains.length > 0) && ( -
+
)}
-
Unassigned
+
Unassigned
{unassignedDomains.map((domain) => ( void; showBase: boolean; }) { - let { pending } = useDomainStatus(props.domain.domain); let assignment = getDomainAssignment(props.domain); return ( @@ -95,15 +89,15 @@ function DomainGroup(props: { {props.showBase && ( )} {assignment.type === "publication" && ( @@ -111,17 +105,8 @@ function DomainGroup(props: { path="/" label={props.domain.domain} onSelect={props.onSelect} - isPub /> )} - {props.domain.custom_domain_routes.map((route) => ( - - ))}
); } @@ -130,18 +115,16 @@ function SubDomainRow(props: { path: string; label: string; onSelect: () => void; - isPub?: boolean; }) { return ( ); @@ -156,15 +139,14 @@ function UnassignedDomainRow(props: { return ( ); } diff --git a/components/Domains/DomainSettingsView.tsx b/components/Domains/DomainSettingsView.tsx index c3f3b884..fb209078 100644 --- a/components/Domains/DomainSettingsView.tsx +++ b/components/Domains/DomainSettingsView.tsx @@ -11,8 +11,10 @@ import { } from "components/IdentityProvider"; import { ButtonPrimary, ButtonTertiary } from "components/Buttons"; import { getDomainAssignment } from "./domainAssignment"; -import { RefreshSmall } from "components/Icons/RefreshSmall"; 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; @@ -47,7 +49,6 @@ export function DomainSettingsView(props: { > - {needsSetup && mutateDomainStatus()} />}
{needsSetup ? ( @@ -56,7 +57,8 @@ export function DomainSettingsView(props: { To verify this domain, add the following record to your DNS provider for {props.domain}.
- +
Verfication may take up to a few hours to process.
+
@@ -124,76 +126,49 @@ export function DomainSettingsView(props: { ))}
Type
+ mutateDomainStatus()} /> ) : null} - {/* Assignment list */} + {!isAssigned && ( +
+
+ You haven't assigned this domain to anything yet! +
+ You can assign it to either a publication from your pub settings, or + to any leaflet! +
+ )} {props.onRemoveAssignment && isAssigned && domainData && (
-

Assigned to

- {domainData.publication_domains.length > 0 && ( -
- publication - -
+

+ Assigned{" "} + {domainData.publication_domains.length > 0 + ? "Publications" + : "Leaflets"} +

+ {domainData.publication_domains.length > 0 ? ( + + ) : ( + domainData.custom_domain_routes.map((route) => ( + + )) )} - {domainData.custom_domain_routes.map((route) => ( -
- - {route.route} - - -
- ))}
)} -

@@ -206,6 +181,157 @@ export function DomainSettingsView(props: {
); } +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["mutate"]; + isLastAssignment: boolean; +}) => { + let [areYouSure, setAreYouSure] = useState(false); + + if (areYouSure) { + return ( + { + setAreYouSure(false); + }} + /> + ); + } + return ( +
+ + {props.domainData.publication_domains[0]?.publications?.name ?? + "publication"} + + +
+ ); +}; + +const AssignedLeaflet = (props: { + route: { + id: string; + edit_permission_token: string; + route: string; + }; + domain: string; + onRemoveAssignment?: () => void; + mutateIdentity: ReturnType["mutate"]; + isLastAssignment: boolean; +}) => { + let [areYouSure, setAreYouSure] = useState(false); + + if (areYouSure) { + return ( + { + setAreYouSure(false); + }} + /> + ); + } + return ( +
+ + {props.route.route} + + +
+ ); +}; + +const UnassignAreYouSure = (props: { + domain: string; + routeId?: string; + isLastAssignment: boolean; + onRemoveAssignment?: () => void; + mutateIdentity: ReturnType["mutate"]; + closeAreYouSure: () => void; +}) => { + let { + domain, + routeId, + isLastAssignment, + onRemoveAssignment, + mutateIdentity, + } = props; + return ( +
+

Are You Sure?

+
+ { + 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 + + +
+
+ ); +}; function DeleteDomainButton(props: { domain: string; @@ -268,16 +394,28 @@ function DeleteDomainButton(props: { function VerifyButton(props: { verify: () => Promise }) { let [loading, setLoading] = useState(false); + let smoker = useSmoker(); return ( { e.preventDefault(); setLoading(true); - await props.verify(); + 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 ? : "Check Status"} diff --git a/components/Domains/PublicationDomains.tsx b/components/Domains/PublicationDomains.tsx index 6739dad3..8e61b00a 100644 --- a/components/Domains/PublicationDomains.tsx +++ b/components/Domains/PublicationDomains.tsx @@ -21,6 +21,7 @@ import { PinTiny } from "components/Icons/PinTiny"; import { LoadingTiny } from "components/Icons/LoadingTiny"; import { AddTiny } from "components/Icons/AddTiny"; import { DotLoader } from "components/utils/DotLoader"; +import { useToaster } from "components/Toast"; import type { CustomDomain } from "./DomainList"; type State = @@ -34,7 +35,8 @@ export function PublicationDomains(props: { }) { let [state, setState] = useState("list"); let [loading, setLoading] = useState(false); - let { data } = usePublicationData(); + let { data, mutate: mutatePubData } = usePublicationData(); + let toaster = useToaster(); let { publication: pubData } = data || {}; let record = useNormalizedPublicationRecord(); let { identity, mutate: mutateIdentity } = useIdentityData(); @@ -93,40 +95,79 @@ export function PublicationDomains(props: { Domains
- {pubDomains.map((d) => ( - - setState({ type: "domain-settings", domain: d.domain }) - } - /> - ))} - {(identity?.custom_domains || []) - .filter((d) => !pubDomainNames.has(d.domain)) +

This Publication's Domains

+
DEFAULT
+ {pubDomains + .filter((d) => d.domain === basePath) .map((d) => ( - { - mutateIdentity(); - mutate("publication-data"); - }} + basePath={basePath} + mutatePubData={mutatePubData} onSettings={() => setState({ type: "domain-settings", domain: d.domain }) } /> ))} - + {pubDomains.filter((d) => d.domain !== basePath).length !== 0 && ( + <> +
ALTERNATES
+ {pubDomains + .filter((d) => d.domain !== basePath) + .map((d) => ( + + setState({ type: "domain-settings", domain: d.domain }) + } + /> + ))} + + )} +
+

Available Domains

+ {(() => { + let availableDomains = (identity?.custom_domains || []) + .filter((d) => !pubDomainNames.has(d.domain)) + .filter( + (d) => + d.publication_domains.length === 0 && + d.custom_domain_routes.length === 0, + ); + return availableDomains.length > 0 ? ( + <> + {availableDomains.map((d) => ( + { + mutateIdentity(); + mutate("publication-data"); + }} + onSettings={() => + setState({ type: "domain-settings", domain: d.domain }) + } + /> + ))} +
+ Add new domains from your profile settings! +
+ + ) : ( +
+ No available domains! +
+ Add new domains from your profile settings! +
+ ); + })()}
); @@ -136,10 +177,12 @@ function PubDomainRow(props: { domain: string; publication_uri: string; basePath: string; + mutatePubData: ReturnType["mutate"]; onSettings: () => void; }) { let { pending } = useDomainStatus(props.domain); let [loading, setLoading] = useState(false); + let toaster = useToaster(); return (
@@ -162,36 +205,58 @@ function PubDomainRow(props: {

- ) : props.basePath === props.domain ? ( -
-

- current default domain -

- -
- ) : ( + ) : props.basePath === props.domain ? null : ( @@ -221,12 +286,16 @@ function UnassignedDomainRow(props: { ); if (domain) { domain.custom_domain_routes = []; + let pub = draft.publications?.find( + (p) => p.uri === props.publication_uri, + ); domain.publication_domains = [ { publication: props.publication_uri, domain: props.domainData.domain, identity: "", created_at: new Date().toISOString(), + publications: pub ? { name: pub.name } : null, }, ]; } @@ -270,7 +339,7 @@ function UnassignedDomainRow(props: { } }} > - {loading ? : "assign"} + {loading ? : "assign"} )}
diff --git a/components/Icons/GoToArrow.tsx b/components/Icons/GoToArrow.tsx index 55bf39ce..d5f6395d 100644 --- a/components/Icons/GoToArrow.tsx +++ b/components/Icons/GoToArrow.tsx @@ -8,6 +8,7 @@ export const GoToArrow = (props: Props) => { viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" + {...props} > {trigger} - + {title ? ( -

{title}

+

{title}

- ) : null} + ) : ( + + )}
{children}
diff --git a/components/OAuthError.tsx b/components/OAuthError.tsx index 32c79552..01c5732d 100644 --- a/components/OAuthError.tsx +++ b/components/OAuthError.tsx @@ -12,8 +12,8 @@ export function OAuthErrorMessage({ const signInUrl = `/api/oauth/login?redirect_url=${encodeURIComponent(window.location.href)}${error.did ? `&handle=${encodeURIComponent(error.did)}` : ""}`; return ( -
- Your session has expired or is invalid. +
+ Your session has expired or is invalid Sign in again diff --git a/components/Toast.tsx b/components/Toast.tsx index d7fd453c..5adfdc40 100644 --- a/components/Toast.tsx +++ b/components/Toast.tsx @@ -139,7 +139,7 @@ const Smoke: React.FC< > = (props) => { return (