From d6a80216137ffab2546a21867aaef071cc82d7ad Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 27 Feb 2026 04:21:20 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20wip:=20update=20resource=20polic?= =?UTF-8?q?y=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- messages/en-US.json | 4 + server/routers/external.ts | 7 + server/routers/policy/index.ts | 1 + .../routers/policy/updateResourcePolicy.ts | 9 +- .../policies/resource/[niceId]/page.tsx | 14 +- .../resource-policy/CreatePolicyForm.tsx | 10 +- .../resource-policy/EditPolicyForm.tsx | 313 +++++++++++------- src/providers/ResourcePolicyProvider.tsx | 63 ++++ 8 files changed, 272 insertions(+), 149 deletions(-) rename server/{private => }/routers/policy/updateResourcePolicy.ts (97%) create mode 100644 src/providers/ResourcePolicyProvider.tsx diff --git a/messages/en-US.json b/messages/en-US.json index 78b4ad0ba..3a0c77462 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -642,7 +642,11 @@ "policyErrorCreate": "Error creating policy", "policyErrorCreateDescription": "An error occurred when creating the policy", "policyErrorCreateMessageDescription": "An unexpected error occurred", + "policyErrorUpdate": "Error updating policy", + "policyErrorUpdateDescription": "An error occurred when updating the policy", + "policyErrorUpdateMessageDescription": "An unexpected error occurred", "policyCreatedSuccess": "Resource policy succesfully created", + "policyUpdatedSuccess": "Resource policy succesfully updated", "resourceErrorCreate": "Error creating resource", "resourceErrorCreateDescription": "An error occurred when creating the resource", "resourceErrorCreateMessage": "Error creating resource:", diff --git a/server/routers/external.ts b/server/routers/external.ts index 67494c643..519d2b4a9 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -638,6 +638,13 @@ authenticated.get( policy.getResourcePolicy ); +authenticated.put( + "/resource-policy/:resourcePolicyId", + verifyResourcePolicyAccess, + verifyUserHasAction(ActionsEnum.updateResourcePolicy), + policy.updateResourcePolicy +); + // authenticated.get( // "/role/:roleId", // verifyRoleAccess, diff --git a/server/routers/policy/index.ts b/server/routers/policy/index.ts index a05c292ee..8cd264925 100644 --- a/server/routers/policy/index.ts +++ b/server/routers/policy/index.ts @@ -1 +1,2 @@ export * from "./getResourcePolicy"; +export * from "./updateResourcePolicy"; diff --git a/server/private/routers/policy/updateResourcePolicy.ts b/server/routers/policy/updateResourcePolicy.ts similarity index 97% rename from server/private/routers/policy/updateResourcePolicy.ts rename to server/routers/policy/updateResourcePolicy.ts index 1f4ff5971..77443e1a2 100644 --- a/server/private/routers/policy/updateResourcePolicy.ts +++ b/server/routers/policy/updateResourcePolicy.ts @@ -4,14 +4,7 @@ import { OpenAPITags, registry } from "@server/openApi"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; import { fromError } from "zod-validation-error"; -import { - db, - orgs, - resourcePolicies, - rolePolicies, - userPolicies, - type ResourcePolicy -} from "@server/db"; +import { db, orgs, resourcePolicies, type ResourcePolicy } from "@server/db"; import { and, eq } from "drizzle-orm"; import logger from "@server/logger"; import response from "@server/lib/response"; diff --git a/src/app/[orgId]/settings/(private)/policies/resource/[niceId]/page.tsx b/src/app/[orgId]/settings/(private)/policies/resource/[niceId]/page.tsx index 61833a1f1..9c18c9b96 100644 --- a/src/app/[orgId]/settings/(private)/policies/resource/[niceId]/page.tsx +++ b/src/app/[orgId]/settings/(private)/policies/resource/[niceId]/page.tsx @@ -3,7 +3,7 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { Button } from "@app/components/ui/button"; import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; -import type { ResourcePolicy } from "@server/db"; +import { ResourcePolicyProvider } from "@app/providers/ResourcePolicyProvider"; import type { GetResourcePolicyResponse } from "@server/routers/policy"; import type { AxiosResponse } from "axios"; import { getTranslations } from "next-intl/server"; @@ -18,7 +18,7 @@ export default async function EditPolicyPage(props: EditPolicyPageProps) { const params = await props.params; const t = await getTranslations(); - let policy: ResourcePolicy | null = null; + let policyResponse: GetResourcePolicyResponse | null = null; try { const res = await internal.get< AxiosResponse @@ -26,12 +26,12 @@ export default async function EditPolicyPage(props: EditPolicyPageProps) { `/org/${params.orgId}/resource-policy/${params.niceId}`, await authCookieHeader() ); - policy = res.data.data.policy; + policyResponse = res.data.data; } catch { redirect(`/${params.orgId}/settings/policies/resource`); } - if (!policy) { + if (!policyResponse) { redirect(`/${params.orgId}/settings/policies/resource`); } @@ -40,7 +40,7 @@ export default async function EditPolicyPage(props: EditPolicyPageProps) {
@@ -52,7 +52,9 @@ export default async function EditPolicyPage(props: EditPolicyPageProps) {
- + + + ); } diff --git a/src/components/resource-policy/CreatePolicyForm.tsx b/src/components/resource-policy/CreatePolicyForm.tsx index 10982c0cc..d805e8772 100644 --- a/src/components/resource-policy/CreatePolicyForm.tsx +++ b/src/components/resource-policy/CreatePolicyForm.tsx @@ -204,14 +204,10 @@ export function CreatePolicyForm({}: CreatePolicyFormProps) { }); if (res && res.status === 201) { - const id = res.data.data.resourcePolicyId; const niceId = res.data.data.niceId; - - router.push(`/${org.org.orgId}/settings/policies/resources/`); - // should redirect to the details page - // router.push( - // `/${org.org.orgId}/settings/policies/resources/${niceId}` - // ); + router.push( + `/${org.org.orgId}/settings/policies/resource/${niceId}` + ); toast({ title: t("success"), description: t("policyCreatedSuccess") diff --git a/src/components/resource-policy/EditPolicyForm.tsx b/src/components/resource-policy/EditPolicyForm.tsx index 4331fb286..5477e8704 100644 --- a/src/components/resource-policy/EditPolicyForm.tsx +++ b/src/components/resource-policy/EditPolicyForm.tsx @@ -121,23 +121,21 @@ import { import { useCallback, useMemo, useState, useActionState } from "react"; import { UseFormReturn, useForm, useWatch } from "react-hook-form"; +import router from "next/navigation"; +import { useResourcePolicyContext } from "@app/providers/ResourcePolicyProvider"; // ─── EditPolicyForm ───────────────────────────────────────────────────────── export type EditPolicyFormProps = { - policy: ResourcePolicy; hidePolicyNameForm?: boolean; }; -export function EditPolicyForm({ - hidePolicyNameForm, - policy -}: EditPolicyFormProps) { +export function EditPolicyForm({ hidePolicyNameForm }: EditPolicyFormProps) { const { org } = useOrgContext(); const t = useTranslations(); const { env } = useEnvContext(); const api = createApiClient({ env }); - const [, formAction, isSubmitting] = useActionState(onSubmit, null); + // const [, formAction, isSubmitting] = useActionState(onSubmit, null); const { isPaidUser } = usePaidStatus(); const router = useRouter(); @@ -145,7 +143,7 @@ export function EditPolicyForm({ const isMaxmindAvailable = !!( env.server.maxmind_db_path && env.server.maxmind_db_path.length > 0 ); - const isMaxmindAsnAvailable = !!( + const isMaxmindASNAvailable = !!( env.server.maxmind_asn_path && env.server.maxmind_asn_path.length > 0 ); @@ -162,75 +160,76 @@ export function EditPolicyForm({ }) ); - const form = useForm({ - resolver: zodResolver(createPolicySchema) as any, - defaultValues: { - name: policy.name, - sso: true, - skipToIdpId: null, - emailWhitelistEnabled: false, - roles: [], - users: [], - emails: [], - applyRules: false, - rules: [], - password: null, - headerAuth: null, - pincode: null - } - }); + // const form = useForm({ + // resolver: zodResolver(createPolicySchema) as any, + // defaultValues: { + // name: "", + // sso: true, + // skipToIdpId: null, + // emailWhitelistEnabled: false, + // roles: [], + // users: [], + // emails: [], + // applyRules: false, + // rules: [], + // password: null, + // headerAuth: null, + // pincode: null + // } + // }); - async function onSubmit() { - const isValid = await form.trigger(); + // async function onSubmit() { + // return; + // // const isValid = await form.trigger(); - if (!isValid) return; + // // if (!isValid) return; - const payload = form.getValues(); + // // const payload = form.getValues(); - try { - const res = await api - .post>( - `/org/${org.org.orgId}/resource-policy/`, - { - name: payload.name, - sso: payload.sso, - roleIds: payload.roles.map((r) => r.id), - userIds: payload.users.map((u) => u.id) - } - ) - .catch((e) => { - toast({ - variant: "destructive", - title: t("policyErrorCreate"), - description: formatAxiosError( - e, - t("policyErrorCreateDescription") - ) - }); - }); + // // try { + // // const res = await api + // // .post>( + // // `/org/${org.org.orgId}/resource-policy/`, + // // { + // // name: payload.name, + // // sso: payload.sso, + // // roleIds: payload.roles.map((r) => r.id), + // // userIds: payload.users.map((u) => u.id) + // // } + // // ) + // // .catch((e) => { + // // toast({ + // // variant: "destructive", + // // title: t("policyErrorCreate"), + // // description: formatAxiosError( + // // e, + // // t("policyErrorCreateDescription") + // // ) + // // }); + // // }); - if (res && res.status === 201) { - const id = res.data.data.resourcePolicyId; - const niceId = res.data.data.niceId; + // // if (res && res.status === 201) { + // // const id = res.data.data.resourcePolicyId; + // // const niceId = res.data.data.niceId; - router.push(`/${org.org.orgId}/settings/policies/resources/`); - // should redirect to the details page - // router.push( - // `/${org.org.orgId}/settings/policies/resources/${niceId}` - // ); - toast({ - title: t("success"), - description: t("policyCreatedSuccess") - }); - } - } catch (e) { - toast({ - variant: "destructive", - title: t("policyErrorCreate"), - description: t("policyErrorCreateMessageDescription") - }); - } - } + // // router.push(`/${org.org.orgId}/settings/policies/resources/`); + // // // should redirect to the details page + // // // router.push( + // // // `/${org.org.orgId}/settings/policies/resources/${niceId}` + // // // ); + // // toast({ + // // title: t("success"), + // // description: t("policyCreatedSuccess") + // // }); + // // } + // // } catch (e) { + // // toast({ + // // variant: "destructive", + // // title: t("policyErrorCreate"), + // // description: t("policyErrorCreateMessageDescription") + // // }); + // // } + // } const allRoles = useMemo( () => @@ -271,12 +270,12 @@ export function EditPolicyForm({ } return ( -
- - - {/* Name */} - {!hidePolicyNameForm && } - + // + + {/* Name */} + {!hidePolicyNameForm && } + {/* - - - + isMaxmindAsnAvailable={isMaxmindASNAvailable} + /> */} +
+ // + // ); } // ─── PolicyNameSection ────────────────────────────────────────────────── -type PolicyNameSectionProps = { - form: UseFormReturn; - isEditing?: boolean; -}; -export function PolicyNameSection({ form }: PolicyNameSectionProps) { +export function PolicyNameSection() { const t = useTranslations(); - return ( - - - - {t("resourcePolicyName")} - - - {t("resourcePolicyNameDescription")} - - - - - ( - - {t("name")} - - - - - - )} - /> - - + const api = createApiClient(useEnvContext()); -
- -
-
+ const { policy } = useResourcePolicyContext(); + const { org } = useOrgContext(); + const form = useForm({ + resolver: zodResolver( + z.object({ + name: z.string() + }) + ), + defaultValues: { + name: policy.name + } + }); + + const [, formAction, isSubmitting] = useActionState(onSubmit, null); + + async function onSubmit() { + const isValid = await form.trigger(); + + if (!isValid) return; + + const payload = form.getValues(); + + try { + const res = await api + .put>( + `/resource-policy/${policy.resourcePolicyId}`, + { + name: payload.name + } + ) + .catch((e) => { + toast({ + variant: "destructive", + title: t("policyErrorUpdate"), + description: formatAxiosError( + e, + t("policyErrorUpdateDescription") + ) + }); + }); + + if (res && res.status === 200) { + toast({ + title: t("success"), + description: t("policyUpdatedSuccess") + }); + } + } catch (e) { + toast({ + variant: "destructive", + title: t("policyErrorUpdate"), + description: t("policyErrorUpdateMessageDescription") + }); + } + } + + return ( +
+ + + + + {t("resourcePolicyName")} + + + {t("resourcePolicyNameDescription")} + + + + + ( + + {t("name")} + + + + + + )} + /> + + + +
+ +
+
+
+ ); } diff --git a/src/providers/ResourcePolicyProvider.tsx b/src/providers/ResourcePolicyProvider.tsx new file mode 100644 index 000000000..c6300304b --- /dev/null +++ b/src/providers/ResourcePolicyProvider.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { createContext, useContext, useState } from "react"; +import { useTranslations } from "next-intl"; +import type { GetResourcePolicyResponse } from "@server/routers/policy"; + +interface ResourcePolicyProviderProps { + children: React.ReactNode; + policy: GetResourcePolicyResponse; +} + +export function ResourcePolicyProvider({ + children, + policy: serverPolicy +}: ResourcePolicyProviderProps) { + const [policy, setPolicy] = + useState(serverPolicy); + + const t = useTranslations(); + + const updatePolicy = ( + updatedPolicy: Partial + ) => { + if (!policy) { + throw new Error(t("resourceErrorNoUpdate")); + } + + setPolicy((prev) => { + if (!prev) { + return prev; + } + + return { + ...prev, + ...updatedPolicy + }; + }); + }; + + return ( + + {children} + + ); +} + +export type ResourcePolicyContextType = GetResourcePolicyResponse & { + updatePolicy: (updatedPolicy: Partial) => void; +}; + +export const ResourcePolicyContext = createContext< + ResourcePolicyContextType | undefined +>(undefined); + +export function useResourcePolicyContext() { + const context = useContext(ResourcePolicyContext); + if (context === undefined) { + throw new Error( + "useResourcePolicyContext must be used within a ResourcePolicyProvider" + ); + } + return context; +}