From 4e842a660a37874cc9770ef1ac157c031b0f6070 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 10 Dec 2025 21:15:42 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20wip:=20refactor=20proxy=20resour?= =?UTF-8?q?ce=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/proxy/[niceId]/general/page.tsx | 668 ++++++++---------- .../resources/proxy/[niceId]/layout.tsx | 3 +- 2 files changed, 286 insertions(+), 385 deletions(-) diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx index fa9a6976..6712f201 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx @@ -1,8 +1,5 @@ "use client"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; -import { formatAxiosError } from "@app/lib/api"; import { Button } from "@/components/ui/button"; import { Form, @@ -15,31 +12,10 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { useResourceContext } from "@app/hooks/useResourceContext"; -import { ListSitesResponse } from "@server/routers/site"; -import { useEffect, useState } from "react"; -import { AxiosResponse } from "axios"; -import { useParams, useRouter } from "next/navigation"; -import { useForm } from "react-hook-form"; -import { toast } from "@app/hooks/useToast"; -import { - SettingsContainer, - SettingsSection, - SettingsSectionHeader, - SettingsSectionTitle, - SettingsSectionDescription, - SettingsSectionBody, - SettingsSectionForm, - SettingsSectionFooter -} from "@app/components/Settings"; -import { useOrgContext } from "@app/hooks/useOrgContext"; -import { createApiClient } from "@app/lib/api"; -import { useEnvContext } from "@app/hooks/useEnvContext"; -import { Label } from "@app/components/ui/label"; -import { ListDomainsResponse } from "@server/routers/domain"; -import { UpdateResourceResponse } from "@server/routers/resource"; -import { SwitchInput } from "@app/components/SwitchInput"; -import { useTranslations } from "next-intl"; -import { Checkbox } from "@app/components/ui/checkbox"; +import { formatAxiosError } from "@app/lib/api"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + import { Credenza, CredenzaBody, @@ -51,26 +27,37 @@ import { CredenzaTitle } from "@app/components/Credenza"; import DomainPicker from "@app/components/DomainPicker"; -import { Globe } from "lucide-react"; -import { build } from "@server/build"; +import { + SettingsContainer, + SettingsSection, + SettingsSectionBody, + SettingsSectionDescription, + SettingsSectionFooter, + SettingsSectionForm, + SettingsSectionHeader, + SettingsSectionTitle +} from "@app/components/Settings"; +import { SwitchInput } from "@app/components/SwitchInput"; +import { Label } from "@app/components/ui/label"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { toast } from "@app/hooks/useToast"; +import { createApiClient } from "@app/lib/api"; import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils"; -import { DomainRow } from "@app/components/DomainsTable"; +import { UpdateResourceResponse } from "@server/routers/resource"; +import { AxiosResponse } from "axios"; +import { Globe } from "lucide-react"; +import { useTranslations } from "next-intl"; +import { useParams, useRouter } from "next/navigation"; import { toASCII, toUnicode } from "punycode"; -import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; -import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext"; -import { useUserContext } from "@app/hooks/useUserContext"; +import { useActionState, useMemo, useState } from "react"; +import { useForm } from "react-hook-form"; export default function GeneralForm() { - const [formKey, setFormKey] = useState(0); const params = useParams(); const { resource, updateResource } = useResourceContext(); - const { org } = useOrgContext(); const router = useRouter(); const t = useTranslations(); const [editDomainOpen, setEditDomainOpen] = useState(false); - const { licenseStatus } = useLicenseStatusContext(); - const subscriptionStatus = useSubscriptionStatusContext(); - const { user } = useUserContext(); const { env } = useEnvContext(); @@ -78,18 +65,30 @@ export default function GeneralForm() { const api = createApiClient({ env }); - const [sites, setSites] = useState([]); - const [saveLoading, setSaveLoading] = useState(false); - const [transferLoading, setTransferLoading] = useState(false); - const [open, setOpen] = useState(false); - const [baseDomains, setBaseDomains] = useState< - ListDomainsResponse["domains"] - >([]); - - const [loadingPage, setLoadingPage] = useState(true); const [resourceFullDomain, setResourceFullDomain] = useState( `${resource.ssl ? "https" : "http"}://${toUnicode(resource.fullDomain || "")}` ); + + console.log({ resource }); + + const [defaultSubdomain, defaultBaseDomain] = useMemo(() => { + const resourceUrl = new URL(resourceFullDomain); + const domain = resourceUrl.hostname; + + const allDomainParts = domain.split("."); + let sub = undefined; + let base = domain; + + if (allDomainParts.length >= 3) { + // 3 parts: [subdomain, domain, tld] + const [first, ...rest] = allDomainParts; + sub = first; + base = rest.join("."); + } + + return [sub, base]; + }, [resourceFullDomain]); + const [selectedDomain, setSelectedDomain] = useState<{ domainId: string; subdomain?: string; @@ -105,7 +104,6 @@ export default function GeneralForm() { niceId: z.string().min(1).max(255).optional(), domainId: z.string().optional(), proxyPort: z.int().min(1).max(65535).optional() - // enableProxy: z.boolean().optional() }) .refine( (data) => { @@ -124,8 +122,6 @@ export default function GeneralForm() { } ); - type GeneralFormValues = z.infer; - const form = useForm({ resolver: zodResolver(GeneralFormSchema), defaultValues: { @@ -135,58 +131,17 @@ export default function GeneralForm() { subdomain: resource.subdomain ? resource.subdomain : undefined, domainId: resource.domainId || undefined, proxyPort: resource.proxyPort || undefined - // enableProxy: resource.enableProxy || false }, mode: "onChange" }); - useEffect(() => { - const fetchSites = async () => { - const res = await api.get>( - `/org/${orgId}/sites/` - ); - setSites(res.data.data.sites); - }; + const [, formAction, saveLoading] = useActionState(onSubmit, null); - const fetchDomains = async () => { - const res = await api - .get< - AxiosResponse - >(`/org/${orgId}/domains/`) - .catch((e) => { - toast({ - variant: "destructive", - title: t("domainErrorFetch"), - description: formatAxiosError( - e, - t("domainErrorFetchDescription") - ) - }); - }); + async function onSubmit() { + const isValid = await form.trigger(); + if (!isValid) return; - if (res?.status === 200) { - const rawDomains = res.data.data.domains as DomainRow[]; - const domains = rawDomains.map((domain) => ({ - ...domain, - baseDomain: toUnicode(domain.baseDomain) - })); - setBaseDomains(domains); - setFormKey((key) => key + 1); - } - }; - - const load = async () => { - await fetchDomains(); - await fetchSites(); - - setLoadingPage(false); - }; - - load(); - }, []); - - async function onSubmit(data: GeneralFormValues) { - setSaveLoading(true); + const data = form.getValues(); const res = await api .post>( @@ -200,9 +155,6 @@ export default function GeneralForm() { : undefined, domainId: data.domainId, proxyPort: data.proxyPort - // ...(!resource.http && { - // enableProxy: data.enableProxy - // }) } ) .catch((e) => { @@ -226,9 +178,6 @@ export default function GeneralForm() { subdomain: data.subdomain, fullDomain: resource.fullDomain, proxyPort: data.proxyPort - // ...(!resource.http && { - // enableProxy: data.enableProxy - // }) }); toast({ @@ -240,306 +189,257 @@ export default function GeneralForm() { router.replace( `/${updated.orgId}/settings/resources/proxy/${data.niceId}/general` ); - } else { - router.refresh(); } - setSaveLoading(false); + router.refresh(); } - - setSaveLoading(false); } return ( - !loadingPage && ( - <> - - - - - {t("resourceGeneral")} - - - {t("resourceGeneralDescription")} - - + <> + + + + + {t("resourceGeneral")} + + + {t("resourceGeneralDescription")} + + - - -
- - ( - -
- - + + + + ( + +
+ + + form.setValue( + "enabled", val - ) => - form.setValue( - "enabled", - val + ) + } + /> + +
+ +
+ )} + /> + + ( + + + {t("name")} + + + + + + + )} + /> + + ( + + + {t("identifier")} + + + + + + + )} + /> + + {!resource.http && ( + <> + ( + + + {t( + "resourcePortNumber" + )} + + + + field.onChange( + e.target + .value + ? parseInt( + e + .target + .value + ) + : undefined ) } /> -
- -
- )} - /> - - ( - - - {t("name")} - - - - - - - )} - /> - - ( - - - {t("identifier")} - - - + + {t( + "resourcePortNumberDescription" )} - className="flex-1" - /> - - - - )} - /> + + + )} + /> + + )} - {!resource.http && ( - <> - ( - - - {t( - "resourcePortNumber" - )} - - - - field.onChange( - e - .target - .value - ? parseInt( - e - .target - .value - ) - : undefined - ) - } - /> - - - - {t( - "resourcePortNumberDescription" - )} - - - )} - /> - - {/* {build == "oss" && ( - ( - - - - -
- - {t( - "resourceEnableProxy" - )} - - - {t( - "resourceEnableProxyDescription" - )} - -
-
- )} - /> - )} */} - - )} - - {resource.http && ( -
- -
- - - {resourceFullDomain} - - -
+ {resource.http && ( +
+ +
+ + + {resourceFullDomain} + +
- )} - - - - +
+ )} + + + + - - - - - + + + + + - setEditDomainOpen(setOpen)} - > - - - Edit Domain - - Select a domain for your resource - - - - { - const selected = { - domainId: res.domainId, - subdomain: res.subdomain, - fullDomain: res.fullDomain, - baseDomain: res.baseDomain - }; - setSelectedDomain(selected); - }} - /> - - - - - - + + - - - - - ) + setEditDomainOpen(false); + } + }} + > + Select Domain + + + + + ); } diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/layout.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/layout.tsx index c453b577..f410b4c8 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/layout.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/layout.tsx @@ -13,9 +13,10 @@ import { GetOrgResponse } from "@server/routers/org"; import OrgProvider from "@app/providers/OrgProvider"; import { cache } from "react"; import ResourceInfoBox from "@app/components/ResourceInfoBox"; -import { GetSiteResponse } from "@server/routers/site"; import { getTranslations } from "next-intl/server"; +export const dynamic = "force-dynamic"; + interface ResourceLayoutProps { children: React.ReactNode; params: Promise<{ niceId: string; orgId: string }>;