add simple implementation of template questions

This commit is contained in:
Aarni Halinen
2021-08-24 02:49:46 +03:00
parent 127cfd3e46
commit 514d2f4c2e
7 changed files with 320 additions and 3 deletions
+65
View File
@@ -2,9 +2,11 @@
import axios from "axios";
import { Signup, SignupForm } from "@models/Signup";
import { getAuthHeader } from "@utils/auth";
import { TemplateQuestion } from "@models/TemplateQuestion";
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/signup/`;
export const FORM_URL = `${process.env.NEXT_PUBLIC_API_URL}/signupForm/`;
export const QUESTIONS_URL = `${process.env.NEXT_PUBLIC_API_URL}/questions/`;
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Options {
@@ -177,6 +179,69 @@ class SignupApi {
throw err;
}
}
static async getTemplateQuestions(): Promise<TemplateQuestion[]> {
try {
const resp = await axios.get(`${QUESTIONS_URL}`);
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
}
static async getTemplateQuestion(id: number): Promise<TemplateQuestion> {
try {
const resp = await axios.get(`${QUESTIONS_URL}${id}/`);
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async createTemplateQuestion(question: TemplateQuestion): Promise<TemplateQuestion> {
try {
const resp = await axios.post(`${QUESTIONS_URL}`, question, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async updateTemplateQuestion(question: TemplateQuestion): Promise<TemplateQuestion> {
try {
const putUrl = `${QUESTIONS_URL}${question.id}/`;
const resp = await axios.put(putUrl, question, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async deleteTemplateQuestion(id: number): Promise<{ message: string }> {
try {
const resp = await axios.delete(`${QUESTIONS_URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
}
export default SignupApi;
+1
View File
@@ -49,6 +49,7 @@ const AdminSidebar: React.FC<AdminSidebarProps> = ({ path }) => (
<StyledLink to="/admin/events" passHref $path={path}>Events&nbsp;</StyledLink>
<StyledLink to="/admin/feed" passHref $path={path}>Feed&nbsp;</StyledLink>
<StyledLink to="/admin/signups" passHref $path={path}>Signup forms&nbsp;</StyledLink>
<StyledLink to="/admin/template-questions" passHref $path={path}>Template questions&nbsp;</StyledLink>
<StyledLink to="/admin/jobads" passHref $path={path}>Job advertisements&nbsp;</StyledLink>
<StyledLink to="https://static.sahkoinsinoorikilta.fi/admin" passHref $path={path}>Files&nbsp;</StyledLink>
<StyledLink data-e2e="admin-sidebar-logout" to="/admin/logout" passHref $path={path}>Logout&nbsp;</StyledLink>
+7
View File
@@ -0,0 +1,7 @@
import { Question } from "@components/Widgets/SignupQuestionsWidget/common";
export type TemplateQuestion = {
id?: number;
name: string;
questions: Question[];
};
+47 -2
View File
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import shortid from "shortid";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
import { SignupForm } from "@models/Signup";
import SignupApi from "@api/signupApi";
@@ -9,6 +10,8 @@ import SignupQuestionsWidget from "@components/Widgets/SignupQuestionsWidget/Sig
import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget";
import { buildValidationSchema } from "@views/SignUpPage/FormUtils";
import { toast } from "react-toastify";
import { TemplateQuestion } from "@models/TemplateQuestion";
import { Question } from "@components/Widgets/SignupQuestionsWidget/common";
const DEFAULT_EMAIL = `Moikka,
@@ -98,7 +101,10 @@ const buildUISchema = () => {
const SignupCreatePage: NextPage = () => {
const [formData, setFormData] = useState<SignupForm>(null);
const [templateQuestions, setTemplateQuestions] = useState<TemplateQuestion[]>([]);
const [error, setError] = useState<string>(null);
const templateSelectionRef = useRef<HTMLSelectElement>(null);
const templateNameRef = useRef<HTMLInputElement>(null);
const router = useRouter();
@@ -118,10 +124,15 @@ const SignupCreatePage: NextPage = () => {
questions: JSON.stringify(res.questions) as any,
});
})
.catch((err) => setError(err));
.catch((err) => setError(err.message));
}
}, [id]);
useEffect(() => {
SignupApi.getTemplateQuestions().then((res) => setTemplateQuestions(res))
.catch((err) => setError(err.message));
}, []);
const onSubmit = async (data: any) => {
try {
const questions = JSON.parse(data.formData.questions);
@@ -172,6 +183,40 @@ const SignupCreatePage: NextPage = () => {
error={error}
widgets={widgets}
/>
<div>
<select
ref={templateSelectionRef}
onChange={(event) => {
const addedTemplate = templateQuestions.find((q) => String(q.id) === event.target.value);
if (addedTemplate) {
// Generate new ids
const newItems = addedTemplate.questions.map((q) => ({ ...q, id: shortid() }));
// Concatenate new items to existing questions
const questions = JSON.parse(formData.questions as unknown as string).concat(newItems);
setFormData({
...formData,
questions: JSON.stringify(questions) as unknown as Question[],
});
}
templateSelectionRef.current.value = "";
}}
>
<option value="">No template</option>
{templateQuestions.map((q) => (
<option key={q.id} value={q.id}>{q.name}</option>
))}
</select>
<input ref={templateNameRef} />
<button
type="button"
onClick={() => {
const questions = JSON.parse(formData.questions as unknown as string);
console.log(questions);
SignupApi.createTemplateQuestion({ name: templateNameRef.current.value, questions });
}}
>Create new template
</button>
</div>
{/* {formData.id && <p>
Check out the signup form here: <Link to={`/signup/${formData.id}`}>{formData.title_fi}</Link>
</p>} */}
+118
View File
@@ -0,0 +1,118 @@
import React, { useEffect, useState } from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
import SignupApi from "@api/signupApi";
import SignupQuestionsWidget from "@components/Widgets/SignupQuestionsWidget/SignupQuestionsWidget";
import { toast } from "react-toastify";
import { TemplateQuestion } from "@models/TemplateQuestion";
const widgets = {
signup: SignupQuestionsWidget,
};
const buildSchema = (formData: TemplateQuestion) => ({
title: formData?.name ?? "New Sign-up form",
type: "object",
required: ["name", "questions"],
properties: {
name: {
type: "string",
title: "Name",
default: "",
},
questions: {
type: "string",
title: "Questions",
default: "[]",
},
},
});
const buildUISchema = () => ({
questions: {
"ui:widget": "signup",
},
});
const TemplateQuestionCreatePage: NextPage = () => {
const [formData, setFormData] = useState<TemplateQuestion>(null);
const [error, setError] = useState<string>(null);
const router = useRouter();
let id: string;
if (router.query?.id && router.query.id !== "create") {
id = router.query.id as string;
}
useEffect(() => {
const templateId = id && Number(id);
SignupApi.getTemplateQuestion(templateId, true)
.then((res) => {
setFormData({
...res,
questions: JSON.stringify(res.questions) as any,
});
})
.catch((err) => setError(err.message));
}, [id]);
const onSubmit = async (data: any) => {
try {
const questions = JSON.parse(data.formData.questions);
const payload: TemplateQuestion = {
...data.formData,
questions,
};
if (payload.id === undefined) {
const resp = await SignupApi.createTemplateQuestion(payload);
toast.success("Sign-up created successfully 😎");
router.push("/admin/template-questions");
setFormData({
...resp,
questions: JSON.stringify(resp.questions) as any,
});
} else {
const resp = await SignupApi.updateTemplateQuestion(payload);
toast.success("Sign-up updated successfully 😎");
router.push("/admin/template-questions");
setFormData({
...resp,
questions: JSON.stringify(resp.questions) as any,
});
}
} catch (err) {
toast.error("Uh oh! Something went wrong! Try again later. 😟");
setError(err);
}
};
const onChange = (data) => setFormData(data.formData);
const title = formData?.id
? `Edit template questions "${formData.name}"`
: "Create template questions";
return (
<>
<AdminCreateCommon
title={title}
formData={formData}
schema={buildSchema(formData)}
UISchema={buildUISchema()}
onChange={onChange}
onSubmit={onSubmit}
error={error}
widgets={widgets}
/>
{/* {formData.id && <p>
Check out the signup form here: <Link to={`/signup/${formData.id}`}>{formData.title_fi}</Link>
</p>} */}
</>
);
};
export default TemplateQuestionCreatePage;
@@ -0,0 +1,80 @@
import React, { useEffect, useState } from "react";
import { NextPage } from "next";
import { toast } from "react-toastify";
import styled from "styled-components";
import AdminListCommon from "@views/admin/AdminListCommon";
import { Button, Link } from "@components/index";
import AddLink from "@components/AddLink";
import SignupApi from "@api/signupApi";
import { TemplateQuestion } from "@models/TemplateQuestion";
const URL = "/admin/template-questions";
const StyledButton = styled(Button) <{ $colorOverride: "red" }>`
background-color: ${(p) => p.$colorOverride};
border-radius: 8px;
color: white;
font-size: 13px;
font-weight: bold;
`;
const confirmDelete = async (template: TemplateQuestion) => {
if (window.confirm(`Delete: ${template.id}: ${template.name}; Are you sure?`) === true) {
try {
await SignupApi.deleteTemplateQuestion(template.id);
toast.success("Template question removed successfully 😎");
window.location.reload(); // TODO: Fetch/update event list, so user sees the signup in the list
} catch (err) {
toast.error("Uh oh! Something went wrong! Try again later. 😟");
}
}
};
const renderData = (templates: TemplateQuestion[]) => {
if (templates.length === 0) {
return <div>No signup forms.</div>;
}
return (
<table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{templates.map((template) => (
<tr key={template.id}>
<td><Link to={`${URL}/${template.id}`}>{template.name}</Link></td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(template)}>
Delete
</StyledButton>
</td>
</tr>
))}
</tbody>
</table>
);
};
const AdminSignupTemplateQuestions: NextPage = () => {
const [allTemplates, setTemplates] = useState<TemplateQuestion[]>([]);
useEffect(() => {
SignupApi.getTemplateQuestions(true)
.then((res) => setTemplates(res));
}, []);
console.log(allTemplates);
return (
<AdminListCommon>
<h1>Sign-up forms</h1>
<AddLink text="Create template questions" to={`${URL}/create`} data-e2e="create-template-questions" />
{renderData(allTemplates)}
</AdminListCommon>
);
};
export default AdminSignupTemplateQuestions;
+2 -1
View File
@@ -6,6 +6,7 @@ import Event from "@models/Event";
import Post from "@models/Feed";
import { SignupForm } from "@models/Signup";
import JobAd from "@models/JobAd";
import { TemplateQuestion } from "@models/TemplateQuestion";
import FormWrapper from "@views/common/FormWrapper";
import AdminPageWrapper from "@views/common/AdminPageWrapper";
@@ -24,7 +25,7 @@ const ErrorMsg = styled.p`
display: inline-block;
`;
type FormTypes = Event | SignupForm | Post | JobAd;
type FormTypes = Event | SignupForm | Post | JobAd | TemplateQuestion;
type AdminCreateCommonProps = {
title: string;