Implement signup page using jsonschema form

This commit is contained in:
Jan Tuomi
2019-11-09 23:52:01 +02:00
parent 4d891462fe
commit 916a834f13
5 changed files with 242 additions and 77 deletions
+37 -17
View File
@@ -3041,6 +3041,7 @@
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"dev": true,
"requires": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
@@ -11669,8 +11670,7 @@
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"lodash-id": {
"version": "0.14.0",
@@ -11708,11 +11708,6 @@
"integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=",
"dev": true
},
"lodash.topath": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz",
"integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak="
},
"lodash.unescape": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
@@ -15001,27 +14996,51 @@
"integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA=="
},
"react-jsonschema-form": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.5.0.tgz",
"integrity": "sha512-SsldN37+5dDLRAGmwNO6cKb9AH2zhgkhIST9+UVaBqQ/KONl4jj1KFerXiEySGGDFBe81CjMGapZV5Ydrdp4pg==",
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.8.0.tgz",
"integrity": "sha512-3rZZ1tCG+vtXRXEdX751t5L1c1TUGk1pvSY/4LzBrUo5FlwulnwJpkosE4BqwSRxvfMckP8YoHFQV4KjzaHGgQ==",
"requires": {
"@babel/runtime-corejs2": "^7.4.5",
"ajv": "^6.7.0",
"babel-runtime": "^6.26.0",
"core-js": "^2.5.7",
"lodash.topath": "^4.5.2",
"prop-types": "^15.5.8"
"lodash": "^4.17.15",
"prop-types": "^15.5.8",
"react-is": "^16.8.4",
"react-lifecycles-compat": "^3.0.4",
"shortid": "^2.2.14"
},
"dependencies": {
"@babel/runtime-corejs2": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.7.2.tgz",
"integrity": "sha512-GfVnHchOBvIMsweQ13l4jd9lT4brkevnavnVOej5g2y7PpTRY+R4pcQlCjWMZoUla5rMLFzaS/Ll2s59cB1TqQ==",
"requires": {
"core-js": "^2.6.5",
"regenerator-runtime": "^0.13.2"
},
"dependencies": {
"core-js": {
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz",
"integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA=="
}
}
},
"ajv": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
}
}
},
@@ -15310,7 +15329,8 @@
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
"dev": true
},
"regenerator-transform": {
"version": "0.10.1",
+2 -2
View File
@@ -120,11 +120,11 @@
"query-string": "6.5.0",
"react-beautiful-dnd": "10.1.1",
"react-helmet": "5.2.1",
"react-jsonschema-form": "1.5.0",
"react-jsonschema-form": "^1.8.0",
"react-router": "4.3.1",
"react-router-dom": "4.3.1",
"react-router-hash-link": "1.2.1",
"shortid": "2.2.14"
},
"postcss": {}
}
}
+26
View File
@@ -0,0 +1,26 @@
import axios from "axios";
import { getAuthHeader } from "../auth";
import * as qs from "query-string";
const url = `${process.env.API_URL}/signup/`;
import { Question } from "../components/SignupQuestionsWidget";
export interface Signup {
id?: number;
time: string;
signupForm_id: number;
answer: string;
}
export async function createSignup(data: Signup): Promise<Signup> {
try {
const resp = await axios.post(url, data, {
headers: {
"Authorization": getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
+24
View File
@@ -1,6 +1,26 @@
.sign-up-page {
display: flex;
fieldset {
border: none;
padding: 0;
margin: 1rem 0;
.form-group > label {
margin: 0.5rem 0;
font-weight: 600;
display: block;
}
input[type="text"] {
display: block;
}
input[type="radio"], input[type="checkbox"] {
margin-right: 4px;
}
}
.question {
display: flex;
flex-flow: column nowrap;
@@ -9,6 +29,10 @@
input {
margin-right: 1rem;
}
h3 {
margin: 0.5rem 0;
}
}
}
}
+153 -58
View File
@@ -1,7 +1,9 @@
import * as React from "react";
import Helmet from "react-helmet";
import Form from "react-jsonschema-form";
import "./SignUpPage.scss";
import { getForm, SignupForm } from "../../models/SignupForm";
import { createSignup, Signup } from "../../models/Signup";
import PageSection from "../../components/PageSection";
import { ColorEnum } from "../../components/ColorDiv/ColorDiv";
import { Question, optionTypes } from "../../components/SignupQuestionsWidget";
@@ -16,36 +18,22 @@ export interface SignUpPageProps {
export interface SignUpPageState {
signUpForm: SignupForm | null;
formData: any;
statusMessage: string;
error: string;
}
class SignUpPage extends React.Component<SignUpPageProps, SignUpPageState> {
constructor(props) {
super(props);
const { staticContext } = props;
this.state = {
signUpForm: null,
formData: {},
statusMessage: "",
error: "",
};
if (staticContext) {
/* The static context is an object that manages promises when
rendering on the server. If staticContext exists, that means
we have to store all promises in it. Otherwise, operate
normally. See server/index.ts. */
if (staticContext.resolutions.getSignUpForm) {
const signUpForm = staticContext.resolutions.getSignUpForm as SignupForm;
this.state = {
signUpForm,
};
} else {
this.state = {
signUpForm: null,
};
const promiseSignUpForm = this.fetchSignUp();
staticContext.promises.getSignUpForm = promiseSignUpForm;
}
} else {
this.state = {
signUpForm: null,
};
this.fetchSignUp();
}
this.fetchSignUp();
}
fetchSignUp = (): Promise<SignupForm> => {
@@ -60,56 +48,163 @@ class SignUpPage extends React.Component<SignUpPageProps, SignUpPageState> {
return formPromise;
}
renderAnswerInput = (question: Question) => {
if (!optionTypes.includes(question.type)) {
throw new Error("Unknown question type");
questionToSchemaProp = (question: Question): {} => {
let obj;
if (question.type === "text") {
obj = {
type: "string",
title: question.name,
default: "",
};
}
else if (question.type === "radiobutton") {
obj = {
type: "string",
title: question.name,
enum: question.options,
};
}
else if (question.type === "checkbox") {
obj = {
type: "array",
title: question.name,
items: {
type: "string",
enum: question.options,
},
uniqueItems: true,
};
}
else {
throw new Error(`No mapping to schema prop for question type ${question.type}`);
}
if (question.type === "text") {
return <input type="text" name={question.id} />;
return {
[question.id]: obj,
}
if (question.type === "radiobutton") {
const { options } = question;
return options.map((opt, index) => (
<span key={index}>
<input type="radio" name={`${question.id}`} />
{opt}
</span>
));
}
questionToUISchemaProp = (question: Question): {} => {
let obj = {};
if (question.type == "checkbox") {
obj = {
"ui:widget": "checkboxes",
};
}
if (question.type === "checkbox") {
const { options } = question;
return options.map((opt, index) => {
const optSlug = opt.replace(/\s/g, "_").toLocaleLowerCase();
return (
<span key={index}>
<input type="checkbox" name={`${question.id}-${optSlug}`} value={opt} /> {opt}<br />
</span>
)
else if (question.type == "radiobutton") {
obj = {
"ui:widget": "radio",
}
}
// else {
// throw new Error(`No mapping to UI schema prop for question type ${question.type}`);
// }
return {
[question.id]: obj,
};
}
buildSchema = () => {
const { error, formData, signUpForm } = this.state;
const questions: Question[] = JSON.parse(signUpForm.questions);
const schemaPropsArray = questions.map(this.questionToSchemaProp);
let schemaProps = {};
schemaPropsArray.forEach((schemaProp) => {
schemaProps = {
...schemaProps,
...schemaProp,
};
});
const schema = {
title: signUpForm.id ? signUpForm.title : "Loading...",
type: "object",
required: [],
properties: schemaProps,
};
return schema;
}
buildUISchema = () => {
const { error, formData, signUpForm } = this.state;
const questions: Question[] = JSON.parse(signUpForm.questions);
const uiSchemaPropsArray = questions.map(this.questionToUISchemaProp);
let uiSchemaProps = {};
uiSchemaPropsArray.forEach((uiSchemaProp) => {
uiSchemaProps = {
...uiSchemaProps,
...uiSchemaProp,
};
});
const uiSchema = {
...uiSchemaProps,
};
return uiSchema;
}
onSubmit = async (data) => {
const { signUpForm } = this.state;
console.log("submitted, data:");
console.log(data);
try {
const payload: Signup = {
time: new Date().toISOString(),
signupForm_id: signUpForm.id,
answer: JSON.stringify(data.formData),
};
// payload.questions = JSON.stringify(payload.questions);
if (payload.id === undefined) {
const resp = await createSignup(payload);
this.setState({
formData: resp,
statusMessage: "Sign-up submitted successfully",
});
}
else {
debugger;
// const resp = await updateSignup(payload);
// this.setState({
// formData: resp,
// statusMessage: "Sign-up submission updated successfully.",
// });
}
} catch (err) {
this.setState({
error: String(err),
});
}
}
renderQuestion = (question: Question) => {
return (
<div className="question">
{question.name}
{this.renderAnswerInput(question)}
</div>
);
onChange = (data) => {
console.log(data);
}
renderForm() {
const { signUpForm } = this.state;
const questions: Question[] = JSON.parse(signUpForm.questions);
const content = questions.map(this.renderQuestion);
const { signUpForm, formData } = this.state;
// const questions: Question[] = JSON.parse(signUpForm.questions);
// const content = questions.map(this.renderQuestion);
const schema = this.buildSchema();
const uiSchema = this.buildUISchema();
return (
<div>
<h1>Title: {signUpForm.title}</h1>
<div>
{/* <form onSubmit={this.handleSubmit}>
{content}
</div>
<button type="submit">Submit</button>
</form> */}
<Form
schema={schema}
uiSchema={uiSchema}
formData={formData}
idPrefix="rjsf"
onChange={this.onChange}
onSubmit={this.onSubmit}
/>
</div>
);
}