Merge branch 'feature/signup-improvements' into 'master'

Simple email & list views for Signups

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!15
This commit is contained in:
Aarni Halinen
2020-11-30 19:13:37 +00:00
9 changed files with 241 additions and 9 deletions
+18 -4
View File
@@ -8,7 +8,7 @@ export interface Signup {
answer: string;
}
export async function getSignup(id: number): Promise<Signup> {
export const getSignup = async (id: number): Promise<Signup> => {
try {
const resp = await axios.get(`${url}${id}`, {
headers: {
@@ -22,7 +22,7 @@ export async function getSignup(id: number): Promise<Signup> {
}
}
export async function createSignup(data: Signup): Promise<Signup> {
export const createSignup = async (data: Signup): Promise<Signup> => {
try {
const resp = await axios.post(url, data);
return resp.data;
@@ -32,7 +32,7 @@ export async function createSignup(data: Signup): Promise<Signup> {
}
}
export async function updateSignup(data: Signup, uuid: string): Promise<Signup> {
export const updateSignup = async (data: Signup, uuid: string): Promise<Signup> => {
try {
const { id } = data;
if (!id) throw new Error("SignupId required!");
@@ -46,7 +46,7 @@ export async function updateSignup(data: Signup, uuid: string): Promise<Signup>
}
}
export async function getSignupUUID(id: number, uuid: string): Promise<Signup> {
export const getSignupUUID = async (id: number, uuid: string): Promise<Signup> => {
try {
const resp = await axios.get(`${url}${id}/edit/`, {
params: {
@@ -59,3 +59,17 @@ export async function getSignupUUID(id: number, uuid: string): Promise<Signup> {
throw err;
}
}
export const deleteSignup = async (id: number): Promise<Signup> => {
try {
const resp = await axios.delete(`${url}${id}`, {
headers: {
"Authorization": getAuthHeader()
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
+29
View File
@@ -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<SignupForm> {
throw err;
}
}
export const signupFormSendEmail = async (data, id): Promise<any> => {
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<Signup[]> => {
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;
}
}
+4
View File
@@ -20,6 +20,8 @@ const renderData = (signupForms: SignupForm[]) => {
<th>Title</th>
<th>Start time</th>
<th>End time</th>
<th>Sign-ups</th>
<th>Send email</th>
</tr>
</thead>
<tbody>
@@ -28,6 +30,8 @@ const renderData = (signupForms: SignupForm[]) => {
<td><Anchor to={`${URL}/${signupForm.id}`}>{signupForm.title_fi}</Anchor></td>
<td>{formatRelative(new Date(signupForm.start_time), new Date())}</td>
<td>{formatRelative(new Date(signupForm.end_time), new Date())}</td>
<td><Anchor to={`${URL}/${signupForm.id}/list`}>View</Anchor></td>
<td><Anchor to={`${URL}/${signupForm.id}/email`}>Send</Anchor></td>
</tr>
))}
</tbody>
+105
View File
@@ -0,0 +1,105 @@
import React, { useEffect, useState } from "react";
import { Helmet } from "react-helmet";
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";
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<MatchParams>;
const SignupEmailPage: React.FC<SignupEmailPageProps> = ({ match: { params: { id } } }) => {
const [signupForm, setSignupForm] = useState<SignupForm>(null);
useEffect(() => {
const formId = Number(id);
if (formId !== undefined) {
getForm(formId, true)
.then(res => setSignupForm(res))
}
}, [id])
const [error, setError] = useState<string>(null);
const [statusMessage, setStatusMessage] = useState<string>(null);
const onSubmit = async (data) => {
try {
const payload = data.formData;
await signupFormSendEmail(payload, id);
setStatusMessage("Email sent successfully");
} catch (err) {
setError(err);
}
}
// const onChange = (data) => setFormData(data.formData);
const onFocus = () => setStatusMessage(null);
const title = signupForm ? signupForm.title_fi : "Loading..."
return (
<>
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/admin/jobads/create" />
</Helmet>
<AdminCreateCommon
title={title}
// formData={formData}
schema={buildSchema(title)}
UISchema={buildUISchema()}
// onChange={onChange}
onFocus={onFocus}
onSubmit={onSubmit}
statusMessage={statusMessage}
error={error}
widgets={widgets}
/>
</>
)};
export default SignupEmailPage;
+79
View File
@@ -0,0 +1,79 @@
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, deleteSignup } from "@models/Signup";
interface MatchParams {
id?: string;
}
type SignupEmailPageProps = RouteComponentProps<MatchParams>;
const SignupEmailPage: React.FC<SignupEmailPageProps> = ({ match: { params: { id } } }) => {
const [signupForm, setSignupForm] = useState<SignupForm>(null);
const [signups, setSignups] = useState<Signup[]>([]);
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...";
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 (
<AdminListCommon>
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/admin/jobads/create" />
</Helmet>
<h1>{title}: Sign-ups</h1>
<table>
<thead>
<tr>
{questions.map(q => (
<th key={q.id}>{q.title}</th>
))}
</tr>
</thead>
<tbody>
{signups.map(s => (
<tr key={s.id}>
{questions.map(q => (
<td key={`${s.id} - ${q.id}`}>
{s.answer[q.id]}
</td>
))}
<td>
<button onClick={() => confirmDelete(s, questions[0])}>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
</AdminListCommon>
)};
export default SignupEmailPage;
+4
View File
@@ -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 },
-3
View File
@@ -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,
};
+1 -1
View File
@@ -51,7 +51,7 @@ const StyledSection = styled(TextSection)`
}
.reserved {
color: color(blue1);
color: ${colors.blue1};
}
}
+1 -1
View File
@@ -109,7 +109,7 @@ type AdminCreateCommonProps = {
UISchema: {
[name: string]: any;
};
onChange: (e: IChangeEvent<FormTypes>, es?: ErrorSchema) => any;
onChange?: (e: IChangeEvent<FormTypes>, es?: ErrorSchema) => any;
onFocus: (id: string, value: string | number | boolean) => void;
onSubmit: (e: ISubmitEvent<FormTypes>) => any;
statusMessage: string;