From acaea6934b138ced1a2481d55d61196e42b4e4de Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Sun, 8 Nov 2020 00:15:00 +0200 Subject: [PATCH 1/5] Simple email & list views for Signups --- src/models/SignupForm.ts | 29 ++++++++ src/pages/admin/AdminSignupPage.tsx | 4 + src/pages/admin/SignupEmailPage.tsx | 110 ++++++++++++++++++++++++++++ src/pages/admin/SignupListPage.tsx | 62 ++++++++++++++++ src/routes.tsx | 6 +- 5 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 src/pages/admin/SignupEmailPage.tsx create mode 100644 src/pages/admin/SignupListPage.tsx diff --git a/src/models/SignupForm.ts b/src/models/SignupForm.ts index 923ee86..b17f737 100644 --- a/src/models/SignupForm.ts +++ b/src/models/SignupForm.ts @@ -2,6 +2,7 @@ import axios from "axios"; import { getAuthHeader } from "@utils/auth"; const url = `${process.env.API_URL}/signupForm/`; import { Question } from "@components/Widgets/SignupQuestionsWidget"; +import { Signup } from "./Signup"; export interface SignupForm { id?: number; @@ -77,3 +78,31 @@ export async function updateForm(data): Promise { throw err; } } + +export const signupFormSendEmail = async (data, id): Promise => { + try { + const resp = await axios.post(`${process.env.API_URL}/signupForm/${id}/sendemail/`, data, { + headers: { + "Authorization": getAuthHeader(), + }, + }); + return resp.data; + } catch (err) { + console.error(err); + throw err; + } +} + +export const getSignups = async (id): Promise => { + try { + const resp = await axios.get(`${process.env.API_URL}/signupForm/${id}/signups/`, { + headers: { + "Authorization": getAuthHeader(), + }, + }); + return resp.data; + } catch (err) { + console.error(err); + throw err; + } +} diff --git a/src/pages/admin/AdminSignupPage.tsx b/src/pages/admin/AdminSignupPage.tsx index 7051cbb..eacf8a0 100644 --- a/src/pages/admin/AdminSignupPage.tsx +++ b/src/pages/admin/AdminSignupPage.tsx @@ -85,6 +85,8 @@ class AdminSignupPage extends React.ComponentTitle Start time End time + Sign-ups + Send email @@ -93,6 +95,8 @@ class AdminSignupPage extends React.Component{signupForm.title_fi} {formatRelative(new Date(signupForm.start_time), new Date())} {formatRelative(new Date(signupForm.end_time), new Date())} + View + Send ))} diff --git a/src/pages/admin/SignupEmailPage.tsx b/src/pages/admin/SignupEmailPage.tsx new file mode 100644 index 0000000..7a4581d --- /dev/null +++ b/src/pages/admin/SignupEmailPage.tsx @@ -0,0 +1,110 @@ +import React, { useEffect, useState } from "react"; +import { Helmet } from "react-helmet"; +import Form from "react-jsonschema-form"; +import { RouteComponentProps } from "react-router-dom"; +import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget"; +import { SignupForm, getForm, signupFormSendEmail } from "@models/SignupForm"; + +const widgets = { + markdownEditor: MarkdownEditorWidget +}; + +const buildSchema = (title: string) => { + return { + title, + type: "object", + required: ["subject", "content", "mode"], + properties: { + subject: { + type: "string", + title: "Title", + default: "" + }, + content: { + type: "string", + title: "Content", + default: "", + }, + mode: { + type: "string", + title: "Send to", + enum : [ + "all", + "actual", + "reserved" + ], + default: "all", + } + } + }; +} + +const buildUISchema = () => ({ + content: { + "ui:widget": "markdownEditor", + }, +}); + +interface MatchParams { + id?: string; +} + +type SignupEmailPageProps = RouteComponentProps; + + +const SignupEmailPage: React.FC = ({ match: { params: { id } } }) => { + const [signupForm, setSignupForm] = useState(null); + useEffect(() => { + const formId = Number(id); + if (formId !== undefined) { + getForm(formId, true) + .then(res => setSignupForm(res)) + } + }, [id]) + + + + const [error, setError] = useState(null); + const [statusMessage, setStatusMessage] = useState(null); + + const onSubmit = async (data) => { + try { + const payload = data.formData; + await signupFormSendEmail(payload, id); + setStatusMessage("Email sent successfully"); + } catch (err) { + setError(err); + } + } + + const onError = (data) => { + console.error("error, data:"); + console.log(data); + } + // const onChange = (data) => setFormData(data.formData); + const onFocus = () => setStatusMessage(null); + + const title = signupForm ? signupForm.title_fi : "Loading..." + + return ( +
+ + + +

