fix form responsiveness

This commit is contained in:
miloschwartz
2026-06-09 16:52:05 -07:00
parent 96a54fc9cc
commit bdb38db5bc
11 changed files with 672 additions and 740 deletions

View File

@@ -17,7 +17,8 @@ import {
SettingsSectionTitle, SettingsSectionTitle,
SettingsSectionDescription, SettingsSectionDescription,
SettingsSectionBody, SettingsSectionBody,
SettingsSectionFooter SettingsSectionFooter,
SettingsSectionForm
} from "@app/components/Settings"; } from "@app/components/Settings";
import { import {
InfoSection, InfoSection,
@@ -1326,44 +1327,46 @@ export default function BillingPage() {
</SettingsSectionDescription> </SettingsSectionDescription>
</SettingsSectionHeader> </SettingsSectionHeader>
<SettingsSectionBody> <SettingsSectionBody>
<SettingsFormGrid> <SettingsSectionForm variant="half">
<SettingsFormCell span="half"> <SettingsFormGrid>
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 border rounded-lg p-4"> <SettingsFormCell span="full">
<div> <div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 border rounded-lg p-4">
<div className="text-sm text-muted-foreground mb-1"> <div>
{t("billingCurrentKeys") || <div className="text-sm text-muted-foreground mb-1">
"Current Keys"} {t("billingCurrentKeys") ||
</div> "Current Keys"}
<div className="flex items-baseline gap-2"> </div>
<span className="text-3xl font-semibold"> <div className="flex items-baseline gap-2">
{getLicenseKeyCount()} <span className="text-3xl font-semibold">
</span> {getLicenseKeyCount()}
<span className="text-lg"> </span>
{getLicenseKeyCount() === 1 <span className="text-lg">
? "key" {getLicenseKeyCount() === 1
: "keys"} ? "key"
</span> : "keys"}
</span>
</div>
</div> </div>
<Button
variant="outline"
onClick={handleModifySubscription}
disabled={isLoading}
loading={isLoading}
>
<CreditCard className="mr-2 h-4 w-4" />
{t("billingModifyCurrentPlan") ||
"Modify Current Plan"}
</Button>
<p className="text-sm text-muted-foreground mt-2">
{t(
"billingManageLicenseSubscriptionDescription"
) ||
"Manage your subscription for paid self-hosted license keys and download invoices."}
</p>
</div> </div>
<Button </SettingsFormCell>
variant="outline" </SettingsFormGrid>
onClick={handleModifySubscription} </SettingsSectionForm>
disabled={isLoading}
loading={isLoading}
>
<CreditCard className="mr-2 h-4 w-4" />
{t("billingModifyCurrentPlan") ||
"Modify Current Plan"}
</Button>
<p className="text-sm text-muted-foreground mt-2">
{t(
"billingManageLicenseSubscriptionDescription"
) ||
"Manage your subscription for paid self-hosted license keys and download invoices."}
</p>
</div>
</SettingsFormCell>
</SettingsFormGrid>
</SettingsSectionBody> </SettingsSectionBody>
</SettingsSection> </SettingsSection>
)} )}

View File

