diff --git a/src/models/SignupForm.ts b/src/models/SignupForm.ts index 455453a..adedde1 100644 --- a/src/models/SignupForm.ts +++ b/src/models/SignupForm.ts @@ -11,6 +11,7 @@ export interface SignupForm { questions: Question[]; signups: string[]; quota: number; + schema: object; } export async function getForms(): Promise { diff --git a/src/pages/admin/SignupCreatePage.tsx b/src/pages/admin/SignupCreatePage.tsx index ce31ac8..99c5b91 100644 --- a/src/pages/admin/SignupCreatePage.tsx +++ b/src/pages/admin/SignupCreatePage.tsx @@ -6,6 +6,7 @@ import Form from "react-jsonschema-form"; import { createForm, getForm, updateForm, SignupForm } from "@models/SignupForm"; import DatetimeWidget from "@components/DatetimeWidget"; import SignupQuestionsWidget from "@components/SignupQuestionsWidget"; +import { buildQuestionsSchema } from "@views/SignUpPage/FormUtils"; const widgets = { datetime: DatetimeWidget, @@ -59,9 +60,11 @@ class SignupCreatePage extends React.Component { try { + const questions = JSON.parse(data.formData.questions); const payload: SignupForm = { ...data.formData, - questions: JSON.parse(data.formData.questions) + questions, + schema: buildQuestionsSchema(questions) } if (payload.id === undefined) { diff --git a/src/utils/regexes.ts b/src/utils/regexes.ts new file mode 100644 index 0000000..726ddc0 --- /dev/null +++ b/src/utils/regexes.ts @@ -0,0 +1,4 @@ + +// HTML 5 email regex +export const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/ +// export const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ diff --git a/src/views/SignUpPage/FormUtils.tsx b/src/views/SignUpPage/FormUtils.tsx index 6f9da21..8acee78 100644 --- a/src/views/SignUpPage/FormUtils.tsx +++ b/src/views/SignUpPage/FormUtils.tsx @@ -1,6 +1,7 @@ import { Question } from "@components/SignupQuestionsWidget"; import { SignupForm } from "@models/SignupForm"; +import { EMAIL_REGEX } from "@utils/regexes"; const questionToSchemaProp = (question: Question): {} => { let obj: any; @@ -51,8 +52,7 @@ const questionToSchemaProp = (question: Question): {} => { type: "number", multipleOf: 1.0, title: `${question.name} (Max: ${question.options[0]})`, - maximum: question.options[0], - exclusiveMaximum: false + maximum: Number(question.options[0]), } } @@ -61,9 +61,8 @@ const questionToSchemaProp = (question: Question): {} => { type: "number", multipleOf: 1.0, title: `${question.name} (${question.options[0]} -- ${question.options[1]})`, - minimum: question.options[0], - maximum: question.options[1], - exclusiveMaximum: false + minimum: Number(question.options[0]), + maximum: Number(question.options[1]), } } @@ -104,6 +103,68 @@ const questionToUISchemaProp = (question: Question): {} => { }; } +const questionToValidationSchema = (question: Question) => { + if (question.type === "text") { + return { + type: "string", + }; + } + + if (question.type == "email") { + // Format is just a "FYI" field, so we also have pattern. + return { + type: "string", + format: "email", + pattern: EMAIL_REGEX.source + } + } + if (question.type == "radiobutton") { + return { + "type": "string", + "pattern": question.options.map(x => `^${x}$`).join("|"), + } + } + if (question.type == "checkbox") { + return { + type: "array", + uniqueItems: true, + maxItems: question.options.length, + items: { + type: "string", + pattern: question.options.map(x => `^${x}$`).join("|"), + } + } + } + // https://json-schema.org/understanding-json-schema/reference/numeric.html + else if (question.type === "integer") { + if (question.options.length === 1) { + return { + type: "number", + multipleOf: 1.0, + title: `${question.name} (Max: ${question.options[0]})`, + maximum: Number(question.options[0]), + } + } + else if (question.options.length === 2) { + return { + type: "number", + multipleOf: 1.0, + title: `${question.name} (${question.options[0]} -- ${question.options[1]})`, + minimum: Number(question.options[0]), + maximum: Number(question.options[1]), + } + } + else { + return { + type: "number", + multipleOf: 1.0, + title: question.name, + } + } + } + throw new Error(`No mapping to schema prop for question type ${question.type}`); +} + export const buildSchema = (signUpForm: SignupForm) => { const {questions} = signUpForm; const schemaPropsArray = questions.map(questionToSchemaProp); @@ -125,6 +186,25 @@ export const buildSchema = (signUpForm: SignupForm) => { return schema; } +export const buildQuestionsSchema = (questions: Question[]) => { + const schemaProps = {}; + const filtered = questions.filter(x => x.type !== "info"); + const ids = filtered.map(q => q.id); + const schemaPropsArray = filtered.map(questionToValidationSchema); + schemaPropsArray.forEach((schemaProp, idx) => { + schemaProps[ids[idx]] = schemaProp; + }); + + const validationSchema = { + type: "object", + required: ids, + minProperties: ids.length, + // maxProperties: ids.length, + properties: schemaProps + } + return validationSchema; +} + export const buildUISchema = (signUpForm: SignupForm) => { const {questions} = signUpForm; const uiSchemaPropsArray = questions.map(questionToUISchemaProp);