{title}

+ {statusMessage &&
{statusMessage}
} +
+ {error &&
{error}
} +
+ )}; + +export default SignupEmailPage; diff --git a/src/pages/admin/SignupListPage.tsx b/src/pages/admin/SignupListPage.tsx new file mode 100644 index 0000000..92994df --- /dev/null +++ b/src/pages/admin/SignupListPage.tsx @@ -0,0 +1,62 @@ +import React, { useEffect, useState } from "react"; +import { Helmet } from "react-helmet"; +import { RouteComponentProps } from "react-router-dom"; +import { SignupForm, getForm, getSignups } from "@models/SignupForm"; +import { Signup } from "@models/Signup"; + +interface MatchParams { + id?: string; +} + +type SignupEmailPageProps = RouteComponentProps; + + +const SignupEmailPage: React.FC = ({ match: { params: { id } } }) => { + const [signupForm, setSignupForm] = useState(null); + const [signups, setSignups] = useState([]); + useEffect(() => { + const formId = Number(id); + getForm(formId, true) + .then(res => setSignupForm(res)) + + getSignups(formId).then(res => setSignups(res)) + + }, [id]); + + const questions = signupForm ? signupForm.questions.map(q => ({ + title: q.name, + id: q.id + })) : []; + + const title = signupForm ? signupForm.title_fi : "Loading..." + + return ( +
+ + + +

{title}: Sign-ups

+ + + + {questions.map(q => ( + + ))} + + + + {signups.map(s => ( + + {questions.map(q => ( + + ))} + + ))} + +
{q.title}
+ {s.answer[q.id]} +
+
+ )}; + +export default SignupEmailPage; diff --git a/src/routes.tsx b/src/routes.tsx index f3ff88f..ec10765 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Switch, Route, Redirect } from "react-router-dom"; +import { Switch, Route } from "react-router-dom"; import { Helmet } from "react-helmet"; import FrontPage from "./pages/FrontPage"; import GuildPage from "./pages/GuildPage"; @@ -18,6 +18,8 @@ import EventCreatePage from "./pages/admin/EventCreatePage"; import FeedCreatePage from "./pages/admin/FeedCreatePage"; import ContactsPage from "./pages/ContactsPage"; import SignupCreatePage from "./pages/admin/SignupCreatePage"; +import SignupEmailPage from "./pages/admin/SignupEmailPage"; +import SignupListPage from "./pages/admin/SignupListPage"; import SignUpPage from "./pages/SignUpPage"; import ActualPage from "./pages/ActualPage"; import FreshmenPage from "./pages/FreshmenPage"; @@ -70,6 +72,8 @@ const adminRoutes = [ { path: "/admin/signups", page: AdminSignupPage }, { path: "/admin/signups/create", page: SignupCreatePage }, { path: "/admin/signups/:id", page: SignupCreatePage }, + { path: "/admin/signups/:id/list", page: SignupListPage }, + { path: "/admin/signups/:id/email", page: SignupEmailPage }, { path: "/admin/jobads", page: AdminJobAdPage }, { path: "/admin/jobads/create", page: JobAdCreatePage }, { path: "/admin/jobads/:id", page: JobAdCreatePage }, From 70c77f6ba87c742210e2fe1fa129d9e6cb0ce8ab Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Wed, 25 Nov 2020 00:01:07 +0200 Subject: [PATCH 2/5] Use new common views for styles --- src/pages/admin/SignupEmailPage.tsx | 31 +++++++++++++-------------- src/pages/admin/SignupListPage.tsx | 5 +++-- src/views/admin/AdminCreateCommon.tsx | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/pages/admin/SignupEmailPage.tsx b/src/pages/admin/SignupEmailPage.tsx index 7a4581d..b5997d8 100644 --- a/src/pages/admin/SignupEmailPage.tsx +++ b/src/pages/admin/SignupEmailPage.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import { Helmet } from "react-helmet"; -import Form from "react-jsonschema-form"; import { RouteComponentProps } from "react-router-dom"; +import AdminCreateCommon from "@views/admin/AdminCreateCommon"; import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget"; import { SignupForm, getForm, signupFormSendEmail } from "@models/SignupForm"; @@ -87,24 +87,23 @@ const SignupEmailPage: React.FC = ({ match: { params: { id const title = signupForm ? signupForm.title_fi : "Loading..." return ( -
+ <> -

{title}

- {statusMessage &&
{statusMessage}
} - - {error &&
{error}
} -
+ + )}; export default SignupEmailPage; diff --git a/src/pages/admin/SignupListPage.tsx b/src/pages/admin/SignupListPage.tsx index 92994df..e030d07 100644 --- a/src/pages/admin/SignupListPage.tsx +++ b/src/pages/admin/SignupListPage.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from "react"; import { Helmet } from "react-helmet"; import { RouteComponentProps } from "react-router-dom"; +import AdminListCommon from "@views/admin/AdminListCommon"; import { SignupForm, getForm, getSignups } from "@models/SignupForm"; import { Signup } from "@models/Signup"; @@ -31,7 +32,7 @@ const SignupEmailPage: React.FC = ({ match: { params: { id const title = signupForm ? signupForm.title_fi : "Loading..." return ( -
+ @@ -56,7 +57,7 @@ const SignupEmailPage: React.FC = ({ match: { params: { id ))} -
+ )}; export default SignupEmailPage; diff --git a/src/views/admin/AdminCreateCommon.tsx b/src/views/admin/AdminCreateCommon.tsx index 3a9b750..03dd22f 100644 --- a/src/views/admin/AdminCreateCommon.tsx +++ b/src/views/admin/AdminCreateCommon.tsx @@ -109,7 +109,7 @@ type AdminCreateCommonProps = { UISchema: { [name: string]: any; }; - onChange: (e: IChangeEvent, es?: ErrorSchema) => any; + onChange?: (e: IChangeEvent, es?: ErrorSchema) => any; onFocus: (id: string, value: string | number | boolean) => void; onSubmit: (e: ISubmitEvent) => any; statusMessage: string; From 6e3a916a3ba431414fc45d0b21b1eabd12558fd3 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Mon, 30 Nov 2020 20:11:55 +0200 Subject: [PATCH 3/5] Fix lint --- src/pages/admin/SignupEmailPage.tsx | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/pages/admin/SignupEmailPage.tsx b/src/pages/admin/SignupEmailPage.tsx index b5997d8..8cd84bf 100644 --- a/src/pages/admin/SignupEmailPage.tsx +++ b/src/pages/admin/SignupEmailPage.tsx @@ -77,10 +77,6 @@ const SignupEmailPage: React.FC = ({ match: { params: { id } } - const onError = (data) => { - console.error("error, data:"); - console.log(data); - } // const onChange = (data) => setFormData(data.formData); const onFocus = () => setStatusMessage(null); @@ -92,16 +88,16 @@ const SignupEmailPage: React.FC = ({ match: { params: { id )}; From 6dd8d9c8cb6c0289d85de8d5f728e95f58d2fa9c Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Mon, 30 Nov 2020 20:39:32 +0200 Subject: [PATCH 4/5] Simple delete functionality to Signup admin --- src/models/Signup.ts | 22 ++++++++++++++++++---- src/pages/admin/SignupListPage.tsx | 20 ++++++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/models/Signup.ts b/src/models/Signup.ts index 3e11a4e..d788e8f 100644 --- a/src/models/Signup.ts +++ b/src/models/Signup.ts @@ -8,7 +8,7 @@ export interface Signup { answer: string; } -export async function getSignup(id: number): Promise { +export const getSignup = async (id: number): Promise => { try { const resp = await axios.get(`${url}${id}`, { headers: { @@ -22,7 +22,7 @@ export async function getSignup(id: number): Promise { } } -export async function createSignup(data: Signup): Promise { +export const createSignup = async (data: Signup): Promise => { try { const resp = await axios.post(url, data); return resp.data; @@ -32,7 +32,7 @@ export async function createSignup(data: Signup): Promise { } } -export async function updateSignup(data: Signup, uuid: string): Promise { +export const updateSignup = async (data: Signup, uuid: string): Promise => { try { const { id } = data; if (!id) throw new Error("SignupId required!"); @@ -46,7 +46,7 @@ export async function updateSignup(data: Signup, uuid: string): Promise } } -export async function getSignupUUID(id: number, uuid: string): Promise { +export const getSignupUUID = async (id: number, uuid: string): Promise => { try { const resp = await axios.get(`${url}${id}/edit/`, { params: { @@ -59,3 +59,17 @@ export async function getSignupUUID(id: number, uuid: string): Promise { throw err; } } + +export const deleteSignup = async (id: number): Promise => { + try { + const resp = await axios.delete(`${url}${id}`, { + headers: { + "Authorization": getAuthHeader() + }, + }); + return resp.data; + } catch (err) { + console.error(err); + throw err; + } +} diff --git a/src/pages/admin/SignupListPage.tsx b/src/pages/admin/SignupListPage.tsx index e030d07..186bed7 100644 --- a/src/pages/admin/SignupListPage.tsx +++ b/src/pages/admin/SignupListPage.tsx @@ -3,7 +3,7 @@ import { Helmet } from "react-helmet"; import { RouteComponentProps } from "react-router-dom"; import AdminListCommon from "@views/admin/AdminListCommon"; import { SignupForm, getForm, getSignups } from "@models/SignupForm"; -import { Signup } from "@models/Signup"; +import { Signup, deleteSignup } from "@models/Signup"; interface MatchParams { id?: string; @@ -29,7 +29,18 @@ const SignupEmailPage: React.FC = ({ match: { params: { id id: q.id })) : []; - const title = signupForm ? signupForm.title_fi : "Loading..." + const title = signupForm ? signupForm.title_fi : "Loading..."; + + const confirmDelete = async (signup: Signup, question: any) => { + if(confirm(`Delete: ${signup.id}: ${signup.answer[question.id]}; Are you sure?`) === true) { + try { + await deleteSignup(signup.id); + setSignups(signups.filter(s => s.id !== signup.id)) + } catch (err) { + alert("Delete failed!") + } + } + } return ( @@ -53,6 +64,11 @@ const SignupEmailPage: React.FC = ({ match: { params: { id {s.answer[q.id]} ))} + + + ))} From 024291c16563d70745b78e152c2067c6becb7c93 Mon Sep 17 00:00:00 2001 From: Aarni Halinen Date: Mon, 30 Nov 2020 20:46:43 +0200 Subject: [PATCH 5/5] Fix SignupPage --- src/views/SignUpPage/FormUtils.tsx | 3 --- src/views/SignUpPage/SignUpPageView.tsx | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/views/SignUpPage/FormUtils.tsx b/src/views/SignUpPage/FormUtils.tsx index b07af3b..7636779 100644 --- a/src/views/SignUpPage/FormUtils.tsx +++ b/src/views/SignUpPage/FormUtils.tsx @@ -16,9 +16,6 @@ const questionToUISchemaProp = (question: Question) => { "ui:widget": "radio", } } - else { - throw new Error(`No mapping to UI schema prop for question type ${question.type}`); - } return { [question.id]: obj, }; diff --git a/src/views/SignUpPage/SignUpPageView.tsx b/src/views/SignUpPage/SignUpPageView.tsx index 530a79d..7d425f6 100644 --- a/src/views/SignUpPage/SignUpPageView.tsx +++ b/src/views/SignUpPage/SignUpPageView.tsx @@ -51,7 +51,7 @@ const StyledSection = styled(TextSection)` } .reserved { - color: color(blue1); + color: ${colors.blue1}; } }