@@ -14,6 +14,7 @@ import {
SettingsSection, SettingsSection,
SettingsSectionBody, SettingsSectionBody,
SettingsSectionDescription, SettingsSectionDescription,
SettingsSectionForm,
SettingsSectionHeader, SettingsSectionHeader,
SettingsSectionTitle SettingsSectionTitle
} from "@app/components/Settings"; } from "@app/components/Settings";
@@ -252,96 +253,102 @@ export default function Page() {
</SettingsSectionTitle> </SettingsSectionTitle>
</SettingsSectionHeader> </SettingsSectionHeader>
<SettingsSectionBody> <SettingsSectionBody>
<Form {...form}> <SettingsSectionForm variant="half">
<form <Form {...form}>
onKeyDown={(e) => { <form
if (e.key === "Enter") { onKeyDown={(e) => {
e.preventDefault(); // block default enter refresh if (e.key === "Enter") {
} e.preventDefault(); // block default enter refresh
}} }
id="create-client-form" }}
> id="create-client-form"
<SettingsFormGrid> >
<SettingsFormCell span="quarter"> <SettingsFormGrid>
<FormField <SettingsFormCell span="half">
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>
{t("name")}
</FormLabel>
<FormControl>
<Input
autoComplete="off"
{...field}
/>
</FormControl>
<FormMessage />
<FormDescription>
{t(
"clientNameDescription"
)}
</FormDescription>
</FormItem>
)}
/>
</SettingsFormCell>
<SettingsFormCell span="full">
<Button
type="button"
variant="ghost"
size="sm"
onClick={() =>
setShowAdvancedSettings(
!showAdvancedSettings
)
}
className="flex items-center gap-2 -ml-3"
>
{showAdvancedSettings ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
{t("advancedSettings")}
</Button>
</SettingsFormCell>
{showAdvancedSettings && (
<SettingsFormCell span="quarter">
<FormField <FormField
control={form.control} control={form.control}
name="subnet" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
{t( {t("name")}
"clientAddress"
)}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
autoComplete="off" autoComplete="off"
placeholder={t(
"subnetPlaceholder"
)}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
{t( {t(
"addressDescription" "clientNameDescription"
)} )}
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
/> />
</SettingsFormCell> </SettingsFormCell>
)} <SettingsFormCell span="full">
</SettingsFormGrid> <Button
</form> type="button"
</Form> variant="ghost"
size="sm"
onClick={() =>
setShowAdvancedSettings(
!showAdvancedSettings
)
}
className="flex items-center gap-2 -ml-3"
>
{showAdvancedSettings ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
{t("advancedSettings")}
</Button>
</SettingsFormCell>
{showAdvancedSettings && (
<SettingsFormCell span="half">
<FormField
control={
form.control
}
name="subnet"
render={({
field
}) => (
<FormItem>
<FormLabel>
{t(
"clientAddress"
)}
</FormLabel>
<FormControl>
<Input
autoComplete="off"
placeholder={t(
"subnetPlaceholder"
)}
{...field}
/>
</FormControl>
<FormMessage />
<FormDescription>
{t(
"addressDescription"
)}
</FormDescription>
</FormItem>
)}
/>
</SettingsFormCell>
)}
</SettingsFormGrid>
</form>
</Form>
</SettingsSectionForm>
</SettingsSectionBody> </SettingsSectionBody>
</SettingsSection> </SettingsSection>

View File

@@ -243,7 +243,7 @@ function ProxyResourceProtocolForm({
{proxySettingsForm.watch("proxyProtocol") && ( {proxySettingsForm.watch("proxyProtocol") && (
<> <>
<SettingsFormCell span="full"> <SettingsFormCell span="full">
<Alert className="[&>svg]:self-start"> <Alert className="[&>svg]:self-start" variant="neutral">
<AlertTriangle className="h-4 w-4" /> <AlertTriangle className="h-4 w-4" />
<AlertDescription> <AlertDescription>
<strong> <strong>

View File

@@ -1454,7 +1454,8 @@ export default function Page() {
</SettingsSectionDescription> </SettingsSectionDescription>
</SettingsSectionHeader> </SettingsSectionHeader>
<SettingsSectionBody> <SettingsSectionBody>
<SettingsFormGrid> <SettingsSectionForm variant="half">
<SettingsFormGrid>
<SettingsFormCell span="full"> <SettingsFormCell span="full">
<SettingsSubsectionHeader> <SettingsSubsectionHeader>
<SettingsSubsectionTitle> <SettingsSubsectionTitle>
@@ -1496,6 +1497,7 @@ export default function Page() {
/> />
</SettingsFormCell> </SettingsFormCell>
</SettingsFormGrid> </SettingsFormGrid>
</SettingsSectionForm>
</SettingsSectionBody> </SettingsSectionBody>
</SettingsSection> </SettingsSection>

View File

@@ -7,6 +7,7 @@ import {
SettingsSection, SettingsSection,
SettingsSectionBody, SettingsSectionBody,
SettingsSectionDescription, SettingsSectionDescription,
SettingsSectionForm,
SettingsSectionHeader, SettingsSectionHeader,
SettingsSectionTitle SettingsSectionTitle
} from "@app/components/Settings"; } from "@app/components/Settings";
@@ -507,121 +508,122 @@ export default function Page() {
/> />
</> </>
)} )}
<SettingsSectionForm variant="half">
<Form {...form}> <Form {...form}>
<form <form
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter") { if (e.key === "Enter") {
e.preventDefault(); // block default enter refresh e.preventDefault(); // block default enter refresh
} }
}} }}
id="create-site-form" id="create-site-form"
> >
<SettingsFormGrid> <SettingsFormGrid>
<SettingsFormCell span="quarter"> <SettingsFormCell span="half">
<FormField <FormField
control={form.control} control={form.control}
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
{t("name")} {t("name")}w
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
autoComplete="off" autoComplete="off"
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
{t( {t(
"siteNameDescription" "siteNameDescription"
)} )}
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
/> />
</SettingsFormCell> </SettingsFormCell>
{form.watch("method") === {form.watch("method") ===
"newt" && ( "newt" && (
<> <>
<SettingsFormCell span="full"> <SettingsFormCell span="full">
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => onClick={() =>
setShowAdvancedSettings( setShowAdvancedSettings(
!showAdvancedSettings !showAdvancedSettings
) )
}
className="mt-2 flex items-center gap-2 -ml-3"
>
{showAdvancedSettings ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
{t(
"advancedSettings"
)}
</Button>
</SettingsFormCell>
{showAdvancedSettings && (
<SettingsFormCell span="quarter">
<FormField
control={
form.control
} }
name="clientAddress" className="mt-2 flex items-center gap-2 -ml-3"
render={({ >
field {showAdvancedSettings ? (
}) => ( <ChevronUp className="h-4 w-4" />
<FormItem> ) : (
<FormLabel> <ChevronDown className="h-4 w-4" />
{t(
"siteAddress"
)}
</FormLabel>
<FormControl>
<Input
autoComplete="off"
value={
clientAddress
}
onChange={(
e
) => {
setClientAddress(
e
.target
.value
);
field.onChange(
e
.target
.value
);
}}
/>
</FormControl>
<FormMessage />
<FormDescription>
{t(
"siteAddressDescription"
)}
</FormDescription>
</FormItem>
)} )}
/> {t(
"advancedSettings"
)}
</Button>
</SettingsFormCell> </SettingsFormCell>
)} {showAdvancedSettings && (
</> <SettingsFormCell span="quarter">
)} <FormField
</SettingsFormGrid> control={
</form> form.control
</Form> }
name="clientAddress"
render={({
field
}) => (
<FormItem>
<FormLabel>
{t(
"siteAddress"
)}
</FormLabel>
<FormControl>
<Input
autoComplete="off"
value={
clientAddress
}
onChange={(
e
) => {
setClientAddress(
e
.target
.value
);
field.onChange(
e
.target
.value
);
}}
/>
</FormControl>
<FormMessage />
<FormDescription>
{t(
"siteAddressDescription"
)}
</FormDescription>
</FormItem>
)}
/>
</SettingsFormCell>
)}
</>
)}
</SettingsFormGrid>
</form>
</Form>
</SettingsSectionForm>
</SettingsSectionBody> </SettingsSectionBody>
</SettingsSection> </SettingsSection>

