diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx index 109aa1a6..b98ab253 100644 --- a/src/components/ui/Input.tsx +++ b/src/components/ui/Input.tsx @@ -10,6 +10,7 @@ type InputProps = { error?: string help?: React.ReactNode multiline?: TMultiline + onChangeText?: (value: string) => void } & React.ComponentPropsWithRef export const Input = forwardRef(function Input< @@ -24,6 +25,7 @@ export const Input = forwardRef(function Input< error, help, multiline, + onChangeText, ...inputProps }: InputProps, ref: TMutliline extends true @@ -53,6 +55,7 @@ export const Input = forwardRef(function Input< isBlock && `is-block`, className, )} + onChange={onChangeText} /> {addon && ( diff --git a/src/css/tailwind.css b/src/css/tailwind.css index 801c6fe0..ddba9706 100644 --- a/src/css/tailwind.css +++ b/src/css/tailwind.css @@ -196,6 +196,7 @@ textarea.input { @apply text-sm; @apply font-bold; @apply text-gray-700; + @apply capitalize; } .post-status-circle { diff --git a/src/lib/types.ts b/src/lib/types.ts index e8a42506..31cad907 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -85,6 +85,7 @@ export type Profile = UniProfile & { navigation?: SiteNavigationItem[] css?: string ga?: string + custom_domain?: string } export type Profiles = { diff --git a/src/models/membership.ts b/src/models/membership.ts deleted file mode 100644 index 245eb2af..00000000 --- a/src/models/membership.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { string } from "zod" -import { prismaRead, MembershipRole, prismaPrimary } from "~/lib/db.server" - -export const getMembership = async (data: { - siteId: string - userId: string - role: MembershipRole -}) => { - const first = await prismaRead.membership.findFirst({ - where: { - role: data.role, - userId: data.userId, - siteId: data.siteId, - }, - }) - - return first -} - -export const getMemberships = async ({ - userId, - // Only return memberships that the user is an owner/admin of - canManage, -}: { - userId: string - canManage?: boolean -}) => { - const memberships = await prismaRead.membership.findMany({ - where: { - userId, - role: canManage - ? { - in: [MembershipRole.ADMIN, MembershipRole.OWNER], - } - : undefined, - }, - include: { - site: true, - }, - }) - - return memberships -} - -export const updateMembership = async ( - membershipId: string, - input: { lastSwitchedTo?: Date }, -) => { - await prismaPrimary.membership.update({ - where: { - id: membershipId, - }, - data: { - lastSwitchedTo: input.lastSwitchedTo, - }, - }) -} diff --git a/src/models/site.model.ts b/src/models/site.model.ts index ef8bec56..3995033d 100644 --- a/src/models/site.model.ts +++ b/src/models/site.model.ts @@ -89,6 +89,10 @@ export const getSite = async (input: string) => { site.ga = site.metadata?.raw?.attributes?.find((a: any) => a.trait_type === "xlog_ga") ?.value || "" + site.custom_domain = + site.metadata?.raw?.attributes?.find( + (a: any) => a.trait_type === "xlog_custom_domain", + )?.value || "" site.name = site.name || site.username return site @@ -118,6 +122,7 @@ export async function updateSite(payload: { navigation?: SiteNavigationItem[] css?: string ga?: string + custom_domain?: string }) { return await unidata.profiles.set( { @@ -131,7 +136,10 @@ export async function updateSite(payload: { ...(payload.description && { bio: payload.description }), ...(payload.icon && { avatars: [payload.icon] }), ...(payload.subdomain && { username: payload.subdomain }), - ...((payload.navigation || payload.css || payload.ga) && { + ...((payload.navigation || + payload.css || + payload.ga || + payload.custom_domain) && { attributes: [ ...(payload.navigation ? [ @@ -157,6 +165,14 @@ export async function updateSite(payload: { }, ] : []), + ...(payload.custom_domain + ? [ + { + trait_type: "xlog_custom_domain", + value: payload.custom_domain, + }, + ] + : []), ], }), }, diff --git a/src/pages/dashboard/[subdomain]/settings/domains.tsx b/src/pages/dashboard/[subdomain]/settings/domains.tsx index 63627a01..120c4120 100644 --- a/src/pages/dashboard/[subdomain]/settings/domains.tsx +++ b/src/pages/dashboard/[subdomain]/settings/domains.tsx @@ -6,7 +6,7 @@ import { DashboardLayout } from "~/components/dashboard/DashboardLayout" import { SettingsLayout } from "~/components/dashboard/SettingsLayout" import { Button } from "~/components/ui/Button" import { Input } from "~/components/ui/Input" -import { OUR_DOMAIN } from "~/lib/env" +import { OUR_DOMAIN, APP_NAME } from "~/lib/env" import { useGetSite, useUpdateSite } from "~/queries/site" export default function SettingsDomainsPage() { @@ -19,6 +19,7 @@ export default function SettingsDomainsPage() { const form = useForm({ defaultValues: { subdomain: "", + custom_domain: "", }, }) @@ -26,9 +27,19 @@ export default function SettingsDomainsPage() { updateSite.mutate({ site: subdomain, subdomain: values.subdomain, + custom_domain: values.custom_domain, }) }) + const getSubdomain = (domain?: string) => { + return domain?.split(".").slice(0, -2).join(".") || "" + } + + const [customDomain, setCustomDomain] = useState("") + const customDomainChange = (e: any) => { + setCustomDomain(e.target.value) + } + useEffect(() => { if (updateSite.isSuccess) { if (updateSite.data?.code === 0) { @@ -42,28 +53,72 @@ export default function SettingsDomainsPage() { } else if (updateSite.isError) { toast.error("Failed to update site") } - }, [updateSite]) + }, [updateSite.data?.code]) + const [hasSet, setHasSet] = useState(false) useEffect(() => { - if (site.isSuccess && site.data && !form.getValues("subdomain")) { + if (site.isSuccess && site.data && !hasSet) { + setHasSet(true) form.setValue("subdomain", site.data.username || "") + form.setValue("custom_domain", site.data.custom_domain || "") + setCustomDomain(site.data.custom_domain || "") } - }, [form, site.data, site.isSuccess]) + }, [form, site.data, site.isSuccess, customDomain]) const title = "Site Settings" return (
-
+
+
+ + {customDomain && ( +
+

+ Set the following record on your DNS provider to active your + custom domain: +

+ + + + + + + + + + + + + + + + + + +
TypeNameValue
CNAME + {getSubdomain(customDomain) || "@"} + cname.xlog.app
TXT{`_xlog-challenge.${getSubdomain( + customDomain, + )}`}{subdomain}
+
+ )} +