♻️ separate create form into multiple ones

This commit is contained in:
Fred KISSIE
2026-03-05 19:45:13 +01:00
parent c5fc49b4fa
commit 642999c8b1
5 changed files with 392 additions and 276 deletions

View File

@@ -14,7 +14,7 @@ import { useTranslations } from "next-intl";
import z from "zod"; import z from "zod";
import { type PolicyFormValues } from "."; import { createPolicySchema, type PolicyFormValues } from ".";
import { SwitchInput } from "@app/components/SwitchInput"; import { SwitchInput } from "@app/components/SwitchInput";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
@@ -46,7 +46,7 @@ import {
import { cn } from "@app/lib/cn"; import { cn } from "@app/lib/cn";
import { Binary, Bot, Key, Plus } from "lucide-react"; import { Binary, Bot, Key, Plus } from "lucide-react";
import { useState } from "react"; import { useEffect, useState } from "react";
import { type UseFormReturn, useForm, useWatch } from "react-hook-form"; import { type UseFormReturn, useForm, useWatch } from "react-hook-form";
// ─── CreatePolicyAuthMethodsSectionForm ─────────────────────────────────────── // ─── CreatePolicyAuthMethodsSectionForm ───────────────────────────────────────
@@ -70,7 +70,7 @@ export type CreatePolicyAuthMethodsSectionFormProps = {
}; };
export function CreatePolicyAuthMethodsSectionForm({ export function CreatePolicyAuthMethodsSectionForm({
form form: parentForm
}: CreatePolicyAuthMethodsSectionFormProps) { }: CreatePolicyAuthMethodsSectionFormProps) {
const t = useTranslations(); const t = useTranslations();
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
@@ -78,6 +78,30 @@ export function CreatePolicyAuthMethodsSectionForm({
const [isSetPincodeOpen, setIsSetPincodeOpen] = useState(false); const [isSetPincodeOpen, setIsSetPincodeOpen] = useState(false);
const [isSetHeaderAuthOpen, setIsSetHeaderAuthOpen] = useState(false); const [isSetHeaderAuthOpen, setIsSetHeaderAuthOpen] = useState(false);
const form = useForm({
resolver: zodResolver(
createPolicySchema.pick({
password: true,
pincode: true,
headerAuth: true
})
),
defaultValues: {
password: null,
pincode: null,
headerAuth: null
}
});
useEffect(() => {
const subscription = form.watch((values) => {
parentForm.setValue("password", values.password as any);
parentForm.setValue("pincode", values.pincode as any);
parentForm.setValue("headerAuth", values.headerAuth as any);
});
return () => subscription.unsubscribe();
}, [form, parentForm]);
const password = useWatch({ const password = useWatch({
control: form.control, control: form.control,
name: "password" name: "password"

View File

@@ -43,7 +43,7 @@ import {
} from "@app/components/ui/form"; } from "@app/components/ui/form";
import { Input } from "@app/components/ui/input"; import { Input } from "@app/components/ui/input";
import { useMemo, useActionState } from "react"; import { useMemo, useTransition } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { CreatePolicyUsersRolesSectionForm } from "./CreatePolicyUserRolesSectionForm"; import { CreatePolicyUsersRolesSectionForm } from "./CreatePolicyUserRolesSectionForm";
import { CreatePolicyAuthMethodsSectionForm } from "./CreatePolicyAuthMethodsSectionForm"; import { CreatePolicyAuthMethodsSectionForm } from "./CreatePolicyAuthMethodsSectionForm";
@@ -59,7 +59,7 @@ export function CreatePolicyForm({}: CreatePolicyFormProps) {
const t = useTranslations(); const t = useTranslations();
const { env } = useEnvContext(); const { env } = useEnvContext();
const api = createApiClient({ env }); const api = createApiClient({ env });
const [, formAction, isSubmitting] = useActionState(onSubmit, null); const [isSubmitting, startTransition] = useTransition();
const { isPaidUser } = usePaidStatus(); const { isPaidUser } = usePaidStatus();
const router = useRouter(); const router = useRouter();
@@ -202,7 +202,6 @@ export function CreatePolicyForm({}: CreatePolicyFormProps) {
return ( return (
<Form {...form}> <Form {...form}>
<form action={formAction}>
<SettingsContainer> <SettingsContainer>
{/* Name */} {/* Name */}
<SettingsSection> <SettingsSection>
@@ -258,14 +257,14 @@ export function CreatePolicyForm({}: CreatePolicyFormProps) {
<div className="flex py-6 justify-end"> <div className="flex py-6 justify-end">
<Button <Button
type="submit" type="button"
onClick={() => startTransition(onSubmit)}
loading={isSubmitting} loading={isSubmitting}
disabled={isSubmitting} disabled={isSubmitting}
> >
{t("resourcePoliciesCreate")} {t("resourcePoliciesCreate")}
</Button> </Button>
</div> </div>
</form>
</Form> </Form>
); );
} }

View File

@@ -9,30 +9,31 @@ import {
SettingsSectionTitle SettingsSectionTitle
} from "@app/components/Settings"; } from "@app/components/Settings";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import z from "zod"; import z from "zod";
import { type PolicyFormValues } from "."; import { createPolicySchema, type PolicyFormValues } from ".";
import { SwitchInput } from "@app/components/SwitchInput"; import { SwitchInput } from "@app/components/SwitchInput";
import { Tag, TagInput } from "@app/components/tags/tag-input"; import { Tag, TagInput } from "@app/components/tags/tag-input";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
import { import {
Form,
FormControl, FormControl,
FormDescription, FormDescription,
FormField, FormField,
FormItem, FormItem,
FormLabel, FormLabel
FormMessage
} from "@app/components/ui/form"; } from "@app/components/ui/form";
import { InfoPopup } from "@app/components/ui/info-popup"; import { InfoPopup } from "@app/components/ui/info-popup";
import { InfoIcon, Plus } from "lucide-react"; import { InfoIcon, Plus } from "lucide-react";
import { useState } from "react"; import { useEffect, useState } from "react";
import { type UseFormReturn } from "react-hook-form"; import { type UseFormReturn, useForm, useWatch } from "react-hook-form";
// ─── CreatePolicyOtpEmailSectionForm ────────────────────────────────────────── // ─── CreatePolicyOtpEmailSectionForm ──────────────────────────────────────────
@@ -42,16 +43,44 @@ export type CreatePolicyOtpEmailSectionFormProps = {
}; };
export function CreatePolicyOtpEmailSectionForm({ export function CreatePolicyOtpEmailSectionForm({
form, form: parentForm,
emailEnabled emailEnabled
}: CreatePolicyOtpEmailSectionFormProps) { }: CreatePolicyOtpEmailSectionFormProps) {
const t = useTranslations(); const t = useTranslations();
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const [whitelistEnabled, setWhitelistEnabled] = useState(false);
const [activeEmailTagIndex, setActiveEmailTagIndex] = useState< const [activeEmailTagIndex, setActiveEmailTagIndex] = useState<
number | null number | null
>(null); >(null);
const form = useForm({
resolver: zodResolver(
createPolicySchema.pick({
emailWhitelistEnabled: true,
emails: true
})
),
defaultValues: {
emailWhitelistEnabled: false,
emails: []
}
});
useEffect(() => {
const subscription = form.watch((values) => {
parentForm.setValue(
"emailWhitelistEnabled",
values.emailWhitelistEnabled as boolean
);
parentForm.setValue("emails", values.emails as [Tag, ...Tag[]]);
});
return () => subscription.unsubscribe();
}, [form, parentForm]);
const whitelistEnabled = useWatch({
control: form.control,
name: "emailWhitelistEnabled"
});
if (!isExpanded) { if (!isExpanded) {
return ( return (
<SettingsSection> <SettingsSection>
@@ -78,6 +107,7 @@ export function CreatePolicyOtpEmailSectionForm({
} }
return ( return (
<Form {...form}>
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
<SettingsSectionTitle> <SettingsSectionTitle>
@@ -105,7 +135,6 @@ export function CreatePolicyOtpEmailSectionForm({
label={t("otpEmailWhitelist")} label={t("otpEmailWhitelist")}
defaultChecked={false} defaultChecked={false}
onCheckedChange={(val) => { onCheckedChange={(val) => {
setWhitelistEnabled(val);
form.setValue("emailWhitelistEnabled", val); form.setValue("emailWhitelistEnabled", val);
}} }}
disabled={!emailEnabled} disabled={!emailEnabled}
@@ -129,7 +158,9 @@ export function CreatePolicyOtpEmailSectionForm({
{/* @ts-ignore */} {/* @ts-ignore */}
<TagInput <TagInput
{...field} {...field}
activeTagIndex={activeEmailTagIndex} activeTagIndex={
activeEmailTagIndex
}
size="sm" size="sm"
validateTag={(tag) => { validateTag={(tag) => {
return z return z
@@ -140,7 +171,8 @@ export function CreatePolicyOtpEmailSectionForm({
.regex( .regex(
/^\*@[\w.-]+\.[a-zA-Z]{2,}$/, /^\*@[\w.-]+\.[a-zA-Z]{2,}$/,
{ {
message: t( message:
t(
"otpEmailErrorInvalid" "otpEmailErrorInvalid"
) )
} }
@@ -156,7 +188,10 @@ export function CreatePolicyOtpEmailSectionForm({
setTags={(newEmails) => { setTags={(newEmails) => {
form.setValue( form.setValue(
"emails", "emails",
newEmails as [Tag, ...Tag[]] newEmails as [
Tag,
...Tag[]
]
); );
}} }}
allowDuplicates={false} allowDuplicates={false}
@@ -173,5 +208,6 @@ export function CreatePolicyOtpEmailSectionForm({
</SettingsSectionForm> </SettingsSectionForm>
</SettingsSectionBody> </SettingsSectionBody>
</SettingsSection> </SettingsSection>
</Form>
); );
} }

View File

@@ -13,7 +13,7 @@ import { useTranslations } from "next-intl";
import z from "zod"; import z from "zod";
import { type PolicyFormValues } from "."; import { createPolicySchema, type PolicyFormValues } from ".";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { SwitchInput } from "@app/components/SwitchInput"; import { SwitchInput } from "@app/components/SwitchInput";
@@ -81,8 +81,8 @@ import {
Plus Plus
} from "lucide-react"; } from "lucide-react";
import { useCallback, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { type UseFormReturn, useForm } from "react-hook-form"; import { type UseFormReturn, useForm, useWatch } from "react-hook-form";
// ─── CreatePolicyRulesSectionForm ───────────────────────────────────────────── // ─── CreatePolicyRulesSectionForm ─────────────────────────────────────────────
@@ -111,18 +111,43 @@ export type CreatePolicyRulesSectionFormProps = {
}; };
export function CreatePolicyRulesSectionForm({ export function CreatePolicyRulesSectionForm({
form, form: parentForm,
isMaxmindAvailable, isMaxmindAvailable,
isMaxmindAsnAvailable isMaxmindAsnAvailable
}: CreatePolicyRulesSectionFormProps) { }: CreatePolicyRulesSectionFormProps) {
const t = useTranslations(); const t = useTranslations();
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const [rules, setRules] = useState<LocalRule[]>([]); const [rules, setRules] = useState<LocalRule[]>([]);
const [rulesEnabled, setRulesEnabled] = useState(false);
const [openAddRuleCountrySelect, setOpenAddRuleCountrySelect] = const [openAddRuleCountrySelect, setOpenAddRuleCountrySelect] =
useState(false); useState(false);
const [openAddRuleAsnSelect, setOpenAddRuleAsnSelect] = useState(false); const [openAddRuleAsnSelect, setOpenAddRuleAsnSelect] = useState(false);
const form = useForm({
resolver: zodResolver(
createPolicySchema.pick({
applyRules: true,
rules: true
})
),
defaultValues: {
applyRules: false,
rules: []
}
});
useEffect(() => {
const subscription = form.watch((values) => {
parentForm.setValue("applyRules", values.applyRules as boolean);
parentForm.setValue("rules", values.rules as any);
});
return () => subscription.unsubscribe();
}, [form, parentForm]);
const rulesEnabled = useWatch({
control: form.control,
name: "applyRules"
});
const addRuleForm = useForm({ const addRuleForm = useForm({
resolver: zodResolver(addRuleSchema), resolver: zodResolver(addRuleSchema),
defaultValues: { defaultValues: {
@@ -656,7 +681,6 @@ export function CreatePolicyRulesSectionForm({
label={t("rulesEnable")} label={t("rulesEnable")}
defaultChecked={false} defaultChecked={false}
onCheckedChange={(val) => { onCheckedChange={(val) => {
setRulesEnabled(val);
form.setValue("applyRules", val); form.setValue("applyRules", val);
}} }}
/> />

View File

@@ -9,9 +9,11 @@ import {
SettingsSectionTitle SettingsSectionTitle
} from "@app/components/Settings"; } from "@app/components/Settings";
import { zodResolver } from "@hookform/resolvers/zod";
import { SwitchInput } from "@app/components/SwitchInput"; import { SwitchInput } from "@app/components/SwitchInput";
import { Tag, TagInput } from "@app/components/tags/tag-input"; import { Tag, TagInput } from "@app/components/tags/tag-input";
import { import {
Form,
FormControl, FormControl,
FormDescription, FormDescription,
FormField, FormField,
@@ -26,10 +28,10 @@ import {
SelectTrigger, SelectTrigger,
SelectValue SelectValue
} from "@app/components/ui/select"; } from "@app/components/ui/select";
import { type PolicyFormValues } from "."; import { createPolicySchema, type PolicyFormValues } from ".";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useState } from "react"; import { useEffect, useState } from "react";
import { type UseFormReturn, useWatch } from "react-hook-form"; import { type UseFormReturn, useForm, useWatch } from "react-hook-form";
// ─── CreatePolicyUsersRolesSectionForm ──────────────────────────────────────── // ─── CreatePolicyUsersRolesSectionForm ────────────────────────────────────────
@@ -41,12 +43,40 @@ export type CreatePolicyUsersRolesSectionFormProps = {
}; };
export function CreatePolicyUsersRolesSectionForm({ export function CreatePolicyUsersRolesSectionForm({
form, form: parentForm,
allRoles, allRoles,
allUsers, allUsers,
allIdps allIdps
}: CreatePolicyUsersRolesSectionFormProps) { }: CreatePolicyUsersRolesSectionFormProps) {
const t = useTranslations(); const t = useTranslations();
const form = useForm({
resolver: zodResolver(
createPolicySchema.pick({
sso: true,
skipToIdpId: true,
roles: true,
users: true
})
),
defaultValues: {
sso: true,
skipToIdpId: null,
roles: [],
users: []
}
});
useEffect(() => {
const subscription = form.watch((values) => {
parentForm.setValue("sso", values.sso as boolean);
parentForm.setValue("skipToIdpId", values.skipToIdpId as number | null);
parentForm.setValue("roles", values.roles as [Tag, ...Tag[]]);
parentForm.setValue("users", values.users as [Tag, ...Tag[]]);
});
return () => subscription.unsubscribe();
}, [form, parentForm]);
const ssoEnabled = useWatch({ control: form.control, name: "sso" }); const ssoEnabled = useWatch({ control: form.control, name: "sso" });
const selectedIdpId = useWatch({ const selectedIdpId = useWatch({
control: form.control, control: form.control,
@@ -60,6 +90,7 @@ export function CreatePolicyUsersRolesSectionForm({
>(null); >(null);
return ( return (
<Form {...form}>
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
<SettingsSectionTitle> <SettingsSectionTitle>
@@ -76,7 +107,6 @@ export function CreatePolicyUsersRolesSectionForm({
label={t("ssoUse")} label={t("ssoUse")}
defaultChecked={ssoEnabled} defaultChecked={ssoEnabled}
onCheckedChange={(val) => { onCheckedChange={(val) => {
console.log(`form.setValue("sso", ${val})`);
form.setValue("sso", val); form.setValue("sso", val);
}} }}
/> />
@@ -195,7 +225,9 @@ export function CreatePolicyUsersRolesSectionForm({
> >
<SelectTrigger className="w-full mt-1"> <SelectTrigger className="w-full mt-1">
<SelectValue <SelectValue
placeholder={t("selectIdpPlaceholder")} placeholder={t(
"selectIdpPlaceholder"
)}
/> />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -220,5 +252,6 @@ export function CreatePolicyUsersRolesSectionForm({
</SettingsSectionForm> </SettingsSectionForm>
</SettingsSectionBody> </SettingsSectionBody>
</SettingsSection> </SettingsSection>
</Form>
); );
} }