View File

@@ -7,6 +7,7 @@ import {
SettingsSection, SettingsSection,
SettingsSectionBody, SettingsSectionBody,
SettingsSectionDescription, SettingsSectionDescription,
SettingsSectionForm,
SettingsSectionHeader, SettingsSectionHeader,
SettingsSectionTitle SettingsSectionTitle
} from "@app/components/Settings"; } from "@app/components/Settings";
@@ -200,25 +201,27 @@ export function CreatePolicyForm({}: CreatePolicyFormProps) {
</SettingsSectionDescription> </SettingsSectionDescription>
</SettingsSectionHeader> </SettingsSectionHeader>
<SettingsSectionBody> <SettingsSectionBody>
<SettingsFormGrid> <SettingsSectionForm variant="half">
<SettingsFormCell span="quarter"> <SettingsFormGrid>
<FormField <SettingsFormCell span="half">
control={form.control} <FormField
name="name" control={form.control}
render={({ field }) => ( name="name"
<FormItem> render={({ field }) => (
<FormLabel> <FormItem>
{t("name")} <FormLabel>
</FormLabel> {t("name")}
<FormControl> </FormLabel>
<Input {...field} /> <FormControl>
</FormControl> <Input {...field} />
<FormMessage /> </FormControl>
</FormItem> <FormMessage />
)} </FormItem>
/> )}
</SettingsFormCell> />
</SettingsFormGrid> </SettingsFormCell>
</SettingsFormGrid>
</SettingsSectionForm>
</SettingsSectionBody> </SettingsSectionBody>
</SettingsSection> </SettingsSection>

View File

@@ -5,6 +5,7 @@ import {
SettingsSectionBody, SettingsSectionBody,
SettingsSectionDescription, SettingsSectionDescription,
SettingsSectionFooter, SettingsSectionFooter,
SettingsSectionForm,
SettingsSectionHeader, SettingsSectionHeader,
SettingsSectionTitle SettingsSectionTitle
} from "@app/components/Settings"; } from "@app/components/Settings";
@@ -148,10 +149,10 @@ function PolicyAccessRulesSectionLayout({
</SettingsSectionDescription> </SettingsSectionDescription>
</SettingsSectionHeader> </SettingsSectionHeader>
<SettingsSectionBody> <SettingsSectionBody>
{resourceOverlayMode && (
<SharedPolicyResourceNotice section="rules" />
)}
<div className="space-y-4"> <div className="space-y-4">
{resourceOverlayMode && (
<SharedPolicyResourceNotice section="rules" />
)}
<PolicyAccessRulesIntro <PolicyAccessRulesIntro
rulesEnabled={rulesEnabled} rulesEnabled={rulesEnabled}
onRulesEnabledChange={onRulesEnabledChange} onRulesEnabledChange={onRulesEnabledChange}

View File

@@ -0,0 +1,120 @@
"use client";
import {
SettingsFormCell,
SettingsFormGrid,
SettingsSubsectionDescription,
SettingsSubsectionHeader,
SettingsSubsectionTitle
} from "@app/components/Settings";
import { useTranslations } from "next-intl";
import { PolicyAuthMethodRow } from "./PolicyAuthMethodRow";
import type { PolicyAuthMethodId } from "./policy-auth-method-id";
import {
getEmailWhitelistSummary,
getHeaderAuthSummary,
getPasscodeSummary,
getPincodeSummary
} from "./policy-auth-summaries";
export type PolicyAuthOtherMethodsSectionProps = {
pinActive: boolean;
passcodeActive: boolean;
emailWhitelistEnabled: boolean;
headerAuthActive: boolean;
headerAuthUser: string;
emailCount: number;
emailEnabled: boolean;
disabled?: boolean;
onConfigure: (method: PolicyAuthMethodId) => void;
onTogglePincode: (active: boolean) => void;
onTogglePasscode: (active: boolean) => void;
onToggleEmail: (active: boolean) => void;
onToggleHeaderAuth: (active: boolean) => void;
};
export function PolicyAuthOtherMethodsSection({
pinActive,
passcodeActive,
emailWhitelistEnabled,
headerAuthActive,
headerAuthUser,
emailCount,
emailEnabled,
disabled,
onConfigure,
onTogglePincode,
onTogglePasscode,
onToggleEmail,
onToggleHeaderAuth
}: PolicyAuthOtherMethodsSectionProps) {
const t = useTranslations();
return (
<SettingsFormGrid>
<SettingsFormCell span="full">
<SettingsSubsectionHeader>
<SettingsSubsectionTitle>
{t("policyAuthOtherMethodsTitle")}
</SettingsSubsectionTitle>
<SettingsSubsectionDescription>
{t("policyAuthOtherMethodsDescription")}
</SettingsSubsectionDescription>
</SettingsSubsectionHeader>
</SettingsFormCell>
<SettingsFormCell span="full">
<div className="flex flex-col gap-3">
<PolicyAuthMethodRow
id="pincode"
title={t("policyAuthPincodeTitle")}
description={t("policyAuthPincodeDescription")}
summary={getPincodeSummary({ t })}
active={pinActive}
onConfigure={() => onConfigure("pincode")}
onToggle={onTogglePincode}
disabled={disabled}
/>
<PolicyAuthMethodRow
id="passcode"
title={t("policyAuthPasscodeTitle")}
description={t("policyAuthPasscodeDescription")}
summary={getPasscodeSummary({ t })}
active={passcodeActive}
onConfigure={() => onConfigure("passcode")}
onToggle={onTogglePasscode}
disabled={disabled}
/>
<PolicyAuthMethodRow
id="email"
title={t("policyAuthEmailTitle")}
description={t("policyAuthEmailDescription")}
summary={getEmailWhitelistSummary({
t,
count: emailCount
})}
active={emailWhitelistEnabled}
onConfigure={() => onConfigure("email")}
onToggle={onToggleEmail}
disabled={disabled || !emailEnabled}
/>
<PolicyAuthMethodRow
id="header-auth"
title={t("policyAuthHeaderAuthTitle")}
description={t("policyAuthHeaderAuthDescription")}
summary={getHeaderAuthSummary({
t,
headerName: headerAuthUser
})}
active={headerAuthActive}
onConfigure={() => onConfigure("headerAuth")}
onToggle={onToggleHeaderAuth}
disabled={disabled}
/>
</div>
</SettingsFormCell>
</SettingsFormGrid>
);
}

View File

@@ -1,10 +1,6 @@
"use client"; "use client";
import { import { SettingsFormCell, SettingsFormGrid } from "@app/components/Settings";
SettingsFormCell,
SettingsFormGrid,
SettingsSectionForm
} from "@app/components/Settings";
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";
import { import {
@@ -58,106 +54,100 @@ export function PolicyAuthSsoSection({
const idpSelectDisabled = idpDisabled ?? disabled; const idpSelectDisabled = idpDisabled ?? disabled;
return ( return (
<SettingsSectionForm variant="half"> <SettingsFormGrid>
<SettingsFormGrid> <SettingsFormCell span="full">
<SettingsFormCell span="full"> <SwitchInput
<SwitchInput id="policy-auth-sso"
id="policy-auth-sso" label={t("policyAuthSsoTitle")}
label={t("policyAuthSsoTitle")} description={t("policyAuthSsoDescription")}
description={t("policyAuthSsoDescription")} checked={sso}
checked={sso} disabled={disabled}
disabled={disabled} onCheckedChange={onSsoChange}
onCheckedChange={onSsoChange} />
/> </SettingsFormCell>
</SettingsFormCell>
{sso && ( {sso && (
<> <>
<SettingsFormCell span="full"> <SettingsFormCell span="full">
<FormItem> <FormItem>
<FormLabel>{t("roles")}</FormLabel> <FormLabel>{t("roles")}</FormLabel>
{rolesEditor} {rolesEditor}
</FormItem> </FormItem>
</SettingsFormCell> </SettingsFormCell>
<SettingsFormCell span="full"> <SettingsFormCell span="full">
<FormItem> <FormItem>
<FormLabel>{t("users")}</FormLabel> <FormLabel>{t("users")}</FormLabel>
{usersEditor} {usersEditor}
</FormItem> </FormItem>
</SettingsFormCell> </SettingsFormCell>
{allIdps.length > 0 && ( {allIdps.length > 0 && (
<SettingsFormCell span="half"> <SettingsFormCell span="half">
{skipToIdpId == null && !showIdpSelect ? ( {skipToIdpId == null && !showIdpSelect ? (
<Button <Button
type="button" type="button"
variant="text" variant="text"
size="sm" size="sm"
className="h-auto px-0" className="h-auto px-0"
disabled={idpSelectDisabled}
onClick={() => setShowIdpSelect(true)}
>
{t("policyAuthAddDefaultIdentityProvider")}
</Button>
) : (
<FormItem>
<FormLabel>
{t("defaultIdentityProvider")}
</FormLabel>
<Select
disabled={idpSelectDisabled} disabled={idpSelectDisabled}
onClick={() => setShowIdpSelect(true)} onValueChange={(value) => {
> if (value === "none") {
{t( onSkipToIdpChange(null);
"policyAuthAddDefaultIdentityProvider" setShowIdpSelect(false);
)} return;
</Button>
) : (
<FormItem>
<FormLabel>
{t("defaultIdentityProvider")}
</FormLabel>
<Select
disabled={idpSelectDisabled}
onValueChange={(value) => {
if (value === "none") {
onSkipToIdpChange(null);
setShowIdpSelect(false);
return;
}
onSkipToIdpChange(
parseInt(value)
);
}}
value={
skipToIdpId
? skipToIdpId.toString()
: "none"
} }
> onSkipToIdpChange(parseInt(value));
<FormControl> }}
<SelectTrigger> value={
<SelectValue skipToIdpId
placeholder={t( ? skipToIdpId.toString()
"selectIdpPlaceholder" : "none"
)} }
/> >
</SelectTrigger> <FormControl>
</FormControl> <SelectTrigger>
<SelectContent> <SelectValue
<SelectItem value="none"> placeholder={t(
{t("none")} "selectIdpPlaceholder"
)}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="none">
{t("none")}
</SelectItem>
{allIdps.map((idp) => (
<SelectItem
key={idp.id}
value={idp.id.toString()}
>
{idp.text}
</SelectItem> </SelectItem>
{allIdps.map((idp) => ( ))}
<SelectItem </SelectContent>
key={idp.id} </Select>
value={idp.id.toString()} <FormDescription>
> {t(
{idp.text} "defaultIdentityProviderDescription"
</SelectItem> )}
))} </FormDescription>
</SelectContent> </FormItem>
</Select> )}
<FormDescription> </SettingsFormCell>
{t( )}
"defaultIdentityProviderDescription" </>
)} )}
</FormDescription> </SettingsFormGrid>
</FormItem>
)}
</SettingsFormCell>
)}
</>
)}
</SettingsFormGrid>
</SettingsSectionForm>
); );
} }

View File

@@ -1,15 +1,11 @@
"use client"; "use client";
import { import {
SettingsFormCell,
SettingsFormGrid,
SettingsSection, SettingsSection,
SettingsSectionBody, SettingsSectionBody,
SettingsSectionDescription, SettingsSectionDescription,
SettingsSectionForm,
SettingsSectionHeader, SettingsSectionHeader,
SettingsSubsectionDescription,
SettingsSubsectionHeader,
SettingsSubsectionTitle,
SettingsSectionTitle SettingsSectionTitle
} from "@app/components/Settings"; } from "@app/components/Settings";
import { RolesSelector } from "@app/components/roles-selector"; import { RolesSelector } from "@app/components/roles-selector";
@@ -25,15 +21,9 @@ import {
PasscodeCredenza, PasscodeCredenza,
PincodeCredenza PincodeCredenza
} from "./PolicyAuthMethodCredenzas"; } from "./PolicyAuthMethodCredenzas";
import { PolicyAuthMethodRow } from "./PolicyAuthMethodRow"; import { PolicyAuthOtherMethodsSection } from "./PolicyAuthOtherMethodsSection";
import { PolicyAuthSsoSection } from "./PolicyAuthSsoSection"; import { PolicyAuthSsoSection } from "./PolicyAuthSsoSection";
import type { PolicyAuthMethodId } from "./policy-auth-method-id"; import type { PolicyAuthMethodId } from "./policy-auth-method-id";
import {
getEmailWhitelistSummary,
getHeaderAuthSummary,
getPasscodeSummary,
getPincodeSummary
} from "./policy-auth-summaries";
export type PolicyAuthStackSectionCreateProps = { export type PolicyAuthStackSectionCreateProps = {
form: UseFormReturn<PolicyFormValues, any, any>; form: UseFormReturn<PolicyFormValues, any, any>;
@@ -105,144 +95,90 @@ export function PolicyAuthStackSectionCreate({
</SettingsSectionDescription> </SettingsSectionDescription>
</SettingsSectionHeader> </SettingsSectionHeader>
<SettingsSectionBody> <SettingsSectionBody>
<SettingsFormGrid> <SettingsSectionForm variant="half">
<SettingsFormCell span="half"> <PolicyAuthSsoSection
<PolicyAuthSsoSection sso={Boolean(sso)}
sso={Boolean(sso)} onSsoChange={(active) =>
onSsoChange={(active) => parentForm.setValue("sso", active)
parentForm.setValue("sso", active) }
} skipToIdpId={skipToIdpId}
skipToIdpId={skipToIdpId} onSkipToIdpChange={(id) =>
onSkipToIdpChange={(id) => parentForm.setValue("skipToIdpId", id)
parentForm.setValue("skipToIdpId", id) }
} allIdps={allIdps}
allIdps={allIdps} rolesEditor={
rolesEditor={ <FormField<PolicyFormValues, "roles">
<FormField<PolicyFormValues, "roles"> control={parentForm.control}
control={parentForm.control} name="roles"
name="roles" render={({ field }) => (
render={({ field }) => ( <RolesSelector
<RolesSelector orgId={orgId}
orgId={orgId} selectedRoles={field.value}
selectedRoles={field.value} onSelectRoles={(selected) =>
onSelectRoles={(selected) => parentForm.setValue(
parentForm.setValue( "roles",
"roles", selected
selected )
) }
} restrictAdminRole
restrictAdminRole />
/>
)}
/>
}
usersEditor={
<FormField<PolicyFormValues, "users">
control={parentForm.control}
name="users"
render={({ field }) => (
<UsersSelector
orgId={orgId}
selectedUsers={field.value}
onSelectUsers={(selected) =>
parentForm.setValue(
"users",
selected
)
}
/>
)}
/>
}
/>
</SettingsFormCell>
</SettingsFormGrid>
<SettingsFormGrid>
<SettingsFormCell span="full">
<SettingsSubsectionHeader>
<SettingsSubsectionTitle>
{t("policyAuthOtherMethodsTitle")}
</SettingsSubsectionTitle>
<SettingsSubsectionDescription>
{t("policyAuthOtherMethodsDescription")}
</SettingsSubsectionDescription>
</SettingsSubsectionHeader>
</SettingsFormCell>
<SettingsFormCell span="half">
<div className="flex flex-col gap-3">
<PolicyAuthMethodRow
id="pincode"
title={t("policyAuthPincodeTitle")}
description={t("policyAuthPincodeDescription")}
summary={getPincodeSummary({ t })}
active={pinActive}
onConfigure={() => setEditingMethod("pincode")}
onToggle={(active) =>
handleToggle("pincode", active, () =>
parentForm.setValue("pincode", null)
)
}
/>
<PolicyAuthMethodRow
id="passcode"
title={t("policyAuthPasscodeTitle")}
description={t("policyAuthPasscodeDescription")}
summary={getPasscodeSummary({ t })}
active={passcodeActive}
onConfigure={() => setEditingMethod("passcode")}
onToggle={(active) =>
handleToggle("passcode", active, () =>
parentForm.setValue("password", null)
)
}
/>
<PolicyAuthMethodRow
id="email"
title={t("policyAuthEmailTitle")}
description={t("policyAuthEmailDescription")}
summary={getEmailWhitelistSummary({
t,
count: emails.length
})}
active={Boolean(emailWhitelistEnabled)}
onConfigure={() => setEditingMethod("email")}
onToggle={(active) =>
handleToggle("email", active, () =>
parentForm.setValue(
"emailWhitelistEnabled",
false
)
)
}
disabled={!emailEnabled}
/>
<PolicyAuthMethodRow
id="header-auth"
title={t("policyAuthHeaderAuthTitle")}
description={t(
"policyAuthHeaderAuthDescription"
)} )}
summary={getHeaderAuthSummary({
t,
headerName: headerAuth?.user ?? ""
})}
active={headerAuthActive}
onConfigure={() =>
setEditingMethod("headerAuth")
}
onToggle={(active) =>
handleToggle("headerAuth", active, () =>
parentForm.setValue("headerAuth", null)
)
}
/> />
</div> }
</SettingsFormCell> usersEditor={
</SettingsFormGrid> <FormField<PolicyFormValues, "users">
control={parentForm.control}
name="users"
render={({ field }) => (
<UsersSelector
orgId={orgId}
selectedUsers={field.value}
onSelectUsers={(selected) =>
parentForm.setValue(
"users",
selected
)
}
/>
)}
/>
}
/>
<PolicyAuthOtherMethodsSection
pinActive={pinActive}
passcodeActive={passcodeActive}
emailWhitelistEnabled={Boolean(emailWhitelistEnabled)}
headerAuthActive={headerAuthActive}
headerAuthUser={headerAuth?.user ?? ""}
emailCount={emails.length}
emailEnabled={emailEnabled}
onConfigure={setEditingMethod}
onTogglePincode={(active) =>
handleToggle("pincode", active, () =>
parentForm.setValue("pincode", null)
)
}
onTogglePasscode={(active) =>
handleToggle("passcode", active, () =>
parentForm.setValue("password", null)
)
}
onToggleEmail={(active) =>
handleToggle("email", active, () =>
parentForm.setValue(
"emailWhitelistEnabled",
false
)
)
}
onToggleHeaderAuth={(active) =>
handleToggle("headerAuth", active, () =>
parentForm.setValue("headerAuth", null)
)
}
/>
</SettingsSectionForm>
<PincodeCredenza <PincodeCredenza
open={editingMethod === "pincode"} open={editingMethod === "pincode"}

View File

@@ -1,16 +1,12 @@
"use client"; "use client";
import { import {
SettingsFormCell,
SettingsFormGrid,
SettingsSection, SettingsSection,
SettingsSectionBody, SettingsSectionBody,
SettingsSectionDescription, SettingsSectionDescription,
SettingsSectionForm,
SettingsSectionFooter, SettingsSectionFooter,
SettingsSectionHeader, SettingsSectionHeader,
SettingsSubsectionDescription,
SettingsSubsectionHeader,
SettingsSubsectionTitle,
SettingsSectionTitle SettingsSectionTitle
} from "@app/components/Settings"; } from "@app/components/Settings";
import { import {
@@ -50,15 +46,9 @@ import {
PasscodeCredenza, PasscodeCredenza,
PincodeCredenza PincodeCredenza
} from "./PolicyAuthMethodCredenzas"; } from "./PolicyAuthMethodCredenzas";
import { PolicyAuthMethodRow } from "./PolicyAuthMethodRow"; import { PolicyAuthOtherMethodsSection } from "./PolicyAuthOtherMethodsSection";
import { PolicyAuthSsoSection } from "./PolicyAuthSsoSection"; import { PolicyAuthSsoSection } from "./PolicyAuthSsoSection";
import type { PolicyAuthMethodId } from "./policy-auth-method-id"; import type { PolicyAuthMethodId } from "./policy-auth-method-id";
import {
getEmailWhitelistSummary,
getHeaderAuthSummary,
getPasscodeSummary,
getPincodeSummary
} from "./policy-auth-summaries";
import { SharedPolicyResourceNotice } from "./SharedPolicyResourceNotice"; import { SharedPolicyResourceNotice } from "./SharedPolicyResourceNotice";
import z from "zod"; import z from "zod";
@@ -528,255 +518,133 @@ export function PolicyAuthStackSectionEdit({
</SettingsSectionDescription> </SettingsSectionDescription>
</SettingsSectionHeader> </SettingsSectionHeader>
<SettingsSectionBody> <SettingsSectionBody>
<div className="space-y-4"> {isResourceOverlay && (
{isResourceOverlay && ( <SharedPolicyResourceNotice section="authentication" />
<SharedPolicyResourceNotice section="authentication" /> )}
)} <SettingsSectionForm variant="half">
<SettingsFormGrid> <PolicyAuthSsoSection
<SettingsFormCell span="half"> sso={Boolean(sso)}
<PolicyAuthSsoSection onSsoChange={(active) =>
sso={Boolean(sso)} form.setValue("sso", active)
onSsoChange={(active) => }
form.setValue("sso", active) skipToIdpId={skipToIdpId}
} onSkipToIdpChange={(id) =>
skipToIdpId={skipToIdpId} form.setValue("skipToIdpId", id)
onSkipToIdpChange={(id) => }
form.setValue("skipToIdpId", id) allIdps={allIdps}
} disabled={authReadonly}
allIdps={allIdps} idpDisabled={authReadonly}
disabled={authReadonly} rolesEditor={
idpDisabled={authReadonly} isResourceOverlay ? (
rolesEditor={ <RolesSelector
isResourceOverlay ? ( orgId={orgId}
selectedRoles={overlayRoles}
onSelectRoles={(selected) =>
setCombinedRoles(
selected.map((role) => ({
...role,
isAdmin: Boolean(
role.isAdmin
)
}))
)
}
disabled={isLoading}
restrictAdminRole
lockedIds={policyRoleLockedIds}
/>
) : (
<FormField
control={form.control}
name="roles"
render={({ field }) => (
<RolesSelector <RolesSelector
orgId={orgId} orgId={orgId}
selectedRoles={overlayRoles} selectedRoles={field.value}
onSelectRoles={(selected) => onSelectRoles={(selected) =>
setCombinedRoles( form.setValue(
selected.map( "roles",
(role) => ({ selected
...role,
isAdmin:
Boolean(
role.isAdmin
)
})
)
) )
} }
disabled={isLoading} disabled={readonly}
restrictAdminRole restrictAdminRole
lockedIds={
policyRoleLockedIds
}
/> />
) : ( )}
<FormField />
control={form.control} )
name="roles" }
render={({ field }) => ( usersEditor={
<RolesSelector isResourceOverlay ? (
orgId={orgId} <UsersSelector
selectedRoles={ orgId={orgId}
field.value selectedUsers={overlayUsers}
} onSelectUsers={setCombinedUsers}
onSelectRoles={( disabled={isLoading}
selected lockedIds={policyUserLockedIds}
) => />
form.setValue( ) : (
"roles", <FormField
selected control={form.control}
) name="users"
} render={({ field }) => (
disabled={readonly}
restrictAdminRole
/>
)}
/>
)
}
usersEditor={
isResourceOverlay ? (
<UsersSelector <UsersSelector
orgId={orgId} orgId={orgId}
selectedUsers={overlayUsers} selectedUsers={field.value}
onSelectUsers={ onSelectUsers={(selected) =>
setCombinedUsers
}
disabled={isLoading}
lockedIds={
policyUserLockedIds
}
/>
) : (
<FormField
control={form.control}
name="users"
render={({ field }) => (
<UsersSelector
orgId={orgId}
selectedUsers={
field.value
}
onSelectUsers={(
selected
) =>
form.setValue(
"users",
selected
)
}
disabled={readonly}
/>
)}
/>
)
}
/>
</SettingsFormCell>
</SettingsFormGrid>
<SettingsFormGrid>
<SettingsFormCell span="full">
<SettingsSubsectionHeader>
<SettingsSubsectionTitle>
{t("policyAuthOtherMethodsTitle")}
</SettingsSubsectionTitle>
<SettingsSubsectionDescription>
{t(
"policyAuthOtherMethodsDescription"
)}
</SettingsSubsectionDescription>
</SettingsSubsectionHeader>
</SettingsFormCell>
<SettingsFormCell span="half">
<div className="flex flex-col gap-3">
<PolicyAuthMethodRow
id="pincode"
title={t("policyAuthPincodeTitle")}
description={t(
"policyAuthPincodeDescription"
)}
summary={getPincodeSummary({ t })}
active={pinActive}
onConfigure={() =>
openMethodEditor("pincode")
}
onToggle={(active) =>
handleToggle(
"pincode",
active,
() => {
setPinActive(false);
form.setValue( form.setValue(
"pincode", "users",
null selected
);
}
)
}
disabled={authReadonly}
/>
<PolicyAuthMethodRow
id="passcode"
title={t("policyAuthPasscodeTitle")}
description={t(
"policyAuthPasscodeDescription"
)}
summary={getPasscodeSummary({ t })}
active={passcodeActive}
onConfigure={() =>
openMethodEditor("passcode")
}
onToggle={(active) =>
handleToggle(
"passcode",
active,
() => {
setPasscodeActive(
false
);
form.setValue(
"password",
null
);
}
)
}
disabled={authReadonly}
/>
<PolicyAuthMethodRow
id="email"
title={t("policyAuthEmailTitle")}
description={t(
"policyAuthEmailDescription"
)}
summary={getEmailWhitelistSummary({
t,
count: emails.length
})}
active={Boolean(
emailWhitelistEnabled
)}
onConfigure={() =>
openMethodEditor("email")
}
onToggle={(active) =>
handleToggle(
"email",
active,
() =>
form.setValue(
"emailWhitelistEnabled",
false
) )
)
}
disabled={
authReadonly || !emailEnabled
}
/>
<PolicyAuthMethodRow
id="header-auth"
title={t(
"policyAuthHeaderAuthTitle"
)}
description={t(
"policyAuthHeaderAuthDescription"
)}
summary={getHeaderAuthSummary({
t,
headerName:
headerAuth?.user ?? ""
})}
active={headerAuthActive}
onConfigure={() =>
openMethodEditor("headerAuth")
}
onToggle={(active) =>
handleToggle(
"headerAuth",
active,
() => {
setHeaderAuthActive(
false
);
form.setValue(
"headerAuth",
null
);
} }
) disabled={readonly}
} />
disabled={authReadonly} )}
/> />
</div> )
</SettingsFormCell> }
</SettingsFormGrid> />
</div>
<PolicyAuthOtherMethodsSection
pinActive={pinActive}
passcodeActive={passcodeActive}
emailWhitelistEnabled={Boolean(
emailWhitelistEnabled
)}
headerAuthActive={headerAuthActive}
headerAuthUser={headerAuth?.user ?? ""}
emailCount={emails.length}
emailEnabled={emailEnabled}
disabled={authReadonly}
onConfigure={openMethodEditor}
onTogglePincode={(active) =>
handleToggle("pincode", active, () => {
setPinActive(false);
form.setValue("pincode", null);
})
}
onTogglePasscode={(active) =>
handleToggle("passcode", active, () => {
setPasscodeActive(false);
form.setValue("password", null);
})
}
onToggleEmail={(active) =>
handleToggle("email", active, () =>
form.setValue(
"emailWhitelistEnabled",
false
)
)
}
onToggleHeaderAuth={(active) =>
handleToggle("headerAuth", active, () => {
setHeaderAuthActive(false);
form.setValue("headerAuth", null);
})
}
/>
</SettingsSectionForm>
<PincodeCredenza <PincodeCredenza
open={editingMethod === "pincode"} open={editingMethod === "pincode"}