mirror of
https://github.com/uroni/urbackup_backend.git
synced 2025-10-26 11:36:50 +00:00
Refactor form components
This commit is contained in:
parent
ee988c7774
commit
e48fd206e2
@ -33,3 +33,8 @@
|
||||
.form > :has(label):not(:last-of-type) {
|
||||
border-block-end: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.form > [type="submit"] {
|
||||
display: block;
|
||||
margin-block-start: var(--spacingXL);
|
||||
}
|
||||
21
urbackupserver/www2/src/features/settings/Form/Form.tsx
Normal file
21
urbackupserver/www2/src/features/settings/Form/Form.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import styles from "./Form.module.css";
|
||||
|
||||
export function Form(props: React.ComponentProps<"form">) {
|
||||
return <form {...props} className={`${styles.form} ${props.className}`} />;
|
||||
}
|
||||
|
||||
export function FormContainer({ children }: { children: React.ReactNode }) {
|
||||
return <div className={`${styles.container} flow`}>{children}</div>;
|
||||
}
|
||||
|
||||
export function FormCard({
|
||||
as,
|
||||
children,
|
||||
}: {
|
||||
as?: React.ElementType;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const Wrapper = as ?? "section";
|
||||
|
||||
return <Wrapper className={styles.card}>{children}</Wrapper>;
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
.container {
|
||||
--border-color: var(--colorNeutralStroke2);
|
||||
--flow-space: 2em;
|
||||
|
||||
width: 74ch;
|
||||
margin: 0 auto;
|
||||
padding-block-end: --spacing;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: "flex";
|
||||
flex-direction: "column";
|
||||
background: var(--colorNeutralCardBackground);
|
||||
padding: var(--spacingL);
|
||||
border-radius: var(--borderRadiusLarge);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.form > :has(label) {
|
||||
padding-block: 0.5em;
|
||||
}
|
||||
|
||||
.form > :first-child:has(label) {
|
||||
padding-block-start: 0em;
|
||||
}
|
||||
|
||||
.form > :last-child:has(label) {
|
||||
padding-block-end: 0em;
|
||||
}
|
||||
|
||||
.form > :has(label):not(:last-of-type) {
|
||||
border-block-end: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.form > [type="submit"] {
|
||||
display: block;
|
||||
margin-block-start: var(--spacingXL);
|
||||
}
|
||||
|
||||
.single-field {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: baseline;
|
||||
gap: var(--gutter);
|
||||
}
|
||||
|
||||
.field-description {
|
||||
color: var(--colorNeutralForeground2);
|
||||
margin-block-start: var(--spacingXS);
|
||||
}
|
||||
@ -8,15 +8,15 @@ import type {
|
||||
SettingState,
|
||||
StringBoolSetting,
|
||||
} from "../../../api/urbackupserver";
|
||||
import styles from "./Mail.module.css";
|
||||
import { CheckboxFieldUncontrolled } from "../Fields/CheckboxField";
|
||||
import { CheckboxFieldUncontrolled } from "../Form/CheckboxField";
|
||||
import { mailFields, mailFormSchema, MailSettingsKey } from "./mailForm";
|
||||
import { TextFieldUncontrolled } from "../Fields/TextField";
|
||||
import { TextFieldUncontrolled } from "../Form/TextField";
|
||||
import { Banner } from "../../../components/Banner/Banner";
|
||||
import { clearMessages } from "../../../components/Banner/messageStore";
|
||||
import { useMail } from "./useMail";
|
||||
import { TestMail } from "./TestMail";
|
||||
import { Field } from "../Fields/types";
|
||||
import { Field } from "../Form/types";
|
||||
import { Form, FormCard, FormContainer } from "../Form/Form";
|
||||
|
||||
export function Mail() {
|
||||
const { settings, handleSubmit, sendTestMail } = useMail();
|
||||
@ -30,22 +30,22 @@ export function Mail() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.container} flow`}>
|
||||
<FormContainer>
|
||||
<h1>Mail</h1>
|
||||
|
||||
<Banner />
|
||||
|
||||
<section className={styles.card}>
|
||||
<FormCard>
|
||||
<FormSection
|
||||
fields={mailFields}
|
||||
schema={mailFormSchema}
|
||||
initialFormState={initialFormState}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
</section>
|
||||
</FormCard>
|
||||
|
||||
<TestMail onSubmit={sendTestMail} />
|
||||
</div>
|
||||
</FormContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ function FormSection<T extends string>({
|
||||
};
|
||||
|
||||
return (
|
||||
<form className={`${styles.form}`} onSubmit={handleSubmit} noValidate>
|
||||
<Form onSubmit={handleSubmit} noValidate>
|
||||
{fields.map((f) => {
|
||||
const initialValue = initialFormState[f.name];
|
||||
|
||||
@ -130,7 +130,7 @@ function FormSection<T extends string>({
|
||||
<Button type="submit" appearance="primary">
|
||||
Save mail settings
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
.single-field {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: baseline;
|
||||
gap: var(--gutter);
|
||||
}
|
||||
@ -6,9 +6,10 @@ import {
|
||||
clearMessages,
|
||||
Message,
|
||||
} from "../../../components/Banner/messageStore";
|
||||
import styles from "./Mail.module.css";
|
||||
import styles from "./TestMail.module.css";
|
||||
import { SingleBanner } from "../../../components/Banner/Banner";
|
||||
import { MailSettings } from "../../../api/urbackupserver";
|
||||
import { FormCard } from "../Form/Form";
|
||||
|
||||
const TESTMAIL = {
|
||||
LABEL: "Send test mail to this email address",
|
||||
@ -78,7 +79,7 @@ export function TestMail({
|
||||
{feedback && (
|
||||
<SingleBanner message={feedback} onClick={() => setFeedback(null)} />
|
||||
)}
|
||||
<div className={styles.card}>
|
||||
<FormCard>
|
||||
<div className="flow flow-xs">
|
||||
<Label htmlFor={TESTMAIL.ID}>{TESTMAIL.LABEL}</Label>
|
||||
<form className={styles["single-field"]} onSubmit={handleSubmit}>
|
||||
@ -87,14 +88,13 @@ export function TestMail({
|
||||
validationState: "error",
|
||||
validationMessage,
|
||||
})}
|
||||
className={styles.field}
|
||||
>
|
||||
<Input id={TESTMAIL.ID} name={TESTMAIL.ID} type="email" />
|
||||
</Field>
|
||||
<Button type="submit">Send test mail</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</FormCard>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { z } from "zod/v4-mini";
|
||||
|
||||
import { integerValidation, VALIDATION_MESSAGES } from "../Fields/validation";
|
||||
import { integerValidation, VALIDATION_MESSAGES } from "../Form/validation";
|
||||
import { type MailSettingsVals } from "../../../api/urbackupserver";
|
||||
import { BaseField, Field } from "../Fields/types";
|
||||
import { BaseField, Field } from "../Form/types";
|
||||
|
||||
export type MailSettingsKey = keyof MailSettingsVals;
|
||||
|
||||
|
||||
@ -6,19 +6,19 @@ import type {
|
||||
GeneralSettings,
|
||||
SettingState,
|
||||
} from "../../../api/urbackupserver";
|
||||
import styles from "./SettingsServer.module.css";
|
||||
import { CheckboxField } from "../Fields/CheckboxField";
|
||||
import { CheckboxField } from "../Form/CheckboxField";
|
||||
import {
|
||||
AdvancedServerFieldNames,
|
||||
advancedServerFields,
|
||||
advancedServerFormSchema,
|
||||
Field,
|
||||
ServerFieldNames,
|
||||
serverFields,
|
||||
serverFormSchema,
|
||||
} from "./serverForm";
|
||||
import { TextField } from "../Fields/TextField";
|
||||
import { TextField } from "../Form/TextField";
|
||||
import { useSettings } from "../useSettings";
|
||||
import { Form, FormCard, FormContainer } from "../Form/Form";
|
||||
import { Field } from "../Form/types";
|
||||
|
||||
export function SettingsServer() {
|
||||
const { settings, updateSettings } = useSettings();
|
||||
@ -34,16 +34,16 @@ export function SettingsServer() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.container} flow`}>
|
||||
<FormContainer>
|
||||
<h1>Server</h1>
|
||||
<section className={styles.card}>
|
||||
<FormCard>
|
||||
<FormSection
|
||||
fields={serverFields}
|
||||
schema={serverFormSchema}
|
||||
initialFormState={initialFormState}
|
||||
updateSettings={updateSettings}
|
||||
/>
|
||||
</section>
|
||||
</FormCard>
|
||||
|
||||
{!showAdvancedSettings && (
|
||||
<div>
|
||||
@ -56,17 +56,17 @@ export function SettingsServer() {
|
||||
{showAdvancedSettings && (
|
||||
<>
|
||||
<h2>Advanced</h2>
|
||||
<section className={styles.card}>
|
||||
<FormCard>
|
||||
<FormSection
|
||||
fields={advancedServerFields}
|
||||
schema={advancedServerFormSchema}
|
||||
initialFormState={initialFormState}
|
||||
updateSettings={updateSettings}
|
||||
/>
|
||||
</section>
|
||||
</FormCard>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</FormContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ function FormSection<T extends string>({
|
||||
) => void;
|
||||
}) {
|
||||
return (
|
||||
<form className={`${styles.form}`}>
|
||||
<Form>
|
||||
{fields.map((f) => {
|
||||
const initialValue = initialFormState[f.name];
|
||||
|
||||
@ -128,7 +128,7 @@ function FormSection<T extends string>({
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -5,8 +5,8 @@ import {
|
||||
integerValidation,
|
||||
requiredStringValidation,
|
||||
VALIDATION_MESSAGES,
|
||||
} from "../Fields/validation";
|
||||
import { BaseField, Field } from "../Fields/types";
|
||||
} from "../Form/validation";
|
||||
import { BaseField, Field } from "../Form/types";
|
||||
|
||||
const CLEANUP_WINDOW_REGEX =
|
||||
/^(([mon|mo|tu|tue|tues|di|wed|mi|th|thu|thur|thurs|do|fri|fr|sat|sa|sun|so|1-7]\-?[mon|mo|tu|tue|tues|di|wed|mi|th|thu|thur|thurs|do|fri|fr|sat|sa|sun|so|1-7]?\s*[,]?\s*)+\/([0-9][0-9]?:?[0-9]?[0-9]?\-[0-9][0-9]?:?[0-9]?[0-9]?\s*[,]?\s*)+\s*[;]?\s*)*$/i;
|
||||
|
||||
@ -19,7 +19,7 @@ import styles from "./CreateUser.module.css";
|
||||
import {
|
||||
requiredStringValidation,
|
||||
VALIDATION_MESSAGES,
|
||||
} from "../Fields/validation";
|
||||
} from "../Form/validation";
|
||||
import { DismissRegular } from "@fluentui/react-icons";
|
||||
import { UserAlreadyExistsError, UserRight } from "../../../api/urbackupserver";
|
||||
import {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user