feat: custom domain setting
This commit is contained in:
parent
c96edcdc79
commit
a23536bdda
|
|
@ -10,6 +10,7 @@ type InputProps<TMultiline extends boolean> = {
|
|||
error?: string
|
||||
help?: React.ReactNode
|
||||
multiline?: TMultiline
|
||||
onChangeText?: (value: string) => void
|
||||
} & React.ComponentPropsWithRef<TMultiline extends true ? "textarea" : "input">
|
||||
|
||||
export const Input = forwardRef(function Input<
|
||||
|
|
@ -24,6 +25,7 @@ export const Input = forwardRef(function Input<
|
|||
error,
|
||||
help,
|
||||
multiline,
|
||||
onChangeText,
|
||||
...inputProps
|
||||
}: InputProps<TMutliline>,
|
||||
ref: TMutliline extends true
|
||||
|
|
@ -53,6 +55,7 @@ export const Input = forwardRef(function Input<
|
|||
isBlock && `is-block`,
|
||||
className,
|
||||
)}
|
||||
onChange={onChangeText}
|
||||
/>
|
||||
{addon && (
|
||||
<span className="flex items-center px-3 text-gray-600 bg-gray-50 h-10 border border-l-0 rounded-r-lg relative -z-10">
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ textarea.input {
|
|||
@apply text-sm;
|
||||
@apply font-bold;
|
||||
@apply text-gray-700;
|
||||
@apply capitalize;
|
||||
}
|
||||
|
||||
.post-status-circle {
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ export type Profile = UniProfile & {
|
|||
navigation?: SiteNavigationItem[]
|
||||
css?: string
|
||||
ga?: string
|
||||
custom_domain?: string
|
||||
}
|
||||
|
||||
export type Profiles = {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<DashboardLayout title={"Domains"}>
|
||||
<SettingsLayout title={"Site Settings"} type="site">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="">
|
||||
<div>
|
||||
<Input
|
||||
id="subdomain"
|
||||
label="Subdomain"
|
||||
label={`${APP_NAME} subdomain`}
|
||||
addon={`.${OUR_DOMAIN}`}
|
||||
className="w-28"
|
||||
{...form.register("subdomain")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Input
|
||||
id="custom_domain"
|
||||
label="Custom Domain"
|
||||
className="w-64"
|
||||
onChangeText={customDomainChange}
|
||||
{...form.register("custom_domain")}
|
||||
/>
|
||||
{customDomain && (
|
||||
<div className="mt-2 text-xs">
|
||||
<p className="mb-2">
|
||||
Set the following record on your DNS provider to active your
|
||||
custom domain:
|
||||
</p>
|
||||
<table className="">
|
||||
<tbody>
|
||||
<tr className="border-b">
|
||||
<th className="text-center p-3">Type</th>
|
||||
<th className="text-center p-3">Name</th>
|
||||
<th className="text-center p-3">Value</th>
|
||||
</tr>
|
||||
<tr className="border-b">
|
||||
<td className="text-center p-3">CNAME</td>
|
||||
<td className="text-center p-3">
|
||||
{getSubdomain(customDomain) || "@"}
|
||||
</td>
|
||||
<td className="text-center p-3">cname.xlog.app</td>
|
||||
</tr>
|
||||
<tr className="border-b">
|
||||
<td className="text-center p-3">TXT</td>
|
||||
<td className="text-center p-3">{`_xlog-challenge.${getSubdomain(
|
||||
customDomain,
|
||||
)}`}</td>
|
||||
<td className="text-center p-3">{subdomain}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Button type="submit" isLoading={updateSite.isLoading}>
|
||||
Save
|
||||
|
|
|
|||
Loading…
Reference in New Issue