codeforlife 2.6.11 → 2.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/main.yml +7 -0
- package/CHANGELOG.md +15 -0
- package/package.json +2 -15
- package/src/api/index.ts +1 -0
- package/src/api/schemas.ts +117 -0
- package/src/components/form/FirstNameField.tsx +2 -2
- package/src/components/form/TextField.tsx +40 -23
- package/src/settings/index.ts +6 -1
- package/src/utils/api.tsx +6 -1
- package/src/utils/general.ts +5 -0
- package/src/utils/schema.ts +210 -0
- package/src/vite.config.ts +49 -0
- package/vite.config.ts +2 -0
- package/codecov.yml +0 -11
- package/src/schemas/user.ts +0 -4
|
@@ -34,3 +34,10 @@ jobs:
|
|
|
34
34
|
with:
|
|
35
35
|
cfl-bot-gh-token: ${{ secrets.CFL_BOT_GH_TOKEN }}
|
|
36
36
|
npm-token: ${{ secrets.NPM_TOKEN }}
|
|
37
|
+
|
|
38
|
+
monitor:
|
|
39
|
+
uses: ocadotechnology/codeforlife-workspace/.github/workflows/monitor-javascript-release.yaml@main
|
|
40
|
+
secrets: inherit
|
|
41
|
+
needs: [release]
|
|
42
|
+
with:
|
|
43
|
+
node-version: 18
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## [2.6.13](https://github.com/ocadotechnology/codeforlife-package-javascript/compare/v2.6.12...v2.6.13) (2025-03-25)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* char set validators ([#81](https://github.com/ocadotechnology/codeforlife-package-javascript/issues/81)) ([9ff1f60](https://github.com/ocadotechnology/codeforlife-package-javascript/commit/9ff1f60808f4a50eb2cbcae997de4ae892665c28))
|
|
7
|
+
|
|
8
|
+
## [2.6.12](https://github.com/ocadotechnology/codeforlife-package-javascript/compare/v2.6.11...v2.6.12) (2025-03-20)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* monitor release ([4bb40e1](https://github.com/ocadotechnology/codeforlife-package-javascript/commit/4bb40e1c0021c5371c64b3fb894b4fddccf19374))
|
|
14
|
+
* Portal frontend 48 ([#82](https://github.com/ocadotechnology/codeforlife-package-javascript/issues/82)) ([b76b4b8](https://github.com/ocadotechnology/codeforlife-package-javascript/commit/b76b4b8dd4b5822820577a149d13992468c1cc0c))
|
|
15
|
+
|
|
1
16
|
## [2.6.11](https://github.com/ocadotechnology/codeforlife-package-javascript/compare/v2.6.10...v2.6.11) (2025-02-25)
|
|
2
17
|
|
|
3
18
|
|
package/package.json
CHANGED
|
@@ -2,22 +2,10 @@
|
|
|
2
2
|
"name": "codeforlife",
|
|
3
3
|
"description": "Common frontend code",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "2.6.
|
|
5
|
+
"version": "2.6.13",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"
|
|
9
|
-
"start": "serve -s dist",
|
|
10
|
-
"build": "tsc && vite build",
|
|
11
|
-
"preview": "vite preview",
|
|
12
|
-
"test": "vitest run",
|
|
13
|
-
"test:coverage": "vitest run --coverage",
|
|
14
|
-
"test:verbose": "vitest run --reporter=verbose --coverage.thresholds.lines=90 --coverage.thresholds.functions=90 --coverage.thresholds.branches=90 --coverage.thresholds.statements=90",
|
|
15
|
-
"test:ui": "vitest --ui",
|
|
16
|
-
"format": "prettier --write .",
|
|
17
|
-
"format:check": "prettier --check --write=false .",
|
|
18
|
-
"lint": "eslint --max-warnings=0 .",
|
|
19
|
-
"lint:fix": "eslint --fix .",
|
|
20
|
-
"type-check": "tsc --noEmit"
|
|
8
|
+
"cli": "VITE_CONFIG=./vite.config.ts ../scripts/frontend/cli $@"
|
|
21
9
|
},
|
|
22
10
|
"repository": {
|
|
23
11
|
"type": "git",
|
|
@@ -47,7 +35,6 @@
|
|
|
47
35
|
"react-dom": "^18.2.0",
|
|
48
36
|
"react-redux": "^9.1.0",
|
|
49
37
|
"react-router-dom": "^6.23.1",
|
|
50
|
-
"serve": "^14.2.3",
|
|
51
38
|
"sirv": "^3.0.0",
|
|
52
39
|
"yup": "^1.1.1"
|
|
53
40
|
},
|
package/src/api/index.ts
CHANGED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as yup from "yup"
|
|
2
|
+
|
|
3
|
+
import { UK_COUNTIES, COUNTRY_ISO_CODES } from "../utils/general"
|
|
4
|
+
import type {
|
|
5
|
+
User,
|
|
6
|
+
Teacher,
|
|
7
|
+
Student,
|
|
8
|
+
Class,
|
|
9
|
+
School,
|
|
10
|
+
AuthFactor,
|
|
11
|
+
OtpBypassToken,
|
|
12
|
+
} from "./models"
|
|
13
|
+
import {
|
|
14
|
+
unicodeAlphanumericString,
|
|
15
|
+
uppercaseAsciiAlphanumericString,
|
|
16
|
+
lowercaseAsciiAlphanumericString,
|
|
17
|
+
numericId,
|
|
18
|
+
} from "../utils/schema"
|
|
19
|
+
import { type Schemas } from "../utils/api"
|
|
20
|
+
|
|
21
|
+
// NOTE: do not use .required() here.
|
|
22
|
+
const id = {
|
|
23
|
+
user: numericId(),
|
|
24
|
+
teacher: numericId(),
|
|
25
|
+
student: numericId(),
|
|
26
|
+
school: numericId(),
|
|
27
|
+
klass: uppercaseAsciiAlphanumericString().length(5),
|
|
28
|
+
authFactor: numericId(),
|
|
29
|
+
otpBypassToken: numericId(),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const _userTeacher: Omit<Schemas<Teacher>, "user"> = {
|
|
33
|
+
id: id.teacher.required(),
|
|
34
|
+
school: id.school,
|
|
35
|
+
is_admin: yup.bool().required(),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const _userStudent: Omit<Schemas<Student>, "user"> = {
|
|
39
|
+
id: id.student.required(),
|
|
40
|
+
school: id.school.required(),
|
|
41
|
+
klass: id.klass.required(),
|
|
42
|
+
auto_gen_password: yup.string().required(),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const user: Schemas<User> = {
|
|
46
|
+
id: id.user.required(),
|
|
47
|
+
requesting_to_join_class: id.klass,
|
|
48
|
+
first_name: unicodeAlphanumericString({
|
|
49
|
+
spaces: true,
|
|
50
|
+
specialChars: "-'",
|
|
51
|
+
})
|
|
52
|
+
.required()
|
|
53
|
+
.max(150),
|
|
54
|
+
last_name: unicodeAlphanumericString({
|
|
55
|
+
spaces: true,
|
|
56
|
+
specialChars: "-'",
|
|
57
|
+
}).max(150),
|
|
58
|
+
last_login: yup.date(),
|
|
59
|
+
email: yup.string().email(),
|
|
60
|
+
password: yup.string().required(),
|
|
61
|
+
is_staff: yup.bool().required(),
|
|
62
|
+
is_active: yup.bool().required(),
|
|
63
|
+
date_joined: yup.date().required(),
|
|
64
|
+
teacher: yup.object(_userTeacher).optional(),
|
|
65
|
+
student: yup.object(_userStudent).optional(),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const teacher: Schemas<Teacher> = {
|
|
69
|
+
..._userTeacher,
|
|
70
|
+
user: id.user.required(),
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const student: Schemas<Student> = {
|
|
74
|
+
..._userStudent,
|
|
75
|
+
user: id.user.required(),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const school: Schemas<School> = {
|
|
79
|
+
id: id.school.required(),
|
|
80
|
+
name: unicodeAlphanumericString({
|
|
81
|
+
spaces: true,
|
|
82
|
+
specialChars: "'.",
|
|
83
|
+
})
|
|
84
|
+
.required()
|
|
85
|
+
.max(200),
|
|
86
|
+
country: yup.string().oneOf(COUNTRY_ISO_CODES),
|
|
87
|
+
uk_county: yup.string().oneOf(UK_COUNTIES),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const klass: Schemas<Class> = {
|
|
91
|
+
id: id.klass.required(),
|
|
92
|
+
teacher: id.teacher.required(),
|
|
93
|
+
school: id.school.required(),
|
|
94
|
+
name: unicodeAlphanumericString({
|
|
95
|
+
spaces: true,
|
|
96
|
+
specialChars: "-_",
|
|
97
|
+
})
|
|
98
|
+
.required()
|
|
99
|
+
.max(200),
|
|
100
|
+
read_classmates_data: yup.bool().required(),
|
|
101
|
+
receive_requests_until: yup.date(),
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const authFactor: Schemas<AuthFactor> = {
|
|
105
|
+
id: id.authFactor.required(),
|
|
106
|
+
user: id.user.required(),
|
|
107
|
+
type: yup
|
|
108
|
+
.string()
|
|
109
|
+
.oneOf(["otp"] as const)
|
|
110
|
+
.required(),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const otpBypassToken: Schemas<OtpBypassToken> = {
|
|
114
|
+
id: id.otpBypassToken.required(),
|
|
115
|
+
user: id.user.required(),
|
|
116
|
+
token: lowercaseAsciiAlphanumericString().required().length(8),
|
|
117
|
+
}
|
|
@@ -3,7 +3,7 @@ import { InputAdornment } from "@mui/material"
|
|
|
3
3
|
import type { FC } from "react"
|
|
4
4
|
|
|
5
5
|
import TextField, { type TextFieldProps } from "./TextField"
|
|
6
|
-
import {
|
|
6
|
+
import { schemas } from "../../api"
|
|
7
7
|
|
|
8
8
|
export type FirstNameFieldProps = Omit<
|
|
9
9
|
TextFieldProps,
|
|
@@ -20,7 +20,7 @@ const FirstNameField: FC<FirstNameFieldProps> = ({
|
|
|
20
20
|
}) => {
|
|
21
21
|
return (
|
|
22
22
|
<TextField
|
|
23
|
-
schema={
|
|
23
|
+
schema={schemas.user.first_name}
|
|
24
24
|
name={name}
|
|
25
25
|
label={label}
|
|
26
26
|
placeholder={placeholder}
|
|
@@ -4,13 +4,7 @@ import {
|
|
|
4
4
|
} from "@mui/material"
|
|
5
5
|
import { Field, type FieldConfig, type FieldProps } from "formik"
|
|
6
6
|
import { type FC, useState, useEffect } from "react"
|
|
7
|
-
import {
|
|
8
|
-
type ArraySchema,
|
|
9
|
-
type StringSchema,
|
|
10
|
-
type ValidateOptions,
|
|
11
|
-
array as YupArray,
|
|
12
|
-
type Schema,
|
|
13
|
-
} from "yup"
|
|
7
|
+
import { type StringSchema, type ValidateOptions, array as YupArray } from "yup"
|
|
14
8
|
|
|
15
9
|
import { schemaToFieldValidator } from "../../utils/form"
|
|
16
10
|
import { getNestedProperty } from "../../utils/general"
|
|
@@ -52,18 +46,40 @@ const TextField: FC<TextFieldProps> = ({
|
|
|
52
46
|
|
|
53
47
|
const dotPath = name.split(".")
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
function buildSchema() {
|
|
50
|
+
// Build a schema for a single string.
|
|
51
|
+
let stringSchema = schema
|
|
52
|
+
// 1: Validate string is required.
|
|
53
|
+
stringSchema = required ? stringSchema.required() : stringSchema.optional()
|
|
54
|
+
// 2: Validate string is dirty.
|
|
55
|
+
if (dirty && !split)
|
|
56
|
+
stringSchema = stringSchema.notOneOf(
|
|
57
|
+
[initialValue as string],
|
|
58
|
+
"cannot be initial value",
|
|
59
|
+
)
|
|
60
|
+
// Return a schema for a single string.
|
|
61
|
+
if (!split) return stringSchema
|
|
62
|
+
|
|
63
|
+
// Build a schema for an array of strings.
|
|
64
|
+
let arraySchema = YupArray().of(stringSchema)
|
|
65
|
+
// 1: Validate array has min one string.
|
|
66
|
+
arraySchema = required
|
|
67
|
+
? arraySchema.required().min(1)
|
|
68
|
+
: arraySchema.optional()
|
|
69
|
+
// 2: Validate array has unique strings.
|
|
70
|
+
if (unique || uniqueCaseInsensitive)
|
|
71
|
+
arraySchema = arraySchema.test({
|
|
60
72
|
message: "cannot have duplicates",
|
|
61
73
|
test: values => {
|
|
62
|
-
if (
|
|
74
|
+
if (
|
|
75
|
+
Array.isArray(values) &&
|
|
76
|
+
values.length >= 2 &&
|
|
77
|
+
values.every(value => typeof value === "string")
|
|
78
|
+
) {
|
|
63
79
|
return (
|
|
64
80
|
new Set(
|
|
65
|
-
uniqueCaseInsensitive
|
|
66
|
-
? values.map(value => value.toLowerCase())
|
|
81
|
+
uniqueCaseInsensitive
|
|
82
|
+
? values.map(value => (value as string).toLowerCase())
|
|
67
83
|
: values,
|
|
68
84
|
).size === values.length
|
|
69
85
|
)
|
|
@@ -72,19 +88,20 @@ const TextField: FC<TextFieldProps> = ({
|
|
|
72
88
|
return true
|
|
73
89
|
},
|
|
74
90
|
})
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
// 3: Validate array is dirty.
|
|
92
|
+
if (dirty)
|
|
93
|
+
arraySchema = arraySchema.notOneOf(
|
|
94
|
+
[initialValue as string[]],
|
|
95
|
+
"cannot be initial value",
|
|
96
|
+
)
|
|
97
|
+
// Return a schema for an array of strings.
|
|
98
|
+
return arraySchema
|
|
80
99
|
}
|
|
81
|
-
if (dirty)
|
|
82
|
-
_schema = _schema.notOneOf([initialValue], "cannot be initial value")
|
|
83
100
|
|
|
84
101
|
const fieldConfig: FieldConfig = {
|
|
85
102
|
name,
|
|
86
103
|
type,
|
|
87
|
-
validate: schemaToFieldValidator(
|
|
104
|
+
validate: schemaToFieldValidator(buildSchema(), validateOptions),
|
|
88
105
|
}
|
|
89
106
|
|
|
90
107
|
const _Field: FC<FieldProps> = ({ form }) => {
|
package/src/settings/index.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// Shorthand to access environment variables.
|
|
2
|
-
export default import.meta.env
|
|
2
|
+
export default Object.defineProperty(import.meta.env, "vite", {
|
|
3
|
+
writable: false,
|
|
4
|
+
value: new Proxy(import.meta.env, {
|
|
5
|
+
get: (target, name: string) => target[`VITE_${name}`],
|
|
6
|
+
}),
|
|
7
|
+
}) as ImportMetaEnv & { vite: Record<string, string> }
|
|
3
8
|
|
|
4
9
|
export * from "./custom"
|
|
5
10
|
export * from "./vite"
|
package/src/utils/api.tsx
CHANGED
|
@@ -7,8 +7,9 @@ import type {
|
|
|
7
7
|
} from "@reduxjs/toolkit/query/react"
|
|
8
8
|
import { type ReactNode } from "react"
|
|
9
9
|
|
|
10
|
-
import SyncError from "../components/SyncError"
|
|
11
10
|
import { type Optional, type Required, getNestedProperty } from "./general"
|
|
11
|
+
import { type SchemaMap } from "./schema"
|
|
12
|
+
import SyncError from "../components/SyncError"
|
|
12
13
|
|
|
13
14
|
// -----------------------------------------------------------------------------
|
|
14
15
|
// Model Types
|
|
@@ -33,6 +34,10 @@ export type Model<Id extends ModelId, MFields extends Fields = Fields> = {
|
|
|
33
34
|
id: Id
|
|
34
35
|
} & Omit<MFields, "id">
|
|
35
36
|
|
|
37
|
+
export type Schemas<M extends Model<any>> = {
|
|
38
|
+
[K in keyof M]-?: SchemaMap<M[K]>
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
export type Result<
|
|
37
42
|
M extends Model<any>,
|
|
38
43
|
MFields extends keyof Omit<M, "id"> = never,
|
package/src/utils/general.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export type Required<T, K extends keyof T> = { [P in K]-?: T[P] }
|
|
2
2
|
export type Optional<T, K extends keyof T> = Partial<Pick<T, K>>
|
|
3
|
+
export type OptionalPropertyNames<T> = {
|
|
4
|
+
[K in keyof T]-?: {} extends { [P in K]: T[K] } ? K : never
|
|
5
|
+
}[keyof T]
|
|
6
|
+
export type IsOptional<T, K extends keyof T> =
|
|
7
|
+
K extends OptionalPropertyNames<T> ? true : false
|
|
3
8
|
|
|
4
9
|
export function openInNewTab(url: string, target = "_blank"): void {
|
|
5
10
|
window.open(url, target)
|
package/src/utils/schema.ts
CHANGED
|
@@ -9,6 +9,13 @@ import {
|
|
|
9
9
|
type Schema,
|
|
10
10
|
type TypeFromShape,
|
|
11
11
|
type ValidateOptions,
|
|
12
|
+
type StringSchema,
|
|
13
|
+
type NumberSchema,
|
|
14
|
+
type Flags,
|
|
15
|
+
type BooleanSchema,
|
|
16
|
+
type DateSchema,
|
|
17
|
+
string as YupString,
|
|
18
|
+
number as YupNumber,
|
|
12
19
|
} from "yup"
|
|
13
20
|
|
|
14
21
|
export type _<T> = T extends {}
|
|
@@ -30,6 +37,209 @@ export type ObjectSchemaFromShape<Shape extends ObjectShape> = ObjectSchema<
|
|
|
30
37
|
""
|
|
31
38
|
>
|
|
32
39
|
|
|
40
|
+
export type SchemaMap<
|
|
41
|
+
TType,
|
|
42
|
+
TContext = AnyObject,
|
|
43
|
+
TDefault = any,
|
|
44
|
+
TFlags extends Flags = "",
|
|
45
|
+
> =
|
|
46
|
+
NonNullable<TType> extends string
|
|
47
|
+
? StringSchema<
|
|
48
|
+
// @ts-expect-error type is fine
|
|
49
|
+
TType extends undefined ? TType | undefined : TType,
|
|
50
|
+
TContext,
|
|
51
|
+
TDefault,
|
|
52
|
+
TFlags
|
|
53
|
+
>
|
|
54
|
+
: NonNullable<TType> extends number
|
|
55
|
+
? NumberSchema<
|
|
56
|
+
// @ts-expect-error type is fine
|
|
57
|
+
TType extends undefined ? TType | undefined : TType,
|
|
58
|
+
TContext,
|
|
59
|
+
TDefault,
|
|
60
|
+
TFlags
|
|
61
|
+
>
|
|
62
|
+
: NonNullable<TType> extends boolean
|
|
63
|
+
? BooleanSchema<
|
|
64
|
+
// @ts-expect-error type is fine
|
|
65
|
+
TType extends undefined ? TType | undefined : TType,
|
|
66
|
+
TContext,
|
|
67
|
+
TDefault,
|
|
68
|
+
TFlags
|
|
69
|
+
>
|
|
70
|
+
: NonNullable<TType> extends Date
|
|
71
|
+
? DateSchema<
|
|
72
|
+
// @ts-expect-error type is fine
|
|
73
|
+
TType extends undefined ? TType | undefined : TType,
|
|
74
|
+
TContext,
|
|
75
|
+
TDefault,
|
|
76
|
+
TFlags
|
|
77
|
+
>
|
|
78
|
+
: NonNullable<TType> extends object
|
|
79
|
+
? ObjectSchema<
|
|
80
|
+
// @ts-expect-error type is fine
|
|
81
|
+
TType extends undefined ? TType | undefined : TType,
|
|
82
|
+
TContext,
|
|
83
|
+
TDefault,
|
|
84
|
+
TFlags
|
|
85
|
+
>
|
|
86
|
+
: Schema<TType, TContext, TDefault, TFlags>
|
|
87
|
+
|
|
88
|
+
export function numericId(schema: NumberSchema = YupNumber()) {
|
|
89
|
+
return schema.min(1)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// -----------------------------------------------------------------------------
|
|
93
|
+
// Limited Character Sets
|
|
94
|
+
// -----------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
export type MatchesCharSetOptions = Partial<{
|
|
97
|
+
schema: StringSchema
|
|
98
|
+
flags: string
|
|
99
|
+
}>
|
|
100
|
+
|
|
101
|
+
export function matchesCharSet(
|
|
102
|
+
charSet: string,
|
|
103
|
+
message: string,
|
|
104
|
+
options: MatchesCharSetOptions = {},
|
|
105
|
+
) {
|
|
106
|
+
const { schema = YupString(), flags } = options
|
|
107
|
+
|
|
108
|
+
return schema.matches(new RegExp(`^[${charSet}]*$`, flags), message)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type BuildCharSetOptions = MatchesCharSetOptions &
|
|
112
|
+
Partial<{ spaces: boolean; specialChars: string }>
|
|
113
|
+
|
|
114
|
+
export function buildCharSet(
|
|
115
|
+
charSet: string,
|
|
116
|
+
description: string,
|
|
117
|
+
options: BuildCharSetOptions = {},
|
|
118
|
+
) {
|
|
119
|
+
const { spaces = false, specialChars, ...matchesCharSetOptions } = options
|
|
120
|
+
|
|
121
|
+
let message = `can only contain: ${description}`
|
|
122
|
+
|
|
123
|
+
if (spaces) {
|
|
124
|
+
charSet += " "
|
|
125
|
+
message += ", spaces"
|
|
126
|
+
}
|
|
127
|
+
if (specialChars) {
|
|
128
|
+
charSet += specialChars
|
|
129
|
+
message += `, special characters (${specialChars})`
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return matchesCharSet(charSet, message, matchesCharSetOptions)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function buildUnicodeCharSet(
|
|
136
|
+
charSet: string,
|
|
137
|
+
description: string,
|
|
138
|
+
options: BuildCharSetOptions = {},
|
|
139
|
+
) {
|
|
140
|
+
let { flags = "u", ...otherOptions } = options
|
|
141
|
+
|
|
142
|
+
if (!flags.includes("u")) flags += "u"
|
|
143
|
+
|
|
144
|
+
return buildCharSet(charSet, description, { flags, ...otherOptions })
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function asciiAlphaString(options?: BuildCharSetOptions) {
|
|
148
|
+
return buildCharSet("a-zA-Z", "ASCII alpha characters (a-z, A-Z)", options)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function lowercaseAsciiAlphaString(options?: BuildCharSetOptions) {
|
|
152
|
+
return buildCharSet("a-z", "lowercase ASCII alpha characters (a-z)", options)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function uppercaseAsciiAlphaString(options?: BuildCharSetOptions) {
|
|
156
|
+
return buildCharSet("A-Z", "uppercase ASCII alpha characters (A-Z)", options)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function asciiNumericString(options?: BuildCharSetOptions) {
|
|
160
|
+
return buildCharSet("0-9", "ASCII numbers (0-9)", options)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function asciiAlphanumericString(options?: BuildCharSetOptions) {
|
|
164
|
+
return buildCharSet(
|
|
165
|
+
"a-zA-Z0-9",
|
|
166
|
+
"ASCII alphanumeric characters (a-z, A-Z, 0-9)",
|
|
167
|
+
options,
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function lowercaseAsciiAlphanumericString(
|
|
172
|
+
options?: BuildCharSetOptions,
|
|
173
|
+
) {
|
|
174
|
+
return buildCharSet(
|
|
175
|
+
"a-z0-9",
|
|
176
|
+
"lowercase ASCII alphanumeric characters (a-z, 0-9)",
|
|
177
|
+
options,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function uppercaseAsciiAlphanumericString(
|
|
182
|
+
options?: BuildCharSetOptions,
|
|
183
|
+
) {
|
|
184
|
+
return buildCharSet(
|
|
185
|
+
"A-Z0-9",
|
|
186
|
+
"uppercase ASCII alphanumeric characters (A-Z, 0-9)",
|
|
187
|
+
options,
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function unicodeAlphaString(options?: BuildCharSetOptions) {
|
|
192
|
+
return buildUnicodeCharSet("\\p{L}", "unicode alpha characters", options)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function lowercaseUnicodeAlphaString(options?: BuildCharSetOptions) {
|
|
196
|
+
return buildUnicodeCharSet(
|
|
197
|
+
"\\p{Ll}",
|
|
198
|
+
"lowercase unicode alpha characters",
|
|
199
|
+
options,
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function uppercaseUnicodeAlphaString(options?: BuildCharSetOptions) {
|
|
204
|
+
return buildUnicodeCharSet(
|
|
205
|
+
"\\p{Lu}",
|
|
206
|
+
"uppercase unicode alpha characters",
|
|
207
|
+
options,
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function unicodeNumericString(options?: BuildCharSetOptions) {
|
|
212
|
+
return buildUnicodeCharSet("\\p{N}", "unicode numbers", options)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function unicodeAlphanumericString(options?: BuildCharSetOptions) {
|
|
216
|
+
return buildUnicodeCharSet(
|
|
217
|
+
"\\p{L}\\p{N}",
|
|
218
|
+
"unicode alphanumeric characters",
|
|
219
|
+
options,
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function lowercaseUnicodeAlphanumericString(
|
|
224
|
+
options?: BuildCharSetOptions,
|
|
225
|
+
) {
|
|
226
|
+
return buildUnicodeCharSet(
|
|
227
|
+
"\\p{Ll}\\p{N}",
|
|
228
|
+
"lowercase unicode alphanumeric characters",
|
|
229
|
+
options,
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function uppercaseUnicodeAlphanumericString(
|
|
234
|
+
options?: BuildCharSetOptions,
|
|
235
|
+
) {
|
|
236
|
+
return buildUnicodeCharSet(
|
|
237
|
+
"\\p{Lu}\\p{N}",
|
|
238
|
+
"uppercase unicode alphanumeric characters",
|
|
239
|
+
options,
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
33
243
|
// -----------------------------------------------------------------------------
|
|
34
244
|
// Try Validate Sync
|
|
35
245
|
// -----------------------------------------------------------------------------
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Vite configuration for all frontend services.
|
|
3
|
+
*
|
|
4
|
+
* Vite: https://vitest.dev/config/
|
|
5
|
+
* Vitest: https://vitest.dev/config/
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This doesn't belong in codeforlife-workspace/configs/frontend as it's
|
|
8
|
+
* still and a script which needs to be included in a Node.js runtime.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { defineConfig } from "vitest/config"
|
|
12
|
+
import react from "@vitejs/plugin-react"
|
|
13
|
+
|
|
14
|
+
export default defineConfig({
|
|
15
|
+
plugins: [react()],
|
|
16
|
+
resolve: {
|
|
17
|
+
alias: {
|
|
18
|
+
codeforlife: "codeforlife/src",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
envDir: "env",
|
|
22
|
+
server: {
|
|
23
|
+
open: true,
|
|
24
|
+
host: true,
|
|
25
|
+
},
|
|
26
|
+
test: {
|
|
27
|
+
globals: true,
|
|
28
|
+
environment: "jsdom",
|
|
29
|
+
setupFiles: "src/setupTests",
|
|
30
|
+
mockReset: true,
|
|
31
|
+
coverage: {
|
|
32
|
+
enabled: true,
|
|
33
|
+
provider: "istanbul",
|
|
34
|
+
reporter: ["html", "cobertura"],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
optimizeDeps: {
|
|
38
|
+
// TODO: investigate which of these are needed
|
|
39
|
+
include: [
|
|
40
|
+
"@mui/x-date-pickers",
|
|
41
|
+
"@mui/x-date-pickers/AdapterDayjs",
|
|
42
|
+
"dayjs",
|
|
43
|
+
"dayjs/locale/en-gb",
|
|
44
|
+
"@mui/icons-material",
|
|
45
|
+
"yup",
|
|
46
|
+
"formik",
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
})
|
package/vite.config.ts
CHANGED
|
@@ -2,6 +2,8 @@ import react from "@vitejs/plugin-react"
|
|
|
2
2
|
import { defineConfig } from "vitest/config"
|
|
3
3
|
|
|
4
4
|
// https://vitejs.dev/config/
|
|
5
|
+
// TODO: https://vite.dev/guide/build.html#library-mode
|
|
6
|
+
// TODO: import common configs from src/vite.config.ts
|
|
5
7
|
export default defineConfig({
|
|
6
8
|
plugins: [react()],
|
|
7
9
|
server: {
|
package/codecov.yml
DELETED
package/src/schemas/user.ts
DELETED