Skip to content

Commit

Permalink
fix: portal frontend#12 (#48)
Browse files Browse the repository at this point in the history
* fix navigation types

* allow for validate options

* fix type

* support async validations

* fix repeat field

* expect error

* simplify schema
  • Loading branch information
SKairinos authored Jul 16, 2024
1 parent 4057d7f commit 4cf7b53
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 75 deletions.
22 changes: 22 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"configurations": [
{
"args": [
"run",
"${relativeFile}"
],
"autoAttachChildProcesses": true,
"console": "integratedTerminal",
"name": "Vitest: Current File",
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"request": "launch",
"skipFiles": [
"<node_internals>/**",
"**/node_modules/**"
],
"smartStep": true,
"type": "node"
}
],
"version": "0.2.0"
}
8 changes: 5 additions & 3 deletions src/components/form/AutocompleteField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type TextFieldProps,
} from "@mui/material"
import { Field, type FieldConfig, type FieldProps } from "formik"
import * as yup from "yup"
import { string as YupString, type ValidateOptions } from "yup"

import { schemaToFieldValidator } from "../../utils/form"

Expand Down Expand Up @@ -39,6 +39,7 @@ export interface AutocompleteFieldProps<
> & {
name: string
}
validateOptions?: ValidateOptions
}

const AutocompleteField = <
Expand All @@ -49,6 +50,7 @@ const AutocompleteField = <
>({
textFieldProps,
options,
validateOptions,
...otherAutocompleteProps
}: AutocompleteFieldProps<
Multiple,
Expand All @@ -58,13 +60,13 @@ const AutocompleteField = <
>): JSX.Element => {
const { name, required, ...otherTextFieldProps } = textFieldProps

let schema = yup.string().oneOf(options, "not a valid option")
let schema = YupString().oneOf(options, "not a valid option")
if (required) schema = schema.required()

const fieldConfig: FieldConfig = {
name,
type: "text",
validate: schemaToFieldValidator(schema),
validate: schemaToFieldValidator(schema, validateOptions),
}

return (
Expand Down
10 changes: 6 additions & 4 deletions src/components/form/CheckboxField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "@mui/material"
import { Field, type FieldConfig, type FieldProps } from "formik"
import { type FC } from "react"
import { bool as YupBool } from "yup"
import { bool as YupBool, type ValidateOptions } from "yup"

import { schemaToFieldValidator } from "../../utils/form"

Expand All @@ -20,22 +20,24 @@ export interface CheckboxFieldProps
name: string
formControlLabelProps: Omit<FormControlLabelProps, "control">
errorMessage?: string
validateOptions?: ValidateOptions
}

const CheckboxField: FC<CheckboxFieldProps> = ({
name,
formControlLabelProps,
required = false,
errorMessage = "this is a required field",
validateOptions,
...otherCheckboxProps
}) => {
let validate = YupBool()
if (required) validate = validate.oneOf([true], errorMessage)
let schema = YupBool()
if (required) schema = schema.oneOf([true], errorMessage)

const fieldConfig: FieldConfig = {
name,
type: "checkbox",
validate: schemaToFieldValidator(validate),
validate: schemaToFieldValidator(schema, validateOptions),
}

return (
Expand Down
8 changes: 5 additions & 3 deletions src/components/form/DatePickerField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"
import dayjs, { type Dayjs } from "dayjs"
import "dayjs/locale/en-gb"
import { Field, type FieldConfig, type FieldProps } from "formik"
import * as yup from "yup"
import { date as YupDate, type ValidateOptions } from "yup"

import { schemaToFieldValidator } from "../../utils/form"

Expand All @@ -23,6 +23,7 @@ export interface DatePickerFieldProps<
required?: boolean
min?: string | Date
max?: string | Date
validateOptions?: ValidateOptions
}

const DatePickerField = <
Expand All @@ -33,20 +34,21 @@ const DatePickerField = <
required,
min,
max,
validateOptions,
...otherDatePickerProps
}: DatePickerFieldProps<
TDate,
TEnableAccessibleFieldDOMStructure
>): JSX.Element => {
let schema = yup.date()
let schema = YupDate()
if (required) schema = schema.required()
if (min) schema = schema.min(min)
if (max) schema = schema.max(max)

const fieldConfig: FieldConfig = {
name,
type: "date",
validate: schemaToFieldValidator(schema),
validate: schemaToFieldValidator(schema, validateOptions),
}

return (
Expand Down
104 changes: 65 additions & 39 deletions src/components/form/RepeatField.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { TextField, type TextFieldProps } from "@mui/material"
import { TextField as MuiTextField, type TextFieldProps } from "@mui/material"
import { Field, type FieldConfig, type FieldProps } from "formik"
import { useState, type FC } from "react"
import * as yup from "yup"
import {
useEffect,
useState,
type Dispatch,
type FC,
type SetStateAction,
} from "react"
import { string as YupString, type ValidateOptions } from "yup"

import { schemaToFieldValidator } from "../../utils/form"

Expand All @@ -18,14 +24,56 @@ export type RepeatFieldProps = Omit<
| "required"
> & {
name: string
validateOptions?: ValidateOptions
}

// https://formik.org/docs/examples/with-material-ui
const RepeatField: FC<RepeatFieldProps> = ({
const TextField: FC<
RepeatFieldProps & {
repeatName: string
setValue: Dispatch<SetStateAction<string>>
fieldProps: FieldProps
}
> = ({
repeatName,
setValue,
fieldProps,
name,
label,
placeholder,
type,
...otherTextFieldProps
}) => {
const { form } = fieldProps

useEffect(() => {
setValue(form.values[name])
})

return (
<MuiTextField
required
type={type}
label={label ?? `Repeat ${name.replace("_", " ")}`}
placeholder={placeholder ?? `Enter your ${name.replace("_", " ")} again`}
id={repeatName}
name={repeatName}
value={form.values[repeatName]}
onChange={form.handleChange}
onBlur={form.handleBlur}
error={form.touched[repeatName] && Boolean(form.errors[repeatName])}
helperText={
(form.touched[repeatName] && form.errors[repeatName]) as false | string
}
{...otherTextFieldProps}
/>
)
}

// https://formik.org/docs/examples/with-material-ui
const RepeatField: FC<RepeatFieldProps> = ({
name,
type = "text",
validateOptions,
...otherTextFieldProps
}) => {
const [value, setValue] = useState("")
Expand All @@ -36,45 +84,23 @@ const RepeatField: FC<RepeatFieldProps> = ({
name: repeatName,
type,
validate: schemaToFieldValidator(
yup
.string()
.required()
.test(
`matches-${name}`,
`does not match`,
repeatValue => value === repeatValue,
),
YupString().required().equals([value], "does not match"),
validateOptions,
),
}

return (
<Field {...fieldConfig}>
{({ form }: FieldProps) => {
setValue(form.values[name])

return (
<TextField
required
type={type}
label={label ?? `Repeat ${name.replace("_", " ")}`}
placeholder={
placeholder ?? `Enter your ${name.replace("_", " ")} again`
}
id={repeatName}
name={repeatName}
value={form.values[repeatName]}
onChange={form.handleChange}
onBlur={form.handleBlur}
error={form.touched[repeatName] && Boolean(form.errors[repeatName])}
helperText={
(form.touched[repeatName] && form.errors[repeatName]) as
| false
| string
}
{...otherTextFieldProps}
/>
)
}}
{(fieldProps: FieldProps) => (
<TextField
name={name}
type={type}
repeatName={repeatName}
setValue={setValue}
fieldProps={fieldProps}
{...otherTextFieldProps}
/>
)}
</Field>
)
}
Expand Down
6 changes: 4 additions & 2 deletions src/components/form/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
} from "@mui/material"
import { Field, type FieldConfig, type FieldProps } from "formik"
import type { FC } from "react"
import { type StringSchema } from "yup"
import { type StringSchema, type ValidateOptions } from "yup"

import { schemaToFieldValidator } from "../../utils/form"

Expand All @@ -21,6 +21,7 @@ export type TextFieldProps = Omit<
> & {
name: string
schema: StringSchema
validateOptions?: ValidateOptions
}

// https://formik.org/docs/examples/with-material-ui
Expand All @@ -29,14 +30,15 @@ const TextField: FC<TextFieldProps> = ({
schema,
type = "text",
required = false,
validateOptions,
...otherTextFieldProps
}) => {
if (required) schema = schema.required()

const fieldConfig: FieldConfig = {
name,
type,
validate: schemaToFieldValidator(schema),
validate: schemaToFieldValidator(schema, validateOptions),
}

return (
Expand Down
8 changes: 4 additions & 4 deletions src/components/router/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Link as MuiLink, type LinkProps as MuiLinkProps } from "@mui/material"
import type { FC } from "react"
import {
Link as RouterLink,
type LinkProps as RouterLinkProps,
} from "react-router-dom"
import { Link as RouterLink } from "react-router-dom"

import { type LinkProps as RouterLinkProps } from "../../utils/router"

export type LinkProps = Omit<MuiLinkProps, "component"> & RouterLinkProps

// https://mui.com/material-ui/integrations/routing/#link
const Link: FC<LinkProps> = props => {
// @ts-expect-error
return <MuiLink component={RouterLink} {...props} />
}

Expand Down
4 changes: 3 additions & 1 deletion src/components/router/LinkButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Button, type ButtonProps } from "@mui/material"
import type { FC } from "react"
import { Link, type LinkProps } from "react-router-dom"
import { Link } from "react-router-dom"

import { type LinkProps } from "../../utils/router"

export type LinkButtonProps = Omit<ButtonProps, "component"> & LinkProps

Expand Down
4 changes: 3 additions & 1 deletion src/components/router/LinkIconButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { IconButton, type IconButtonProps } from "@mui/material"
import type { FC } from "react"
import { Link, type LinkProps } from "react-router-dom"
import { Link } from "react-router-dom"

import { type LinkProps } from "../../utils/router"

export type LinkIconButtonProps = Omit<IconButtonProps, "component"> & LinkProps

Expand Down
4 changes: 3 additions & 1 deletion src/components/router/LinkListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ListItem, type ListItemProps } from "@mui/material"
import type { FC } from "react"
import { Link, type LinkProps } from "react-router-dom"
import { Link } from "react-router-dom"

import { type LinkProps } from "../../utils/router"

export type LinkListItemProps = Omit<ListItemProps, "component"> & LinkProps

Expand Down
4 changes: 3 additions & 1 deletion src/components/router/LinkTab.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Tab, type TabProps } from "@mui/material"
import type { FC } from "react"
import { Link, type LinkProps } from "react-router-dom"
import { Link } from "react-router-dom"

import { type LinkProps } from "../../utils/router"

export type LinkTabProps = Omit<TabProps, "component"> & LinkProps

Expand Down
Loading

0 comments on commit 4cf7b53

Please sign in to comment.