mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-11 21:41:41 +02:00
🚧 redirect create form
This commit is contained in:
+1
-1
@@ -4397,13 +4397,13 @@
|
|||||||
"redirectSettings": "Redirect Settings",
|
"redirectSettings": "Redirect Settings",
|
||||||
"selectedRedirectDomain": "Selected Domain",
|
"selectedRedirectDomain": "Selected Domain",
|
||||||
"selectedRedirectResource": "Selected Resource",
|
"selectedRedirectResource": "Selected Resource",
|
||||||
|
"redirectResourceNoDomain": "This resource has no domain",
|
||||||
"redirectSettingsGeneralDescription": "Configure the basic redirect settings",
|
"redirectSettingsGeneralDescription": "Configure the basic redirect settings",
|
||||||
"redirectSettingsDescription": "Configure where requests come from and where they are sent",
|
"redirectSettingsDescription": "Configure where requests come from and where they are sent",
|
||||||
"redirectEnabledDescription": "Turn the redirect off to stop forwarding requests without deleting it",
|
"redirectEnabledDescription": "Turn the redirect off to stop forwarding requests without deleting it",
|
||||||
"redirectAttachedToDescription": "Choose whether this redirect applies to a whole domain or a single resource",
|
"redirectAttachedToDescription": "Choose whether this redirect applies to a whole domain or a single resource",
|
||||||
"redirectAttachDomain": "Domain",
|
"redirectAttachDomain": "Domain",
|
||||||
"redirectAttachResource": "Resource",
|
"redirectAttachResource": "Resource",
|
||||||
"redirectDomainSelect": "Select a domain",
|
|
||||||
"redirectDomainRequired": "Select a domain to attach this redirect to",
|
"redirectDomainRequired": "Select a domain to attach this redirect to",
|
||||||
"redirectResourceRequired": "Select a resource to attach this redirect to",
|
"redirectResourceRequired": "Select a resource to attach this redirect to",
|
||||||
"redirectPermanent": "Permanent Redirect",
|
"redirectPermanent": "Permanent Redirect",
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export const redirects = pgTable("redirects", {
|
|||||||
.$type<"exact" | "prefix" | "regex">()
|
.$type<"exact" | "prefix" | "regex">()
|
||||||
.notNull()
|
.notNull()
|
||||||
.default("regex"), // exact, prefix, regex
|
.default("regex"), // exact, prefix, regex
|
||||||
matchPath: varchar("matchPath").notNull().default("*"),
|
matchPath: varchar("matchPath").notNull().default(".*"),
|
||||||
rewritePath: varchar("rewritePath"), // if set, rewrites the path to this value,
|
rewritePath: varchar("rewritePath"), // if set, rewrites the path to this value,
|
||||||
// else, the original path will be kept
|
// else, the original path will be kept
|
||||||
rewritePathType: varchar("rewritePathType").$type<
|
rewritePathType: varchar("rewritePathType").$type<
|
||||||
|
|||||||
@@ -258,6 +258,7 @@ export const redirects = sqliteTable("redirects", {
|
|||||||
}),
|
}),
|
||||||
niceId: text("niceId").notNull(),
|
niceId: text("niceId").notNull(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
|
subdomain: text("subdomain"),
|
||||||
destinationDomain: text("destinationDomain").notNull(),
|
destinationDomain: text("destinationDomain").notNull(),
|
||||||
pathMatchType: text("pathMatchType")
|
pathMatchType: text("pathMatchType")
|
||||||
.$type<"exact" | "prefix" | "regex">()
|
.$type<"exact" | "prefix" | "regex">()
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const bodySchema = z.strictObject({
|
|||||||
name: z.string().nonempty(),
|
name: z.string().nonempty(),
|
||||||
resourceId: z.number().int().positive().optional().nullable(),
|
resourceId: z.number().int().positive().optional().nullable(),
|
||||||
domainId: z.string().nonempty().optional().nullable(),
|
domainId: z.string().nonempty().optional().nullable(),
|
||||||
|
subdomain: z.string().nonempty().optional().nullable(),
|
||||||
destinationDomain: z.string().nonempty(),
|
destinationDomain: z.string().nonempty(),
|
||||||
pathMatchType: redirectPathMatchTypeSchema.optional(),
|
pathMatchType: redirectPathMatchTypeSchema.optional(),
|
||||||
matchPath: redirectMatchPathSchema,
|
matchPath: redirectMatchPathSchema,
|
||||||
@@ -103,6 +104,7 @@ export async function createRedirect(
|
|||||||
name,
|
name,
|
||||||
resourceId,
|
resourceId,
|
||||||
domainId,
|
domainId,
|
||||||
|
subdomain,
|
||||||
destinationDomain,
|
destinationDomain,
|
||||||
pathMatchType,
|
pathMatchType,
|
||||||
matchPath,
|
matchPath,
|
||||||
@@ -170,6 +172,7 @@ export async function createRedirect(
|
|||||||
niceId,
|
niceId,
|
||||||
resourceId: resourceId ?? null,
|
resourceId: resourceId ?? null,
|
||||||
domainId: domainId ?? null,
|
domainId: domainId ?? null,
|
||||||
|
subdomain: subdomain ?? null,
|
||||||
destinationDomain,
|
destinationDomain,
|
||||||
pathMatchType: pathMatchType ?? "regex",
|
pathMatchType: pathMatchType ?? "regex",
|
||||||
matchPath,
|
matchPath,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type GetRedirectResponse = {
|
|||||||
orgId: string;
|
orgId: string;
|
||||||
niceId: string;
|
niceId: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
subdomain: string | null;
|
||||||
destinationDomain: string;
|
destinationDomain: string;
|
||||||
pathMatchType: "exact" | "prefix" | "regex";
|
pathMatchType: "exact" | "prefix" | "regex";
|
||||||
matchPath: string;
|
matchPath: string;
|
||||||
@@ -39,6 +40,7 @@ const redirectColumns = {
|
|||||||
orgId: redirects.orgId,
|
orgId: redirects.orgId,
|
||||||
niceId: redirects.niceId,
|
niceId: redirects.niceId,
|
||||||
name: redirects.name,
|
name: redirects.name,
|
||||||
|
subdomain: redirects.subdomain,
|
||||||
destinationDomain: redirects.destinationDomain,
|
destinationDomain: redirects.destinationDomain,
|
||||||
pathMatchType: redirects.pathMatchType,
|
pathMatchType: redirects.pathMatchType,
|
||||||
matchPath: redirects.matchPath,
|
matchPath: redirects.matchPath,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type ListRedirectsResponse = PaginatedResponse<{
|
|||||||
orgId: string;
|
orgId: string;
|
||||||
niceId: string;
|
niceId: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
subdomain: string | null;
|
||||||
destinationDomain: string;
|
destinationDomain: string;
|
||||||
pathMatchType: "exact" | "prefix" | "regex";
|
pathMatchType: "exact" | "prefix" | "regex";
|
||||||
matchPath: string;
|
matchPath: string;
|
||||||
@@ -137,6 +138,7 @@ export async function listRedirects(
|
|||||||
orgId: redirects.orgId,
|
orgId: redirects.orgId,
|
||||||
niceId: redirects.niceId,
|
niceId: redirects.niceId,
|
||||||
name: redirects.name,
|
name: redirects.name,
|
||||||
|
subdomain: redirects.subdomain,
|
||||||
destinationDomain: redirects.destinationDomain,
|
destinationDomain: redirects.destinationDomain,
|
||||||
pathMatchType: redirects.pathMatchType,
|
pathMatchType: redirects.pathMatchType,
|
||||||
matchPath: redirects.matchPath,
|
matchPath: redirects.matchPath,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const bodySchema = z.strictObject({
|
|||||||
niceId: redirectNiceIdSchema.optional(),
|
niceId: redirectNiceIdSchema.optional(),
|
||||||
resourceId: z.number().int().positive().optional().nullable(),
|
resourceId: z.number().int().positive().optional().nullable(),
|
||||||
domainId: z.string().nonempty().optional().nullable(),
|
domainId: z.string().nonempty().optional().nullable(),
|
||||||
|
subdomain: z.string().nonempty().optional().nullable(),
|
||||||
destinationDomain: z.string().nonempty().optional(),
|
destinationDomain: z.string().nonempty().optional(),
|
||||||
pathMatchType: redirectPathMatchTypeSchema.optional(),
|
pathMatchType: redirectPathMatchTypeSchema.optional(),
|
||||||
matchPath: redirectMatchPathSchema.optional(),
|
matchPath: redirectMatchPathSchema.optional(),
|
||||||
@@ -196,6 +197,9 @@ export async function updateRedirect(
|
|||||||
if (body.domainId !== undefined) {
|
if (body.domainId !== undefined) {
|
||||||
updateData.domainId = body.domainId;
|
updateData.domainId = body.domainId;
|
||||||
}
|
}
|
||||||
|
if (body.subdomain !== undefined) {
|
||||||
|
updateData.subdomain = body.subdomain;
|
||||||
|
}
|
||||||
if (body.destinationDomain !== undefined) {
|
if (body.destinationDomain !== undefined) {
|
||||||
updateData.destinationDomain = body.destinationDomain;
|
updateData.destinationDomain = body.destinationDomain;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export default async function RedirectIndexPage(props: RedirectIndexPageProps) {
|
|||||||
redirectId: redirect.redirectId,
|
redirectId: redirect.redirectId,
|
||||||
niceId: redirect.niceId,
|
niceId: redirect.niceId,
|
||||||
name: redirect.name,
|
name: redirect.name,
|
||||||
|
subdomain: redirect.subdomain,
|
||||||
destinationDomain: redirect.destinationDomain,
|
destinationDomain: redirect.destinationDomain,
|
||||||
pathMatchType: redirect.pathMatchType,
|
pathMatchType: redirect.pathMatchType,
|
||||||
matchPath: redirect.matchPath,
|
matchPath: redirect.matchPath,
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
|
|||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
import { cn } from "@app/lib/cn";
|
import { cn } from "@app/lib/cn";
|
||||||
import { orgQueries } from "@app/lib/queries";
|
|
||||||
import { CaretSortIcon } from "@radix-ui/react-icons";
|
import { CaretSortIcon } from "@radix-ui/react-icons";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import type {
|
import type {
|
||||||
@@ -49,7 +48,6 @@ import type {
|
|||||||
GetRedirectResponse
|
GetRedirectResponse
|
||||||
} from "@server/routers/redirect";
|
} from "@server/routers/redirect";
|
||||||
import type { AxiosResponse } from "axios";
|
import type { AxiosResponse } from "axios";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
@@ -63,9 +61,10 @@ import {
|
|||||||
PathRewriteModal
|
PathRewriteModal
|
||||||
} from "@app/components/PathMatchRenameModal";
|
} from "@app/components/PathMatchRenameModal";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
|
import DomainPicker from "@app/components/DomainPicker";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
const DEFAULT_MATCH_PATH = "*";
|
const DEFAULT_MATCH_PATH = ".*";
|
||||||
const DEFAULT_PATH_MATCH_TYPE = "regex" as const;
|
const DEFAULT_PATH_MATCH_TYPE = "regex" as const;
|
||||||
|
|
||||||
export type ExistingRedirect = GetRedirectResponse["redirect"];
|
export type ExistingRedirect = GetRedirectResponse["redirect"];
|
||||||
@@ -95,8 +94,6 @@ export default function RedirectForm({
|
|||||||
const [selectedResource, setSelectedResource] =
|
const [selectedResource, setSelectedResource] =
|
||||||
useState<SelectedResource | null>(initialResource);
|
useState<SelectedResource | null>(initialResource);
|
||||||
|
|
||||||
const { data: domains = [] } = useQuery(orgQueries.domains({ orgId }));
|
|
||||||
|
|
||||||
const formSchema = useMemo(
|
const formSchema = useMemo(
|
||||||
() =>
|
() =>
|
||||||
z
|
z
|
||||||
@@ -107,6 +104,7 @@ export default function RedirectForm({
|
|||||||
.min(1, { message: t("nameRequired") }),
|
.min(1, { message: t("nameRequired") }),
|
||||||
attachTo: z.enum(["domain", "resource"]),
|
attachTo: z.enum(["domain", "resource"]),
|
||||||
domainId: z.string().nullable(),
|
domainId: z.string().nullable(),
|
||||||
|
subdomain: z.string().nullable(),
|
||||||
resourceId: z.number().int().positive().nullable(),
|
resourceId: z.number().int().positive().nullable(),
|
||||||
destinationDomain: z
|
destinationDomain: z
|
||||||
.string()
|
.string()
|
||||||
@@ -163,6 +161,7 @@ export default function RedirectForm({
|
|||||||
name: redirect?.name ?? "",
|
name: redirect?.name ?? "",
|
||||||
attachTo: redirect?.resourceId ? "resource" : "domain",
|
attachTo: redirect?.resourceId ? "resource" : "domain",
|
||||||
domainId: redirect?.domainId ?? null,
|
domainId: redirect?.domainId ?? null,
|
||||||
|
subdomain: redirect?.subdomain ?? null,
|
||||||
resourceId: redirect?.resourceId ?? null,
|
resourceId: redirect?.resourceId ?? null,
|
||||||
destinationDomain: redirect?.destinationDomain ?? "",
|
destinationDomain: redirect?.destinationDomain ?? "",
|
||||||
pathMatchType: redirect?.pathMatchType ?? DEFAULT_PATH_MATCH_TYPE,
|
pathMatchType: redirect?.pathMatchType ?? DEFAULT_PATH_MATCH_TYPE,
|
||||||
@@ -190,6 +189,8 @@ export default function RedirectForm({
|
|||||||
const body = {
|
const body = {
|
||||||
name: values.name.trim(),
|
name: values.name.trim(),
|
||||||
domainId: values.attachTo === "domain" ? values.domainId : null,
|
domainId: values.attachTo === "domain" ? values.domainId : null,
|
||||||
|
subdomain:
|
||||||
|
values.attachTo === "domain" ? values.subdomain || null : null,
|
||||||
resourceId:
|
resourceId:
|
||||||
values.attachTo === "resource" ? values.resourceId : null,
|
values.attachTo === "resource" ? values.resourceId : null,
|
||||||
destinationDomain: values.destinationDomain.trim(),
|
destinationDomain: values.destinationDomain.trim(),
|
||||||
@@ -283,7 +284,7 @@ export default function RedirectForm({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
<SettingsSection>
|
<SettingsSection className="pb-10">
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
{t("general")}
|
{t("general")}
|
||||||
@@ -301,6 +302,35 @@ export default function RedirectForm({
|
|||||||
id="redirect-form"
|
id="redirect-form"
|
||||||
>
|
>
|
||||||
<SettingsFormGrid>
|
<SettingsFormGrid>
|
||||||
|
<SettingsFormCell span="full">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="enabled"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<SwitchInput
|
||||||
|
id="redirect-enabled"
|
||||||
|
label={t(
|
||||||
|
"enabled"
|
||||||
|
)}
|
||||||
|
description={t(
|
||||||
|
"redirectEnabledDescription"
|
||||||
|
)}
|
||||||
|
checked={
|
||||||
|
field.value
|
||||||
|
}
|
||||||
|
onCheckedChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
<SettingsFormCell span="full">
|
<SettingsFormCell span="full">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -368,56 +398,41 @@ export default function RedirectForm({
|
|||||||
/>
|
/>
|
||||||
</SettingsFormCell>
|
</SettingsFormCell>
|
||||||
{attachTo === "domain" ? (
|
{attachTo === "domain" ? (
|
||||||
<SettingsFormCell span="half">
|
<SettingsFormCell span="full">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="domainId"
|
name="domainId"
|
||||||
render={({ field }) => (
|
render={() => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<DomainPicker
|
||||||
{t(
|
orgId={orgId}
|
||||||
"selectedRedirectDomain"
|
cols={1}
|
||||||
)}
|
hideFreeDomain
|
||||||
</FormLabel>
|
defaultDomainId={
|
||||||
<Select
|
redirect?.domainId
|
||||||
value={
|
|
||||||
field.value ??
|
|
||||||
undefined
|
|
||||||
}
|
}
|
||||||
onValueChange={
|
allowWildcard
|
||||||
field.onChange
|
defaultSubdomain={
|
||||||
|
redirect?.subdomain
|
||||||
}
|
}
|
||||||
>
|
onDomainChange={(
|
||||||
<FormControl>
|
res
|
||||||
<SelectTrigger>
|
) => {
|
||||||
<SelectValue
|
form.setValue(
|
||||||
placeholder={t(
|
"domainId",
|
||||||
"redirectDomainSelect"
|
res?.domainId ??
|
||||||
)}
|
null,
|
||||||
/>
|
{
|
||||||
</SelectTrigger>
|
shouldValidate: true
|
||||||
</FormControl>
|
}
|
||||||
<SelectContent>
|
);
|
||||||
{domains.map(
|
form.setValue(
|
||||||
(
|
"subdomain",
|
||||||
domain
|
res?.subdomain ||
|
||||||
) => (
|
null
|
||||||
<SelectItem
|
);
|
||||||
key={
|
}}
|
||||||
domain.domainId
|
/>
|
||||||
}
|
|
||||||
value={
|
|
||||||
domain.domainId
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
domain.baseDomain
|
|
||||||
}
|
|
||||||
</SelectItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
@@ -484,6 +499,33 @@ export default function RedirectForm({
|
|||||||
/>
|
/>
|
||||||
</SettingsFormCell>
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{attachTo === "resource" && (
|
||||||
|
<SettingsFormCell span="full">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t("resourceDomain")}
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
disabled
|
||||||
|
readOnly
|
||||||
|
value={
|
||||||
|
selectedResource?.fullDomain ??
|
||||||
|
""
|
||||||
|
}
|
||||||
|
placeholder={
|
||||||
|
selectedResource
|
||||||
|
? t(
|
||||||
|
"redirectResourceNoDomain"
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
"resourceSelect"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</SettingsFormCell>
|
||||||
|
)}
|
||||||
</SettingsFormGrid>
|
</SettingsFormGrid>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -491,7 +533,7 @@ export default function RedirectForm({
|
|||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<SettingsSection>
|
<SettingsSection className="pb-10">
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
{t("redirectSettings")}
|
{t("redirectSettings")}
|
||||||
@@ -504,33 +546,9 @@ export default function RedirectForm({
|
|||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<SettingsSectionForm variant="half">
|
<SettingsSectionForm variant="half">
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
id="redirect-form"
|
|
||||||
>
|
|
||||||
<SettingsFormGrid>
|
<SettingsFormGrid>
|
||||||
<SettingsFormCell span="full">
|
<SettingsFormCell span="full">
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="name"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
{t("name")}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
autoComplete="off"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SettingsFormCell>
|
|
||||||
|
|
||||||
<SettingsFormCell span="half">
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="destinationDomain"
|
name="destinationDomain"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export type RedirectRow = {
|
|||||||
redirectId: number;
|
redirectId: number;
|
||||||
niceId: string;
|
niceId: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
subdomain: string | null;
|
||||||
destinationDomain: string;
|
destinationDomain: string;
|
||||||
pathMatchType: "exact" | "prefix" | "regex";
|
pathMatchType: "exact" | "prefix" | "regex";
|
||||||
matchPath: string;
|
matchPath: string;
|
||||||
@@ -200,8 +201,14 @@ export default function RedirectsTable({
|
|||||||
),
|
),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const redirect = row.original;
|
const redirect = row.original;
|
||||||
const host =
|
// A domain-attached redirect may target a specific host
|
||||||
redirect.resourceFullDomain ?? redirect.baseDomain;
|
// under the base domain, e.g. old.example.com.
|
||||||
|
const domainHost = redirect.baseDomain
|
||||||
|
? [redirect.subdomain, redirect.baseDomain]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(".")
|
||||||
|
: null;
|
||||||
|
const host = redirect.resourceFullDomain ?? domainHost;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -237,7 +244,13 @@ export default function RedirectsTable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (redirect.baseDomain) {
|
if (redirect.baseDomain) {
|
||||||
return <span>{redirect.baseDomain}</span>;
|
return (
|
||||||
|
<span>
|
||||||
|
{[redirect.subdomain, redirect.baseDomain]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(".")}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <span>-</span>;
|
return <span>-</span>;
|
||||||
|
|||||||
+67
-15
@@ -1,23 +1,46 @@
|
|||||||
import { cn } from "@app/lib/cn";
|
import { cn } from "@app/lib/cn";
|
||||||
|
|
||||||
export function SettingsContainer({ children }: { children: React.ReactNode }) {
|
export function SettingsContainer({
|
||||||
return <div className="space-y-6">{children}</div>;
|
children,
|
||||||
|
className
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return <div className={cn("space-y-6", className)}>{children}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSection({ children }: { children: React.ReactNode }) {
|
export function SettingsSection({
|
||||||
|
children,
|
||||||
|
className
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="border rounded-lg bg-card p-5 flex flex-col min-h-[200px]">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"border rounded-lg bg-card p-5 flex flex-col min-h-[200px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSectionHeader({
|
export function SettingsSectionHeader({
|
||||||
children
|
children,
|
||||||
|
className
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return <div className="text-lg space-y-0.5 pb-6">{children}</div>;
|
return (
|
||||||
|
<div className={cn("text-lg space-y-0.5 pb-6", className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSectionForm({
|
export function SettingsSectionForm({
|
||||||
@@ -87,23 +110,36 @@ export function SettingsFormCell({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSectionTitle({
|
export function SettingsSectionTitle({
|
||||||
children
|
children,
|
||||||
|
className
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<h2 className="text-1xl font-semibold tracking-tight flex items-center gap-2">
|
<h2
|
||||||
|
className={cn(
|
||||||
|
"text-1xl font-semibold tracking-tight flex items-center gap-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</h2>
|
</h2>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSectionDescription({
|
export function SettingsSectionDescription({
|
||||||
children
|
children,
|
||||||
|
className
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return <p className="text-muted-foreground text-sm">{children}</p>;
|
return (
|
||||||
|
<p className={cn("text-muted-foreground text-sm", className)}>
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSubsectionHeader({
|
export function SettingsSubsectionHeader({
|
||||||
@@ -141,11 +177,15 @@ export function SettingsSubsectionDescription({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSectionBody({
|
export function SettingsSectionBody({
|
||||||
children
|
children,
|
||||||
|
className
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return <div className="space-y-5 flex-grow">{children}</div>;
|
return (
|
||||||
|
<div className={cn("space-y-5 flex-grow", className)}>{children}</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSectionFooter({
|
export function SettingsSectionFooter({
|
||||||
@@ -169,10 +209,22 @@ export function SettingsSectionFooter({
|
|||||||
|
|
||||||
export function SettingsSectionGrid({
|
export function SettingsSectionGrid({
|
||||||
children,
|
children,
|
||||||
cols
|
cols = 4,
|
||||||
|
className
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
cols: number;
|
cols?: number;
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return <div className={`grid md:grid-cols-${cols} gap-6`}>{children}</div>;
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
// @ts-expect-error
|
||||||
|
"--cols": `repeat(${cols}, minmax(0, 1fr))`
|
||||||
|
}}
|
||||||
|
className={cn(`grid md:grid-cols-(--cols) gap-6`, className)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user