Merge branch 'master' into 'production'
Prod deploy: Translated signup questions & preparation for PoTa100 signup See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!84
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
roots: ["<rootDir>/src"],
|
||||
testMatch: ["**/*.test.ts"],
|
||||
transform: {
|
||||
"^.+\\.tsx?$": "ts-jest",
|
||||
},
|
||||
preset: "ts-jest",
|
||||
verbose: true,
|
||||
|
||||
moduleNameMapper: {
|
||||
"^@api/(.*)$": "<rootDir>/src/api/$1",
|
||||
"^@components/(.*)$": "<rootDir>/src/components/$1",
|
||||
"^@hooks/(.*)$": "<rootDir>/src/hooks/$1",
|
||||
"^@models/(.*)$": "<rootDir>/src/models/$1",
|
||||
"^@pages/(.*)$": "<rootDir>/src/pages/$1",
|
||||
"^@theme/(.*)$": "<rootDir>/src/theme/$1",
|
||||
"^@views/(.*)$": "<rootDir>/src/views/$1",
|
||||
"^@utils/(.*)$": "<rootDir>/src/utils/$1",
|
||||
},
|
||||
};
|
||||
Generated
+5009
File diff suppressed because it is too large
Load Diff
@@ -27,12 +27,14 @@
|
||||
"start": "next dev",
|
||||
"start-prod": "next start --port ${SERVER_PORT:=80}",
|
||||
"serve": "next start --port 3000",
|
||||
"test:unit": "jest --coverage",
|
||||
"test": "npm run testcafe",
|
||||
"testcafe": "testcafe --config-file testcafe.json",
|
||||
"build-analyze": "ANALYZE=true npm run build",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/js-cookie": "^2.2.7",
|
||||
"@types/react": "^17.0.19",
|
||||
"@types/react-beautiful-dnd": "^13.1.1",
|
||||
@@ -48,12 +50,14 @@
|
||||
"eslint-config-airbnb-typescript": "^13.0.0",
|
||||
"eslint-config-next": "^11.1.0",
|
||||
"husky": "^7.0.1",
|
||||
"jest": "^27.1.0",
|
||||
"next-sitemap": "^1.6.162",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-standard": "^22.0.0",
|
||||
"stylelint-config-styled-components": "^0.1.1",
|
||||
"testcafe": "^1.15.3",
|
||||
"ts-jest": "^27.0.5",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -4,7 +4,14 @@ import { WidgetProps } from "@rjsf/core";
|
||||
import RadioButton from "./RadioButton";
|
||||
|
||||
type RadioButtonWidgetProps = Omit<WidgetProps, "options"> & {
|
||||
options: any;
|
||||
options: {
|
||||
enumOptions: {
|
||||
value: string;
|
||||
label: string;
|
||||
}[];
|
||||
enumDisabled: string[];
|
||||
inline: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const RadioButtonContainer = styled.div`
|
||||
|
||||
@@ -1,49 +1,72 @@
|
||||
import React from "react";
|
||||
import Checkbox from "@components/Widgets/Checkbox/Checkbox";
|
||||
import { SignupFormQuestion } from "@models/Signup";
|
||||
import { Lang } from "../../../i18n";
|
||||
import {
|
||||
Question, InputProps, optionTypes, SignupQuestionError,
|
||||
InputProps, optionTypes, SignupQuestionError,
|
||||
} from "./common";
|
||||
|
||||
interface OptionsWidgetProps {
|
||||
inputProps: InputProps;
|
||||
onChange: (value: Question[]) => void;
|
||||
onChange: (value: SignupFormQuestion[]) => void;
|
||||
}
|
||||
|
||||
class OptionsWidget extends React.Component<OptionsWidgetProps> {
|
||||
handleListOptionsChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
handleListOptionsChange = (questions: SignupFormQuestion[], index: number, lang: Lang): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
const { onChange } = this.props;
|
||||
const val = event.target.value;
|
||||
const lst = val.split(";").map((p) => p.trimLeft());
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].options = lst;
|
||||
|
||||
if (lang === "fi") {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].options = {
|
||||
...questions[index].options,
|
||||
enumNames_fi: lst,
|
||||
enum: lst,
|
||||
};
|
||||
}
|
||||
if (lang === "en") {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].options = {
|
||||
...questions[index].options,
|
||||
enumNames_en: lst,
|
||||
};
|
||||
}
|
||||
onChange(questions);
|
||||
};
|
||||
|
||||
handleTextOptionsChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
handleInfoTextOptionsChange = (questions: SignupFormQuestion[], index: number, lang: Lang): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
const { onChange } = this.props;
|
||||
const val = event.target.value;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].options = val as unknown as string[]; // TODO: Check type
|
||||
|
||||
if (lang === "fi") {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].description_fi = val;
|
||||
}
|
||||
if (lang === "en") {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].description_en = val;
|
||||
}
|
||||
onChange(questions);
|
||||
};
|
||||
|
||||
handleIntegerOptionsChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
handleIntegerOptionsChange = (questions: SignupFormQuestion[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
const { onChange } = this.props;
|
||||
const val = event.target.value;
|
||||
if (val !== "") {
|
||||
const lst = val.split(";").map((p) => p.trimLeft());
|
||||
// Ignore everything else but the two first values
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].options = lst.splice(0, 2);
|
||||
questions[index].options.enum = lst.splice(0, 2);
|
||||
} else {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].options = [];
|
||||
questions[index].options.enum = [];
|
||||
}
|
||||
|
||||
onChange(questions);
|
||||
};
|
||||
|
||||
handleRequiredChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
handleRequiredChange = (questions: SignupFormQuestion[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
const { onChange } = this.props;
|
||||
const val: boolean = event.target.checked;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
@@ -67,7 +90,7 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
|
||||
render(): JSX.Element {
|
||||
const { inputProps } = this.props;
|
||||
const {
|
||||
type, value, questions, index,
|
||||
value, type, questions, index,
|
||||
} = inputProps;
|
||||
if (!optionTypes.includes(type)) {
|
||||
throw new SignupQuestionError(`Question widget type "${type}" not in types array.`);
|
||||
@@ -82,25 +105,29 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Write something informative"
|
||||
value={questions[index].options}
|
||||
onChange={this.handleTextOptionsChange(questions, index)}
|
||||
placeholder="Write something informative in Finnish"
|
||||
value={questions[index].description_fi}
|
||||
onChange={this.handleInfoTextOptionsChange(questions, index, "fi")}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Write something informative in English"
|
||||
value={questions[index].description_en}
|
||||
onChange={this.handleInfoTextOptionsChange(questions, index, "en")}
|
||||
required
|
||||
/>
|
||||
{this.requiredField()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === "integer") {
|
||||
const lst = value as string[];
|
||||
const joinedValue = lst.join(";");
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Minimum;Maximum"
|
||||
value={joinedValue}
|
||||
value={value.enum.join(";")}
|
||||
onChange={this.handleIntegerOptionsChange(questions, index)}
|
||||
/>
|
||||
{this.requiredField()}
|
||||
@@ -109,15 +136,20 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
|
||||
}
|
||||
|
||||
if (type === "radiobutton") {
|
||||
const lst = value as string[];
|
||||
const joinedValue = lst.join(";");
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Kyllä;ei;ehkä"
|
||||
value={value.enumNames_fi.join(";")}
|
||||
onChange={this.handleListOptionsChange(questions, index, "fi")}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Yes;no;maybe"
|
||||
value={joinedValue}
|
||||
onChange={this.handleListOptionsChange(questions, index)}
|
||||
value={value.enumNames_en.join(";")}
|
||||
onChange={this.handleListOptionsChange(questions, index, "en")}
|
||||
required
|
||||
/>
|
||||
</>
|
||||
@@ -125,15 +157,20 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
|
||||
}
|
||||
|
||||
if (type === "checkbox") {
|
||||
const lst = value as string[];
|
||||
const joinedValue = lst.join(";");
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="A;B;C"
|
||||
value={joinedValue}
|
||||
onChange={this.handleListOptionsChange(questions, index)}
|
||||
placeholder="Yksi;Kaksi;Kolme"
|
||||
value={value.enumNames_fi.join(";")}
|
||||
onChange={this.handleListOptionsChange(questions, index, "fi")}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="One;Two;Three"
|
||||
value={value.enumNames_en.join(";")}
|
||||
onChange={this.handleListOptionsChange(questions, index, "en")}
|
||||
required
|
||||
/>
|
||||
{this.requiredField()}
|
||||
|
||||
@@ -2,7 +2,8 @@ import React, { ReactNode } from "react";
|
||||
import styled from "styled-components";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import colors from "@theme/colors";
|
||||
import { Question, InputProps } from "./common";
|
||||
import { SignupFormQuestion } from "@models/Signup";
|
||||
import { Lang } from "../../../i18n";
|
||||
import OptionsWidget from "./OptionsWidget";
|
||||
import TypeWidget from "./TypeWidget";
|
||||
import QuestionElement from "./Question";
|
||||
@@ -16,26 +17,28 @@ const WidgetRow = styled.div`
|
||||
`;
|
||||
|
||||
interface QuestionListProps {
|
||||
questions: Question[];
|
||||
questions: SignupFormQuestion[];
|
||||
innerRef: React.Ref<HTMLDivElement>;
|
||||
placeholder: ReactNode;
|
||||
onChange: (value: Question[]) => void;
|
||||
onChange: (value: SignupFormQuestion[]) => void;
|
||||
}
|
||||
|
||||
class QuestionList extends React.Component<QuestionListProps> {
|
||||
renderTextWidget = ({ questions, value, index }: InputProps): JSX.Element => (
|
||||
<input type="text" value={value} onChange={this.handleNameInputChange(questions, index)} />
|
||||
);
|
||||
|
||||
handleNameInputChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
handleNameInputChange = (questions: SignupFormQuestion[], index: number, lang: Lang): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
const { onChange } = this.props;
|
||||
const val = event.target.value;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].name = val;
|
||||
if (lang === "fi") {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].title_fi = val;
|
||||
}
|
||||
if (lang === "en") {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].title_en = val;
|
||||
}
|
||||
onChange(questions);
|
||||
};
|
||||
|
||||
handleElementRemove = (questions: Question[], index: number) => (): void => {
|
||||
handleElementRemove = (questions: SignupFormQuestion[], index: number) => (): void => {
|
||||
const { onChange } = this.props;
|
||||
const newQuestions = [...questions];
|
||||
newQuestions.splice(index, 1);
|
||||
@@ -45,11 +48,6 @@ class QuestionList extends React.Component<QuestionListProps> {
|
||||
renderQuestions(): JSX.Element[] {
|
||||
const { questions, onChange } = this.props;
|
||||
return questions.map((q, index) => {
|
||||
const nameWidgetProps = {
|
||||
value: q.name, type: "text", questions, index,
|
||||
};
|
||||
const nameWidget = this.renderTextWidget(nameWidgetProps);
|
||||
|
||||
const dataProps = {
|
||||
value: q.options, type: q.type, questions, index,
|
||||
};
|
||||
@@ -66,7 +64,8 @@ class QuestionList extends React.Component<QuestionListProps> {
|
||||
<QuestionElement
|
||||
onClick={this.handleElementRemove(questions, index)}
|
||||
>
|
||||
{nameWidget}
|
||||
<input type="text" value={q.title_fi} onChange={this.handleNameInputChange(questions, index, "fi")} />
|
||||
<input type="text" value={q.title_en} onChange={this.handleNameInputChange(questions, index, "en")} />
|
||||
{typeSelectWidget}
|
||||
{optionsWidget}
|
||||
</QuestionElement>
|
||||
|
||||
@@ -4,8 +4,8 @@ import shortid from "shortid";
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||
import colors from "@theme/colors";
|
||||
import AddIcon from "@components/AddIcon";
|
||||
import { SignupFormQuestion } from "@models/Signup";
|
||||
import QuestionList from "./QuestionList";
|
||||
import { Question } from "./common";
|
||||
|
||||
const Widget = styled.div`
|
||||
& > button {
|
||||
@@ -40,24 +40,29 @@ interface SignupQuestionsWidgetProps {
|
||||
}
|
||||
|
||||
const SignupQuestionsWidget: React.FC<SignupQuestionsWidgetProps> = ({ value, onFocus, onChange }) => {
|
||||
const onValueChange = (questions: Question[]) => {
|
||||
const onValueChange = (questions: SignupFormQuestion[]) => {
|
||||
const newValue = JSON.stringify(questions);
|
||||
onChange(newValue);
|
||||
};
|
||||
|
||||
const handleNewRowClick = (questions) => () => {
|
||||
const newRow: Question = {
|
||||
const newRow: SignupFormQuestion = {
|
||||
id: shortid.generate(),
|
||||
name: `Question #${questions.length + 1}`,
|
||||
options: [],
|
||||
title_fi: `Kysymys #${questions.length + 1}`,
|
||||
title_en: `Question #${questions.length + 1}`,
|
||||
options: {
|
||||
enum: [],
|
||||
enumNames_fi: [],
|
||||
enumNames_en: [],
|
||||
},
|
||||
type: "text",
|
||||
};
|
||||
const newQuestions: Question[] = questions.concat([newRow]);
|
||||
const newQuestions: SignupFormQuestion[] = questions.concat([newRow]);
|
||||
|
||||
onValueChange(newQuestions);
|
||||
};
|
||||
|
||||
const handleDragEnd = (questions: Question[]) => (result) => {
|
||||
const handleDragEnd = (questions: SignupFormQuestion[]) => (result) => {
|
||||
const srcIndex = result.source.index;
|
||||
const dstIndex = result.destination.index;
|
||||
const srcCopy = { ...questions[srcIndex] };
|
||||
@@ -66,7 +71,7 @@ const SignupQuestionsWidget: React.FC<SignupQuestionsWidgetProps> = ({ value, on
|
||||
|
||||
onValueChange(questions);
|
||||
};
|
||||
const questions = JSON.parse(value) as Question[];
|
||||
const questions: SignupFormQuestion[] = JSON.parse(value);
|
||||
|
||||
return (
|
||||
<Widget>
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
import React from "react";
|
||||
import { Question, InputProps, optionTypes } from "./common";
|
||||
import { SignupFormQuestion } from "@models/Signup";
|
||||
import { InputProps, optionTypes } from "./common";
|
||||
|
||||
interface TypeWidgetProps {
|
||||
inputProps: InputProps;
|
||||
onChange: (value: Question[]) => void;
|
||||
onChange: (value: SignupFormQuestion[]) => void;
|
||||
}
|
||||
|
||||
class TypeWidget extends React.Component<TypeWidgetProps> {
|
||||
handleTypeChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLSelectElement> => (event) => {
|
||||
const { onChange } = this.props;
|
||||
const val = event.target.value as Question["type"];
|
||||
const TypeWidget = ({ onChange, inputProps }: TypeWidgetProps): JSX.Element => {
|
||||
const handleTypeChange = (questions: SignupFormQuestion[], index: number): React.ChangeEventHandler<HTMLSelectElement> => (event) => {
|
||||
const val = event.target.value as SignupFormQuestion["type"];
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].type = val;
|
||||
onChange(questions);
|
||||
};
|
||||
|
||||
render(): JSX.Element {
|
||||
const { inputProps } = this.props;
|
||||
const { type, questions, index } = inputProps;
|
||||
const options = optionTypes.map((t) => (
|
||||
<option key={t} value={t}>{t}</option>
|
||||
));
|
||||
return (
|
||||
<select onChange={this.handleTypeChange(questions, index)} value={type} name="type">
|
||||
{options}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
}
|
||||
const { questions, type, index } = inputProps;
|
||||
const options = optionTypes.map((t) => (
|
||||
<option key={t} value={t}>{t}</option>
|
||||
));
|
||||
return (
|
||||
<select onChange={handleTypeChange(questions, index)} value={type} name="type">
|
||||
{options}
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
export default TypeWidget;
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import type { SignupFormQuestion } from "@models/Signup";
|
||||
|
||||
export interface Question {
|
||||
id: string;
|
||||
name: string;
|
||||
type: OptionTypes;
|
||||
options: string[];
|
||||
enum?: string[];
|
||||
enumNames?: string[];
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export interface InputProps {
|
||||
index: number;
|
||||
value: string | string[];
|
||||
questions: Question[];
|
||||
value: SignupFormQuestion["options"];
|
||||
questions: SignupFormQuestion[];
|
||||
type: string;
|
||||
}
|
||||
|
||||
type OptionTypes =
|
||||
export type OptionTypes =
|
||||
"text" |
|
||||
"info" |
|
||||
"integer" |
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import React, {
|
||||
import fi from "./locales/fi/common.json";
|
||||
import en from "./locales/en/common.json";
|
||||
|
||||
type Lang = "fi" | "en";
|
||||
export type Lang = "fi" | "en";
|
||||
const LOCAL_STORAGE_KEY = "locale";
|
||||
|
||||
type TranslateFunc = (key: string) => string;
|
||||
|
||||
+19
-2
@@ -1,4 +1,4 @@
|
||||
import { Question } from "@components/Widgets/SignupQuestionsWidget/common";
|
||||
import { OptionTypes } from "@components/Widgets/SignupQuestionsWidget/common";
|
||||
|
||||
export interface Signup {
|
||||
id?: number;
|
||||
@@ -6,14 +6,31 @@ export interface Signup {
|
||||
answer: string;
|
||||
}
|
||||
|
||||
// Describes how forms are stored in backend
|
||||
export interface SignupFormQuestion {
|
||||
id: string;
|
||||
title_fi: string;
|
||||
title_en: string;
|
||||
description_fi?: string;
|
||||
description_en?: string;
|
||||
type: OptionTypes;
|
||||
options: {
|
||||
enum: string[];
|
||||
enumNames_fi: string[];
|
||||
enumNames_en: string[];
|
||||
};
|
||||
required?: boolean;
|
||||
}
|
||||
export interface SignupForm {
|
||||
id?: number;
|
||||
title_fi: string;
|
||||
title_en: string;
|
||||
visible: boolean;
|
||||
isOpen: boolean;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
questions: Question[];
|
||||
email_content: string;
|
||||
questions: SignupFormQuestion[];
|
||||
signups: string[];
|
||||
quota: number;
|
||||
schema: {
|
||||
|
||||
@@ -180,11 +180,11 @@ const EventCreatePage: NextPage = () => {
|
||||
useEffect(() => {
|
||||
TagApi.getTags()
|
||||
.then((res) => setTags(res))
|
||||
.catch((err) => setError(err));
|
||||
.catch((err) => setError(err.message));
|
||||
|
||||
SignupApi.getForms(true)
|
||||
.then((res) => setSignupForms(res))
|
||||
.catch((err) => setError(err));
|
||||
.catch((err) => setError(err.message));
|
||||
|
||||
const eventId = id && Number(id);
|
||||
if (eventId !== undefined) {
|
||||
@@ -194,7 +194,7 @@ const EventCreatePage: NextPage = () => {
|
||||
tags: (res.tags).map((inst) => inst.id) as any,
|
||||
signupForm: (res.signupForm).map((inst) => inst.id) as any,
|
||||
}))
|
||||
.catch((err) => setError(err));
|
||||
.catch((err) => setError(err.message));
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
@@ -230,7 +230,7 @@ const EventCreatePage: NextPage = () => {
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error("Uh oh! Something went wrong! Try again later. 😟");
|
||||
setError(err);
|
||||
setError(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ const FeedCreatePage: NextPage = () => {
|
||||
useEffect(() => {
|
||||
TagApi.getTags()
|
||||
.then((res) => setTags(res))
|
||||
.catch((err) => setError(err));
|
||||
.catch((err) => setError(err.message));
|
||||
|
||||
const feedId = id && Number(id);
|
||||
if (feedId !== undefined) {
|
||||
@@ -155,7 +155,7 @@ const FeedCreatePage: NextPage = () => {
|
||||
...res,
|
||||
tags: (res.tags).map((inst) => inst.id) as any,
|
||||
}))
|
||||
.catch((err) => setError(err));
|
||||
.catch((err) => setError(err.message));
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
@@ -179,7 +179,7 @@ const FeedCreatePage: NextPage = () => {
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error("Uh oh! Something went wrong! Try again later. 😟");
|
||||
setError(err);
|
||||
setError(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ const JobAdCreatePage: NextPage = () => {
|
||||
if (jobId !== undefined) {
|
||||
JobAdApi.getJobAd(jobId, true)
|
||||
.then((res) => setFormData(res))
|
||||
.catch((err) => setError(err));
|
||||
.catch((err) => setError(err.message));
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
@@ -143,7 +143,7 @@ const JobAdCreatePage: NextPage = () => {
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error("Uh oh! Something went wrong! Try again later. 😟");
|
||||
setError(err);
|
||||
setError(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { NextPage } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
|
||||
import { SignupForm } from "@models/Signup";
|
||||
import { SignupForm, SignupFormQuestion } from "@models/Signup";
|
||||
import SignupApi from "@api/signupApi";
|
||||
import DatetimeWidget from "@components/Widgets/DatetimeWidget";
|
||||
import SignupQuestionsWidget from "@components/Widgets/SignupQuestionsWidget/SignupQuestionsWidget";
|
||||
@@ -118,13 +118,13 @@ const SignupCreatePage: NextPage = () => {
|
||||
questions: JSON.stringify(res.questions) as any,
|
||||
});
|
||||
})
|
||||
.catch((err) => setError(err));
|
||||
.catch((err) => setError(err.message));
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
const questions = JSON.parse(data.formData.questions);
|
||||
const questions: SignupFormQuestion[] = JSON.parse(data.formData.questions);
|
||||
const payload: SignupForm = {
|
||||
...data.formData,
|
||||
questions,
|
||||
@@ -150,7 +150,7 @@ const SignupCreatePage: NextPage = () => {
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error("Uh oh! Something went wrong! Try again later. 😟");
|
||||
setError(err);
|
||||
setError(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ const SignupEmailPage: NextPage = () => {
|
||||
await SignupApi.signupFormSendEmail(payload, Number(id));
|
||||
toast.success("Email sent successfully 😎");
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
setError(err.message);
|
||||
toast.error("Uh oh! Something went wrong! Try again later. 😟");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ const SignupEmailPage: NextPage = () => {
|
||||
|
||||
// TODO: ATM we filter 'info' questions from table here. Maybe remove them from answer JSON altogether?
|
||||
const questions = signupForm ? signupForm.questions.filter((q) => q.type !== "info").map((q) => ({
|
||||
title: q.name,
|
||||
title: q.title_fi,
|
||||
id: q.id,
|
||||
})) : [];
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Question } from "@components/Widgets/SignupQuestionsWidget/common";
|
||||
import { SignupForm } from "@models/Signup";
|
||||
import { SignupFormQuestion } from "@models/Signup";
|
||||
import { EMAIL_REGEX } from "@utils/regexes";
|
||||
import escapeRegExp from "lodash/escapeRegExp";
|
||||
import { Lang } from "../../i18n";
|
||||
|
||||
const questionToUISchemaProp = (question: Question) => {
|
||||
let obj: Record<"ui:widget", string>;
|
||||
@@ -30,7 +31,7 @@ const questionToValidationSchema = (question: Question) => {
|
||||
obj = {
|
||||
type: "null",
|
||||
title: question.name,
|
||||
description: question.options,
|
||||
description: question.description,
|
||||
};
|
||||
} else if (question.type === "email") {
|
||||
// Format is just a "FYI" field, so we also have pattern.
|
||||
@@ -45,37 +46,39 @@ const questionToValidationSchema = (question: Question) => {
|
||||
obj = {
|
||||
type: "string",
|
||||
title: question.name,
|
||||
pattern: question.options.map((x) => `^${escapeRegExp(x)}$`).join("|"),
|
||||
enum: question.options,
|
||||
pattern: question.enum.map((x) => `^${escapeRegExp(x)}$`).join("|"),
|
||||
enum: question.enum,
|
||||
enumNames: question.enumNames,
|
||||
};
|
||||
} else if (question.type === "checkbox") {
|
||||
obj = {
|
||||
type: "array",
|
||||
title: question.name,
|
||||
uniqueItems: true,
|
||||
maxItems: question.options.length,
|
||||
maxItems: question.enum.length,
|
||||
items: {
|
||||
type: "string",
|
||||
enum: question.options,
|
||||
pattern: question.options.map((x) => `^${escapeRegExp(x)}$`).join("|"),
|
||||
enum: question.enum,
|
||||
enumNames: question.enumNames,
|
||||
pattern: question.enum.map((x) => `^${escapeRegExp(x)}$`).join("|"),
|
||||
},
|
||||
};
|
||||
} else if (question.type === "integer") {
|
||||
// https://json-schema.org/understanding-json-schema/reference/numeric.html
|
||||
if (question.options.length === 1) {
|
||||
if (question.enum.length === 1) {
|
||||
obj = {
|
||||
type: "number",
|
||||
title: `${question.name} (Max: ${question.options[0]})`,
|
||||
title: `${question.name} (Max: ${question.enum[0]})`,
|
||||
multipleOf: 1.0,
|
||||
maximum: Number(question.options[0]),
|
||||
maximum: Number(question.enum[0]),
|
||||
};
|
||||
} else if (question.options.length === 2) {
|
||||
} else if (question.enum.length === 2) {
|
||||
obj = {
|
||||
type: "number",
|
||||
title: `${question.name} (${question.options[0]} -- ${question.options[1]})`,
|
||||
title: `${question.name} (${question.enum[0]} -- ${question.enum[1]})`,
|
||||
multipleOf: 1.0,
|
||||
minimum: Number(question.options[0]),
|
||||
maximum: Number(question.options[1]),
|
||||
minimum: Number(question.enum[0]),
|
||||
maximum: Number(question.enum[1]),
|
||||
};
|
||||
} else {
|
||||
obj = {
|
||||
@@ -92,9 +95,8 @@ const questionToValidationSchema = (question: Question) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const buildFormSchema = (signUpForm: SignupForm) => {
|
||||
export const buildFormSchema = (questions: Question[], title: string) => {
|
||||
let schemaProps = {};
|
||||
const { questions } = signUpForm;
|
||||
const requiredIds = questions.filter((q) => q.required).map((q) => q.id);
|
||||
const schemaPropsArray = questions.map(questionToValidationSchema);
|
||||
schemaPropsArray.forEach((schemaProp) => {
|
||||
@@ -105,7 +107,7 @@ export const buildFormSchema = (signUpForm: SignupForm) => {
|
||||
});
|
||||
|
||||
const schema = {
|
||||
title: signUpForm.id ? signUpForm.title_fi : "Loading...",
|
||||
title,
|
||||
type: "object",
|
||||
required: requiredIds,
|
||||
properties: schemaProps,
|
||||
@@ -114,9 +116,24 @@ export const buildFormSchema = (signUpForm: SignupForm) => {
|
||||
return schema;
|
||||
};
|
||||
|
||||
export const buildValidationSchema = (questions: Question[]) => {
|
||||
export const signupFormQuestionToQuestion = ({
|
||||
id, title_fi, title_en, description_fi, description_en, type, options, required,
|
||||
}: SignupFormQuestion, language: Lang): Question => ({
|
||||
id,
|
||||
type,
|
||||
name: language === "fi" ? title_fi : title_en,
|
||||
enum: options?.enum,
|
||||
enumNames: language === "fi" ? options?.enumNames_fi : options?.enumNames_en,
|
||||
description: language === "fi" ? description_fi : description_en,
|
||||
required,
|
||||
});
|
||||
|
||||
export const buildValidationSchema = (sfQuestions: SignupFormQuestion[]) => {
|
||||
let schemaProps = {};
|
||||
|
||||
// Remove translations. We use Finnish translations as values for validation
|
||||
const questions = sfQuestions.map((q) => signupFormQuestionToQuestion(q, "fi"));
|
||||
|
||||
// Force every radiobutton to be required field
|
||||
questions.forEach((q) => {
|
||||
if (q.type === "radiobutton") {
|
||||
@@ -144,8 +161,7 @@ export const buildValidationSchema = (questions: Question[]) => {
|
||||
return validationSchema;
|
||||
};
|
||||
|
||||
export const buildUISchema = (signUpForm: SignupForm) => {
|
||||
const { questions } = signUpForm;
|
||||
export const buildUISchema = (questions: Question[]) => {
|
||||
const uiSchemaPropsArray = questions.map(questionToUISchemaProp);
|
||||
let uiSchemaProps = {};
|
||||
uiSchemaPropsArray.forEach((uiSchemaProp) => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { TextSection, ChangeLanguageButton } from "@components/index";
|
||||
import colors from "@theme/colors";
|
||||
import FormWrapper from "@views/common/FormWrapper";
|
||||
import Loader from "@components/Loader";
|
||||
import { buildFormSchema, buildUISchema } from "./FormUtils";
|
||||
import { buildFormSchema, buildUISchema, signupFormQuestionToQuestion } from "./FormUtils";
|
||||
import { useTranslation } from "../../i18n";
|
||||
|
||||
const customWidgets = {
|
||||
@@ -106,12 +106,14 @@ const SignUpPageView: React.FC<SignUpPageViewProps> = ({
|
||||
);
|
||||
signups = renderList();
|
||||
} else {
|
||||
const formTitle = signUpForm.id ? signUpForm.title_fi : "Loading...";
|
||||
const questions = signUpForm.questions.map((q) => signupFormQuestionToQuestion(q, i18n.language));
|
||||
form = (
|
||||
<>
|
||||
<p>{`${t("Ilmoittauminen sulkeutuu")} ${endDateStr}`}.</p>
|
||||
<FormWrapper
|
||||
schema={buildFormSchema(signUpForm) as unknown}
|
||||
uiSchema={buildUISchema(signUpForm)}
|
||||
schema={buildFormSchema(questions, formTitle) as unknown}
|
||||
uiSchema={buildUISchema(questions)}
|
||||
formData={formData}
|
||||
widgets={customWidgets}
|
||||
idPrefix="rjsf"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,533 @@
|
||||
import { SignupForm } from "@models/Signup";
|
||||
import {
|
||||
signupFormQuestionToQuestion, buildFormSchema, buildValidationSchema, buildUISchema,
|
||||
} from "./FormUtils";
|
||||
|
||||
const signupForm: SignupForm = {
|
||||
id: 250,
|
||||
title_fi: "Potentiaalin Tasaus 100 ilmoittautuminen - deviversio",
|
||||
title_en: "Pota100 dev",
|
||||
visible: true,
|
||||
isOpen: true,
|
||||
start_time: "2021-08-17T16:45:15+03:00",
|
||||
end_time: "2021-09-30T23:59:59+03:00",
|
||||
email_content: "Hei, \r\n\r\nIlmoittautumisesi on saapunut perille!\r\n\r\nMaksutiedot lähetetään sinulle vasta kun ilmoittautuminen on sulkeutunut. \r\n\r\nJos ilmoittautumisessa ilmenee ongelmia tai pääjuhlasta nousee kysymyksiä, olethan yhetydessä Potentiaalin Tasaus 100 pääjuhlavastaaviin:\r\n\r\nEmmaleena Ahonen ja Jonna Tammikivi \r\npota@sik100.fi\r\n\r\nPS. Jos tulet juhlaan avecin kanssa, muista, että hänen tulee myös ilmoittautua juhlaan erikseen!\r\n\r\nPoTassa nähdään!",
|
||||
questions: [
|
||||
{
|
||||
id: "yigh6mhd4",
|
||||
title_fi: "Ilmoittautuminen",
|
||||
title_en: "EN-Ilmoittautuminen",
|
||||
type: "info",
|
||||
description_fi: "Tämä ilmoittautuminen kustantaa 120€ opiskelijoille ja 180€ alumneille. Ilmoittautuminen on sitova.",
|
||||
description_en: "EN - Tämä ilmoittautuminen kustantaa 120€ opiskelijoille ja 180€ alumneille. Ilmoittautuminen on sitova.",
|
||||
},
|
||||
{
|
||||
id: "WRflgsBe_",
|
||||
title_fi: "Nimi",
|
||||
title_en: "Name",
|
||||
type: "name",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "OF55WBbOx",
|
||||
title_fi: "Sähköposti",
|
||||
title_en: "Email",
|
||||
type: "email",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "ZY5UpArqx",
|
||||
title_fi: "Olen ",
|
||||
title_en: "I am ",
|
||||
type: "radiobutton",
|
||||
options: {
|
||||
enum: [
|
||||
"Killan jäsen",
|
||||
"Killan alumni",
|
||||
"Jäsenen avec",
|
||||
"Alumnin avec",
|
||||
],
|
||||
enumNames_fi: [
|
||||
"Killan jäsen",
|
||||
"Killan alumni",
|
||||
"Jäsenen avec",
|
||||
"Alumnin avec",
|
||||
],
|
||||
enumNames_en: [
|
||||
"Member",
|
||||
"Alumni",
|
||||
"Member avec",
|
||||
"Alumni avec",
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "dUzh31kag",
|
||||
title_fi: "Fuksivuosi (yyyy)",
|
||||
title_en: "Freshman year (yyyy)",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
id: "1LaFnZ-Of",
|
||||
title_fi: "Erikoisruokavaliot / Allergiat",
|
||||
title_en: "Allergies",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
id: "PajprpSLa",
|
||||
title_fi: "Liha ja kala menuvaihtoehto tarkoittaa sitä että menuun kuuluu molempia alku- tai pääruokana.",
|
||||
title_en: "EN - Liha ja kala menuvaihtoehto tarkoittaa sitä että menuun kuuluu molempia alku- tai pääruokana.",
|
||||
type: "info",
|
||||
description_fi: "Huomioimme allergiat menuvalinnan lisäksi. Esimerkiksi jos on allerginen kalalle tämä otetaan huomioon jos on valinnut \"liha ja kala\" vaihtoehdon.",
|
||||
description_en: "EN - Huomioimme allergiat menuvalinnan lisäksi. Esimerkiksi jos on allerginen kalalle tämä otetaan huomioon jos on valinnut \"liha ja kala\" vaihtoehdon.",
|
||||
},
|
||||
{
|
||||
id: "0GMtDu46R",
|
||||
title_fi: "Pääjuhlan ruokatarjoilut",
|
||||
title_en: "EN - Pääjuhlan ruokatarjoilut",
|
||||
type: "radiobutton",
|
||||
|
||||
options: {
|
||||
enum: [
|
||||
"Vegaaninen",
|
||||
"Liha ja Kala",
|
||||
],
|
||||
enumNames_fi: [
|
||||
"Vegaaninen",
|
||||
"Liha ja Kala",
|
||||
],
|
||||
enumNames_en: [
|
||||
"Vegan",
|
||||
"Meat and fish",
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "MMghazOPT",
|
||||
title_fi: "Pääjuhlan juomatarjoilut",
|
||||
title_en: "EN - Pääjuhlan juomatarjoilut",
|
||||
type: "radiobutton",
|
||||
options: {
|
||||
enum: [
|
||||
"Alkoholillinen",
|
||||
"Alkoholiton",
|
||||
],
|
||||
enumNames_fi: [
|
||||
"Alkoholillinen",
|
||||
"Alkoholiton",
|
||||
],
|
||||
enumNames_en: [
|
||||
"Alcoholic",
|
||||
"Alcohol free",
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "fCYJxDSrL",
|
||||
title_fi: "Haluan tilata pöytään pullon viiniä tai kuohuvaa",
|
||||
title_en: "EN - Haluan tilata pöytään pullon viiniä tai kuohuvaa",
|
||||
type: "checkbox",
|
||||
options: {
|
||||
enum: [
|
||||
"Punaviini (42€)",
|
||||
"Valkoviini (42€)",
|
||||
"Kuohuviini (42€)",
|
||||
"Shamppanja (68€)",
|
||||
],
|
||||
enumNames_fi: [
|
||||
"Punaviini (42€)",
|
||||
"Valkoviini (42€)",
|
||||
"Kuohuviini (42€)",
|
||||
"Shamppanja (68€)",
|
||||
],
|
||||
enumNames_en: [
|
||||
"Red wine (42€)",
|
||||
"White wine (42€)",
|
||||
"Sparkling wine (42€)",
|
||||
"Champagne (68€)",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "0q74weKci",
|
||||
title_fi: "Avec",
|
||||
title_en: "Avec",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
id: "kqPI12VK_",
|
||||
title_fi: "Pöytäseurue",
|
||||
title_en: "EN - Pöytäseurue",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
id: "ofKH9GhFg",
|
||||
title_fi: "Annan lahjan lahjanantotilaisuudessa",
|
||||
title_en: "EN - Annan lahjan lahjanantotilaisuudessa",
|
||||
type: "radiobutton",
|
||||
options: {
|
||||
enum: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
enumNames_fi: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
enumNames_en: [
|
||||
"Yes",
|
||||
"No",
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "AsYHmSz2V",
|
||||
title_fi: "Jos annat lahjan, mitä tahoa edustat?",
|
||||
title_en: "EN - Jos annat lahjan, mitä tahoa edustat?",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
id: "hA3b8X6P4",
|
||||
title_fi: "Haluan osallistua jatkoille",
|
||||
title_en: "EN - Haluan osallistua jatkoille",
|
||||
type: "radiobutton",
|
||||
options: {
|
||||
enum: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
enumNames_fi: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
enumNames_en: [
|
||||
"Yes",
|
||||
"No",
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "rf34jMWSe",
|
||||
title_fi: "Sillikselle on rajattu määrä paikkoja, jolloin emme voi varmistaa kaikille pääsyä.",
|
||||
title_en: "EN - Sillikselle on rajattu määrä paikkoja, jolloin emme voi varmistaa kaikille pääsyä.",
|
||||
type: "info",
|
||||
description_fi: "Ilmoitamme sähköpostilla siinä tapauksessa jos olet jonossa tai et maahtunut silliksen kiintiöön. ",
|
||||
description_en: "EN - Ilmoitamme sähköpostilla siinä tapauksessa jos olet jonossa tai et maahtunut silliksen kiintiöön. ",
|
||||
},
|
||||
{
|
||||
id: "PnzuTUxZH",
|
||||
title_fi: "Haluan osallistua sillikselle seuraavana päivänä (25€)",
|
||||
title_en: "EN - Haluan osallistua sillikselle seuraavana päivänä (25€)",
|
||||
type: "radiobutton",
|
||||
options: {
|
||||
enum: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
enumNames_fi: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
enumNames_en: [
|
||||
"Yes",
|
||||
"No",
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "aM8Xjhsqs",
|
||||
title_fi: "Haluan kuulla lisää SIK100-historiateoksesta ja mahdollisuudesta ostaa teoksen",
|
||||
title_en: "EN - Haluan kuulla lisää SIK100-historiateoksesta ja mahdollisuudesta ostaa teoksen",
|
||||
type: "radiobutton",
|
||||
options: {
|
||||
enum: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
enumNames_fi: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
enumNames_en: [
|
||||
"Yes",
|
||||
"No",
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "m2aKUikfI",
|
||||
title_fi: "Vapaaehtoinen kannatusmaksu",
|
||||
title_en: "EN - Vapaaehtoinen kannatusmaksu",
|
||||
type: "checkbox",
|
||||
options: {
|
||||
enum: [
|
||||
"15€",
|
||||
"25€",
|
||||
"50€",
|
||||
],
|
||||
enumNames_fi: [
|
||||
"15€",
|
||||
"25€",
|
||||
"50€",
|
||||
],
|
||||
enumNames_en: [
|
||||
"15€",
|
||||
"25€",
|
||||
"50€",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "13qShsW03",
|
||||
title_fi: "Haluan saada sähköpostiini lisää tietoa SIK100-vuodesta",
|
||||
title_en: "EN - Haluan saada sähköpostiini lisää tietoa SIK100-vuodesta",
|
||||
type: "radiobutton",
|
||||
options: {
|
||||
enum: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
enumNames_fi: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
enumNames_en: [
|
||||
"Yes",
|
||||
"No",
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "xI_OlVAxM",
|
||||
title_fi: "Terveisiä killalle",
|
||||
title_en: "EN - Terveisiä killalle",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
id: "04FkeTQZm",
|
||||
title_fi: "Paras vuosijuhlamuisto",
|
||||
title_en: "EN - Paras vuosijuhlamuisto",
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
schema: {
|
||||
type: "object",
|
||||
required: [
|
||||
"WRflgsBe_",
|
||||
"OF55WBbOx",
|
||||
"ZY5UpArqx",
|
||||
"0GMtDu46R",
|
||||
"MMghazOPT",
|
||||
"ofKH9GhFg",
|
||||
"hA3b8X6P4",
|
||||
"PnzuTUxZH",
|
||||
"aM8Xjhsqs",
|
||||
"13qShsW03",
|
||||
],
|
||||
properties: {
|
||||
"04FkeTQZm": {
|
||||
type: "string",
|
||||
title: "Paras vuosijuhlamuisto",
|
||||
},
|
||||
"0GMtDu46R": {
|
||||
enum: [
|
||||
"Vegaaninen",
|
||||
"Liha ja Kala",
|
||||
],
|
||||
type: "string",
|
||||
title: "Pääjuhlan ruokatarjoilut",
|
||||
pattern: "^Vegaaninen$|^Liha ja Kala$",
|
||||
},
|
||||
"0q74weKci": {
|
||||
type: "string",
|
||||
title: "Avec",
|
||||
},
|
||||
"13qShsW03": {
|
||||
enum: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
type: "string",
|
||||
title: "Haluan saada sähköpostiini lisää tietoa SIK100-vuodesta",
|
||||
pattern: "^Kyllä$|^Ei$",
|
||||
},
|
||||
"1LaFnZ-Of": {
|
||||
type: "string",
|
||||
title: "Erikoisruokavaliot / Allergiat",
|
||||
},
|
||||
AsYHmSz2V: {
|
||||
type: "string",
|
||||
title: "Jos annat lahjan, mitä tahoa edustat?",
|
||||
},
|
||||
MMghazOPT: {
|
||||
enum: [
|
||||
"Alkoholillinen",
|
||||
"Alkoholiton",
|
||||
],
|
||||
type: "string",
|
||||
title: "Pääjuhlan juomatarjoilut ",
|
||||
pattern: "^Alkoholillinen$|^Alkoholiton$",
|
||||
},
|
||||
OF55WBbOx: {
|
||||
type: [
|
||||
"string",
|
||||
],
|
||||
title: "Sähköposti",
|
||||
format: "email",
|
||||
default: null,
|
||||
pattern: "^[a-zA-Z0-9.!#$%&\\u2019*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$",
|
||||
},
|
||||
PajprpSLa: {
|
||||
type: "null",
|
||||
title: "Liha ja kala menuvaihtoehto tarkoittaa sitä että menuun kuuluu molempia alku- tai pääruokana.",
|
||||
description: "Huomioimme allergiat menuvalinnan lisäksi. Esimerkiksi jos on allerginen kalalle tämä otetaan huomioon jos on valinnut \"liha ja kala\" vaihtoehdon.",
|
||||
},
|
||||
PnzuTUxZH: {
|
||||
enum: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
type: "string",
|
||||
title: "Haluan osallistua sillikselle seuraavana päivänä (25€)",
|
||||
pattern: "^Kyllä$|^Ei$",
|
||||
},
|
||||
WRflgsBe_: {
|
||||
type: "string",
|
||||
title: "Nimi",
|
||||
},
|
||||
ZY5UpArqx: {
|
||||
enum: [
|
||||
"Killan jäsen",
|
||||
"Killan alumni",
|
||||
"Jäsenen avec",
|
||||
"Alumnin avec",
|
||||
],
|
||||
type: "string",
|
||||
title: "Olen ",
|
||||
pattern: "^Killan jäsen$|^Killan alumni$|^Jäsenen avec$|^Alumnin avec$",
|
||||
},
|
||||
aM8Xjhsqs: {
|
||||
enum: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
type: "string",
|
||||
title: "Haluan kuulla lisää SIK100-historiateoksesta ja mahdollisuudesta ostaa teoksen",
|
||||
pattern: "^Kyllä$|^Ei$",
|
||||
},
|
||||
dUzh31kag: {
|
||||
type: "string",
|
||||
title: "Fuksivuosi (yyyy)",
|
||||
},
|
||||
fCYJxDSrL: {
|
||||
type: "array",
|
||||
items: {
|
||||
enum: [
|
||||
"Punaviini (42€)",
|
||||
"Valkoviini (42€)",
|
||||
"Kuohuviini (42€)",
|
||||
"Shamppanja (68€)",
|
||||
],
|
||||
type: "string",
|
||||
pattern: "^Punaviini \\(42€\\)$|^Valkoviini \\(42€\\)$|^Kuohuviini \\(42€\\)$|^Shamppanja \\(68€\\)$",
|
||||
},
|
||||
title: "Haluan tilata pöytään pullon viiniä tai kuohuvaa",
|
||||
maxItems: 4,
|
||||
uniqueItems: true,
|
||||
},
|
||||
hA3b8X6P4: {
|
||||
enum: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
type: "string",
|
||||
title: "Haluan osallistua jatkoille",
|
||||
pattern: "^Kyllä$|^Ei$",
|
||||
},
|
||||
kqPI12VK_: {
|
||||
type: "string",
|
||||
title: "Pöytäseurue",
|
||||
},
|
||||
m2aKUikfI: {
|
||||
type: "array",
|
||||
items: {
|
||||
enum: [
|
||||
"15€",
|
||||
"25€",
|
||||
"50€",
|
||||
],
|
||||
type: "string",
|
||||
pattern: "^15€$|^25€$|^50€$",
|
||||
},
|
||||
title: "Vapaaehtoinen kannatusmaksu",
|
||||
maxItems: 3,
|
||||
uniqueItems: true,
|
||||
},
|
||||
ofKH9GhFg: {
|
||||
enum: [
|
||||
"Kyllä",
|
||||
"Ei",
|
||||
],
|
||||
type: "string",
|
||||
title: "Annan lahjan lahjanantotilaisuudessa",
|
||||
pattern: "^Kyllä$|^Ei$",
|
||||
},
|
||||
rf34jMWSe: {
|
||||
type: "null",
|
||||
title: "Sillikselle on rajattu määrä paikkoja, jolloin emme voi varmistaa kaikille pääsyä.",
|
||||
description: "Ilmoitamme sähköpostilla siinä tapauksessa jos olet jonossa tai et maahtunut silliksen kiintiöön. ",
|
||||
},
|
||||
xI_OlVAxM: {
|
||||
type: "string",
|
||||
title: "Terveisiä killalle",
|
||||
},
|
||||
yigh6mhd4: {
|
||||
type: "null",
|
||||
title: "Ilmoittautuminen",
|
||||
description: "Tämä ilmoittautuminen kustantaa 120€ opiskelijoille ja 180€ alumneille. Ilmoittautuminen on sitova.",
|
||||
},
|
||||
},
|
||||
},
|
||||
signups: [
|
||||
"asd",
|
||||
],
|
||||
quota: 200,
|
||||
};
|
||||
|
||||
const finnishQuestions = signupForm.questions.map((q) => signupFormQuestionToQuestion(q, "fi"));
|
||||
const englishQuestions = signupForm.questions.map((q) => signupFormQuestionToQuestion(q, "en"));
|
||||
|
||||
describe("signupFormQuestionToQuestion", () => {
|
||||
it("mathces snapshot in Finnish", () => {
|
||||
expect(finnishQuestions).toMatchSnapshot();
|
||||
});
|
||||
it("mathces snapshot in English", () => {
|
||||
expect(englishQuestions).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildFormSchema", () => {
|
||||
it("matches snapshot", () => {
|
||||
expect(buildFormSchema(finnishQuestions, signupForm.title_fi)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildUISchema", () => {
|
||||
it("matches snapshot", () => {
|
||||
expect(buildUISchema(finnishQuestions)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildValidationSchema", () => {
|
||||
it("matches snapshot", () => {
|
||||
expect(buildValidationSchema(signupForm.questions)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -64,10 +64,10 @@ const AdminPageWrapper: React.FC<PageProps> = ({ requiresAuthentication, childre
|
||||
const router = useRouter();
|
||||
const { completed, redirecting } = useShouldRedirect(requiresAuthentication);
|
||||
|
||||
const { pathname } = router;
|
||||
const { asPath } = router;
|
||||
|
||||
if (redirecting) {
|
||||
const loginURL = `/admin/login?next=${pathname}`;
|
||||
const loginURL = `/admin/login?next=${asPath}`;
|
||||
router.push(loginURL);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ const AdminPageWrapper: React.FC<PageProps> = ({ requiresAuthentication, childre
|
||||
<>
|
||||
<AdminHeader />
|
||||
<Main>
|
||||
<AdminSidebar path={pathname} />
|
||||
<AdminSidebar path={asPath} />
|
||||
{children}
|
||||
</Main>
|
||||
</>
|
||||
|
||||
@@ -38,45 +38,58 @@ test("Logged in user can create signup", async (t) => {
|
||||
|
||||
await t.click(newQuestionButton);
|
||||
let question = lastQuestion();
|
||||
let questionName = question.child("input");
|
||||
let questionNameFi = question.child("input").nth(0);
|
||||
let questionNameEn = question.child("input").nth(1);
|
||||
let questionTypeSelect = question.child("select");
|
||||
let requiredBox = question.child("label");
|
||||
|
||||
await t
|
||||
.selectText(questionName)
|
||||
.selectText(questionNameFi)
|
||||
.pressKey("delete")
|
||||
.typeText(questionName, "Nimi")
|
||||
.typeText(questionNameFi, "Nimi")
|
||||
.selectText(questionNameEn)
|
||||
.pressKey("delete")
|
||||
.typeText(questionNameEn, "Name")
|
||||
.click(questionTypeSelect)
|
||||
.click(questionTypeSelect.find("option").withExactText("name"))
|
||||
.click(requiredBox);
|
||||
|
||||
await t.click(newQuestionButton);
|
||||
question = lastQuestion();
|
||||
questionName = question.child("input");
|
||||
questionNameFi = question.child("input").nth(0);
|
||||
questionNameEn = question.child("input").nth(1);
|
||||
questionTypeSelect = question.child("select");
|
||||
requiredBox = question.child("label");
|
||||
|
||||
await t
|
||||
.selectText(questionName)
|
||||
.selectText(questionNameFi)
|
||||
.pressKey("delete")
|
||||
.typeText(questionName, "S-Posti")
|
||||
.typeText(questionNameFi, "S-Posti")
|
||||
.selectText(questionNameEn)
|
||||
.pressKey("delete")
|
||||
.typeText(questionNameEn, "Email")
|
||||
.click(questionTypeSelect)
|
||||
.click(questionTypeSelect.find("option").withExactText("email"))
|
||||
.click(requiredBox);
|
||||
|
||||
await t.click(newQuestionButton);
|
||||
question = lastQuestion();
|
||||
questionName = question.child("input");
|
||||
questionNameFi = question.child("input");
|
||||
questionTypeSelect = question.child("select");
|
||||
const radioOptions = question.child("input").nth(-1);
|
||||
const radioOptionsFi = question.child("input").nth(-2);
|
||||
const radioOptionsEn = question.child("input").nth(-1);
|
||||
|
||||
await t
|
||||
.selectText(questionName)
|
||||
.selectText(questionNameFi)
|
||||
.pressKey("delete")
|
||||
.typeText(questionName, "Olen")
|
||||
.typeText(questionNameFi, "Olen")
|
||||
.selectText(questionNameEn)
|
||||
.pressKey("delete")
|
||||
.typeText(questionNameEn, "I am")
|
||||
.click(questionTypeSelect)
|
||||
.click(questionTypeSelect.find("option").withExactText("radiobutton"))
|
||||
.typeText(radioOptions, "Nuori,Vanha,Testaaja");
|
||||
.typeText(radioOptionsFi, "Nuori;Vanha;Testaaja")
|
||||
.typeText(radioOptionsEn, "Yung;Old;Tester");
|
||||
|
||||
const submit = Selector("button[type=\"submit\"]");
|
||||
|
||||
|
||||
@@ -111,23 +111,23 @@ export const generateTestForm = async (jwt: string) => (
|
||||
end_time: tomorrow,
|
||||
email_content: "E2E Test",
|
||||
questions: [{
|
||||
id: "XS_Ox5Rry", name: "Nimi", type: "name", options: [], required: true,
|
||||
id: "Kv0IRYUWE", type: "name", options: { enum: [], enumNames_en: [], enumNames_fi: [] }, required: true, title_en: "Name", title_fi: "Nimi",
|
||||
}, {
|
||||
id: "Ve02XSEEx", name: "S-Posti", type: "email", options: [], required: true,
|
||||
id: "_9o78DbdZ", type: "email", options: { enum: [], enumNames_en: [], enumNames_fi: [] }, required: true, title_en: "Email", title_fi: "S-Posti",
|
||||
}, {
|
||||
id: "luMqnz5y9", name: "Olen", type: "radiobutton", options: ["Nuori", "Vanha", "Testaaja"],
|
||||
id: "-Zk6tCy7U", type: "radiobutton", options: { enum: ["Nuori", "Vanha", "Testaaja"], enumNames_en: ["Yung", "Old", "Tester"], enumNames_fi: ["Nuori", "Vanha", "Testaaja"] }, title_en: "I am", title_fi: "Olen",
|
||||
}],
|
||||
id: 14,
|
||||
isOpen: true,
|
||||
schema: {
|
||||
type: "object",
|
||||
required: ["XS_Ox5Rry", "Ve02XSEEx"],
|
||||
required: ["Kv0IRYUWE", "_9o78DbdZ"],
|
||||
properties: {
|
||||
XS_Ox5Rry: { type: "string", title: "Nimi" },
|
||||
Ve02XSEEx: {
|
||||
Kv0IRYUWE: { type: "string", title: "Nimi" },
|
||||
_9o78DbdZ: {
|
||||
type: ["string"], title: "S-Posti", format: "email", pattern: "^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$", default: null,
|
||||
},
|
||||
luMqnz5y9: {
|
||||
"-Zk6tCy7U": {
|
||||
type: "string", title: "Olen", pattern: "^Nuori$|^Vanha$|^Testaaja$", enum: ["Nuori", "Vanha", "Testaaja"],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
"./tests/testcafe/**/*",
|
||||
"next-sitemap.js",
|
||||
"next.config.js",
|
||||
"jest.config.js",
|
||||
".eslintrc.js"
|
||||
],
|
||||
"exclude": [
|
||||
|
||||
Reference in New Issue
Block a user