add simple implementation of template questions
This commit is contained in:
@@ -2,9 +2,11 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Signup, SignupForm } from "@models/Signup";
|
import { Signup, SignupForm } from "@models/Signup";
|
||||||
import { getAuthHeader } from "@utils/auth";
|
import { getAuthHeader } from "@utils/auth";
|
||||||
|
import { TemplateQuestion } from "@models/TemplateQuestion";
|
||||||
|
|
||||||
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/signup/`;
|
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/signup/`;
|
||||||
export const FORM_URL = `${process.env.NEXT_PUBLIC_API_URL}/signupForm/`;
|
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
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
export interface Options {
|
export interface Options {
|
||||||
@@ -177,6 +179,69 @@ class SignupApi {
|
|||||||
throw err;
|
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;
|
export default SignupApi;
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const AdminSidebar: React.FC<AdminSidebarProps> = ({ path }) => (
|
|||||||
<StyledLink to="/admin/events" passHref $path={path}>Events ›</StyledLink>
|
<StyledLink to="/admin/events" passHref $path={path}>Events ›</StyledLink>
|
||||||
<StyledLink to="/admin/feed" passHref $path={path}>Feed ›</StyledLink>
|
<StyledLink to="/admin/feed" passHref $path={path}>Feed ›</StyledLink>
|
||||||
<StyledLink to="/admin/signups" passHref $path={path}>Signup forms ›</StyledLink>
|
<StyledLink to="/admin/signups" passHref $path={path}>Signup forms ›</StyledLink>
|
||||||
|
<StyledLink to="/admin/template-questions" passHref $path={path}>Template questions ›</StyledLink>
|
||||||
<StyledLink to="/admin/jobads" passHref $path={path}>Job advertisements ›</StyledLink>
|
<StyledLink to="/admin/jobads" passHref $path={path}>Job advertisements ›</StyledLink>
|
||||||
<StyledLink to="https://static.sahkoinsinoorikilta.fi/admin" passHref $path={path}>Files ›</StyledLink>
|
<StyledLink to="https://static.sahkoinsinoorikilta.fi/admin" passHref $path={path}>Files ›</StyledLink>
|
||||||
<StyledLink data-e2e="admin-sidebar-logout" to="/admin/logout" passHref $path={path}>Logout ›</StyledLink>
|
<StyledLink data-e2e="admin-sidebar-logout" to="/admin/logout" passHref $path={path}>Logout ›</StyledLink>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Question } from "@components/Widgets/SignupQuestionsWidget/common";
|
||||||
|
|
||||||
|
export type TemplateQuestion = {
|
||||||
|
id?: number;
|
||||||
|
name: string;
|
||||||
|
questions: Question[];
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import shortid from "shortid";
|
||||||
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
|
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
|
||||||
import { SignupForm } from "@models/Signup";
|
import { SignupForm } from "@models/Signup";
|
||||||
import SignupApi from "@api/signupApi";
|
import SignupApi from "@api/signupApi";
|
||||||
@@ -9,6 +10,8 @@ import SignupQuestionsWidget from "@components/Widgets/SignupQuestionsWidget/Sig
|
|||||||
import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget";
|
import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget";
|
||||||
import { buildValidationSchema } from "@views/SignUpPage/FormUtils";
|
import { buildValidationSchema } from "@views/SignUpPage/FormUtils";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
import { TemplateQuestion } from "@models/TemplateQuestion";
|
||||||
|
import { Question } from "@components/Widgets/SignupQuestionsWidget/common";
|
||||||
|
|
||||||
const DEFAULT_EMAIL = `Moikka,
|
const DEFAULT_EMAIL = `Moikka,
|
||||||
|
|
||||||
@@ -98,7 +101,10 @@ const buildUISchema = () => {
|
|||||||
|
|
||||||
const SignupCreatePage: NextPage = () => {
|
const SignupCreatePage: NextPage = () => {
|
||||||
const [formData, setFormData] = useState<SignupForm>(null);
|
const [formData, setFormData] = useState<SignupForm>(null);
|
||||||
|
const [templateQuestions, setTemplateQuestions] = useState<TemplateQuestion[]>([]);
|
||||||
const [error, setError] = useState<string>(null);
|
const [error, setError] = useState<string>(null);
|
||||||
|
const templateSelectionRef = useRef<HTMLSelectElement>(null);
|
||||||
|
const templateNameRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -118,10 +124,15 @@ const SignupCreatePage: NextPage = () => {
|
|||||||
questions: JSON.stringify(res.questions) as any,
|
questions: JSON.stringify(res.questions) as any,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => setError(err));
|
.catch((err) => setError(err.message));
|
||||||
}
|
}
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
SignupApi.getTemplateQuestions().then((res) => setTemplateQuestions(res))
|
||||||
|
.catch((err) => setError(err.message));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onSubmit = async (data: any) => {
|
const onSubmit = async (data: any) => {
|
||||||
try {
|
try {
|
||||||
const questions = JSON.parse(data.formData.questions);
|
const questions = JSON.parse(data.formData.questions);
|
||||||
@@ -172,6 +183,40 @@ const SignupCreatePage: NextPage = () => {
|
|||||||
error={error}
|
error={error}
|
||||||
widgets={widgets}
|
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>
|
{/* {formData.id && <p>
|
||||||
Check out the signup form here: <Link to={`/signup/${formData.id}`}>{formData.title_fi}</Link>
|
Check out the signup form here: <Link to={`/signup/${formData.id}`}>{formData.title_fi}</Link>
|
||||||
</p>} */}
|
</p>} */}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -6,6 +6,7 @@ import Event from "@models/Event";
|
|||||||
import Post from "@models/Feed";
|
import Post from "@models/Feed";
|
||||||
import { SignupForm } from "@models/Signup";
|
import { SignupForm } from "@models/Signup";
|
||||||
import JobAd from "@models/JobAd";
|
import JobAd from "@models/JobAd";
|
||||||
|
import { TemplateQuestion } from "@models/TemplateQuestion";
|
||||||
import FormWrapper from "@views/common/FormWrapper";
|
import FormWrapper from "@views/common/FormWrapper";
|
||||||
import AdminPageWrapper from "@views/common/AdminPageWrapper";
|
import AdminPageWrapper from "@views/common/AdminPageWrapper";
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ const ErrorMsg = styled.p`
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type FormTypes = Event | SignupForm | Post | JobAd;
|
type FormTypes = Event | SignupForm | Post | JobAd | TemplateQuestion;
|
||||||
|
|
||||||
type AdminCreateCommonProps = {
|
type AdminCreateCommonProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user