Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 514d2f4c2e |
@@ -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;
|
||||
|
||||
@@ -49,6 +49,7 @@ const AdminSidebar: React.FC<AdminSidebarProps> = ({ path }) => (
|
||||
<StyledLink to="/admin/events" passHref $path={path}>Events ›</StyledLink>
|
||||
<StyledLink to="/admin/feed" passHref $path={path}>Feed ›</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="https://static.sahkoinsinoorikilta.fi/admin" passHref $path={path}>Files ›</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 { 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>} */}
|
||||
|
||||
@@ -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 { 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;
|
||||
|
||||
Reference in New Issue
Block a user