From fd5d6fa71bb43e994e5ace3636b4b120f2319a58 Mon Sep 17 00:00:00 2001 From: Dominic Zimmer Date: Sun, 16 Oct 2022 18:18:49 +0200 Subject: [PATCH] Implement party editing, Creation and Deletion --- src/AdminUI.css | 18 +++++++- src/AdminUI.tsx | 87 ++++++++++++++++++++++++++++++++------- src/partyAdminApi.ts | 30 +++++++++++--- src/partyAdminApiTypes.ts | 22 ++++++---- 4 files changed, 126 insertions(+), 31 deletions(-) diff --git a/src/AdminUI.css b/src/AdminUI.css index e625c7e..da0ffe7 100644 --- a/src/AdminUI.css +++ b/src/AdminUI.css @@ -155,11 +155,16 @@ dialog > div label { top: 0px; left:0px; } -.dialog-table { +.dialog-person-table { display: grid; gap: 5px; grid-template-columns: 1fr 1fr; } +.dialog-party-table { + display: grid; + gap: 5px; + grid-template-columns: 1fr auto 1fr auto; +} .add-guest-button { padding: 5px; border-radius: 4px; @@ -171,7 +176,16 @@ dialog > div label { width: 100%; justify-content: space-between; } -.add-guest > .tip { +.danger-hint { + align-self: flex-end; + font-size: 8pt; + color: red; + flex-wrap:nowrap; +} +.danger-hint > pre { + display: inline-block; +} +.tip { align-self: flex-end; font-size: 8pt; color: gray; diff --git a/src/AdminUI.tsx b/src/AdminUI.tsx index 27ff61e..4163c49 100644 --- a/src/AdminUI.tsx +++ b/src/AdminUI.tsx @@ -1,21 +1,22 @@ - import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; import './AdminUI.css'; -import { createGuestRequest, deleteGuestRequest, listGuestsRequest, listPartyRequest, modifyGuestRequest, parseToken } from './partyAdminApi'; +import { createGuestRequest, createPartyRequest, deleteGuestRequest, deletePartyRequest, listGuestsRequest, listPartyRequest, modifyGuestRequest, parseToken, setAllowedExtras } from './partyAdminApi'; import { GrammaticalGender, Person, RequestCreateGuest, ResponseCreateParty, ResponseListGuests, ResponseListParties } from './partyAdminApiTypes'; export const AdminUI: React.FC = () => { + const [editingParty, setEditingParty] = useState(false); const [partyList, setPartyList] = useState([]); const [selectedParty, setSelectedParty] = useState(undefined); // eslint-disable-next-line no-restricted-globals const adminToken = useMemo(() => parseToken(location.href) ?? "", []); const loadPartyList = useCallback(async () => { - const response = await listPartyRequest(adminToken); + console.log("load party list"); + const listPartyResponse = await listPartyRequest(adminToken); if (selectedParty === undefined) - setSelectedParty(response.length > 0 ? 0 : undefined); - setPartyList(response); + setSelectedParty(listPartyResponse.length > 0 ? 0 : undefined); + setPartyList([...listPartyResponse]); }, [adminToken, selectedParty]); const deleteUser = async () => { @@ -33,11 +34,46 @@ export const AdminUI: React.FC = () => { dialog.show(); }; - const [editUserData, setEditUserData] = useState(); - const dismissBackdrop = () => { - const dialog = document.getElementById("dialog-edit-user") as HTMLDialogElement; + const editParty = () => { + const dialog = document.getElementById("dialog-edit-party") as HTMLDialogElement; + if (!dialog) return; + setEditingParty(true); + dialog.show(); + }; + const [editUserData, setEditUserData] = useState(); + + const createParty = () => { + const element = document.getElementById("create-party") as HTMLInputElement; + if (!element) return; + const partyName = element.value; + if (!partyName) return; + createPartyRequest(adminToken, { allowed_extra: {}, name: partyName }); + loadPartyList(); + dismissBackdrop(); + }; + + const updateExtras = (partyName: string) => { + const element = document.getElementById(`payload-${partyName}`) as HTMLInputElement; + if (!element) return; + const newExtras = element.value; + if (newExtras === "deletethis") { + deletePartyRequest(adminToken, partyName); + } else { + const payload = JSON.parse(newExtras); + setAllowedExtras(adminToken, partyName, payload); + } + loadPartyList(); + dismissBackdrop(); + }; + + const dismissBackdrop = (e?: React.MouseEvent) => { + e?.stopPropagation(); + ["dialog-edit-user", "dialog-edit-party"].forEach(elementId => { + const dialog = document.getElementById(elementId) as HTMLDialogElement; + dialog.close(); + }) setEditUserData(undefined); - dialog.close(); + setEditingParty(false); }; const saveEditUser = () => { @@ -56,17 +92,36 @@ export const AdminUI: React.FC = () => { if (adminToken === "") return; loadPartyList(); }, [adminToken, loadPartyList]); - + return <> - {editUserData ? + {editUserData || editingParty ?
: null} + +
+ Edit Parties +
+ {partyList.map((p, i)=> <> + {p.name} + + + + )} +
+ To delete a party, enter
deletethis
and press update!
+
+ Create a party: + + +
+
+
{editUserData ? <> - {"_id" in editUserData ? `Editing ${editUserData.name}`: "New Invitation"} -
+ {"_id" in editUserData ? `Editing ${editUserData.name}` : "New Invitation"} +
{ setEditUserData({ ...editUserData, token: e.target.value }) }} /> @@ -108,12 +163,12 @@ export const AdminUI: React.FC = () => {
{partyList.map((p, index) => -
- setSelectedParty(index)} key={index}>{p.name} +
setSelectedParty(index)} className={`select-party ${index === selectedParty ? "selected" : ""}`}> + {p.name}
)}
-
+
editParty()}> 🎉 diff --git a/src/partyAdminApi.ts b/src/partyAdminApi.ts index 37d6e78..661e588 100644 --- a/src/partyAdminApi.ts +++ b/src/partyAdminApi.ts @@ -1,4 +1,4 @@ -import { Person, RequestCreateGuest, RequestCreateParty, ResponseCreateParty, ResponseListGuests, ResponseListParties } from "./partyAdminApiTypes"; +import { AllowedExtraLengths, Person, RequestCreateGuest, RequestCreateParty, ResponseCreateParty, ResponseListGuests, ResponseListParties } from "./partyAdminApiTypes"; export const parseToken = (uri: string): string | undefined => { const x = uri.match(/https?:\/\/party\.leafbla\.de\/(?.+)/); @@ -20,7 +20,7 @@ export const listPartyRequest = async (adminToken: string): Promise => { - const result = await fetch(`${apiUrl(adminToken)}`, { method: "post", body: JSON.stringify(payload) }); + const result = await fetch(`${apiUrl(adminToken)}`, { method: "post", body: JSON.stringify(payload), headers: { "Content-Type": "application/json" } }); if (!result.ok) throw new Error("Error sending createPartyRequest"); const data = await result.json(); return data as ResponseCreateParty; @@ -47,9 +47,29 @@ export const modifyGuestRequest = async (adminToken: string, partyName: string, return data as Person; }; -export const deleteGuestRequest = async (adminToken: string, partyName: string, payload: Person): Promise => { +export const deleteGuestRequest = async (adminToken: string, partyName: string, payload: Person): Promise => { const result = await fetch(`${apiUrl(adminToken)}/${partyName}/${payload._id}`, { method: "DELETE", headers: { "Content-Type": "application/json" } }); if (!result.ok) throw new Error("Error sending createGuestRequest"); +}; + +export const deletePartyRequest = async (adminToken: string, partyName: string): Promise => { + const result = await fetch(`${apiUrl(adminToken)}/${partyName}`, { method: "DELETE" }); + if (!result.ok) throw new Error("Error sending deletePartyRequest"); +}; + +/** + * @deprecated This info can already be found in the listPartiesResponse + */ +export const getAllowedExtras = async (adminToken: string, partyName: string): Promise => { + const result = await fetch(`${apiUrl(adminToken)}/${partyName}/userAllowedExtra`, { method: "get" }); + if (!result.ok) throw new Error("Error getting allowed extras"); const data = await result.json(); - return data as Person; -}; \ No newline at end of file + return data as AllowedExtraLengths; +}; + +export const setAllowedExtras = async (adminToken: string, partyName: string, payload: AllowedExtraLengths): Promise => { + const result = await fetch(`${apiUrl(adminToken)}/${partyName}/userAllowedExtra`, { headers: { "Content-Type": "application/json" }, method: "PATCH", body: JSON.stringify(payload) }); + if (!result.ok) throw new Error("Error setting allowed extras"); +}; + + diff --git a/src/partyAdminApiTypes.ts b/src/partyAdminApiTypes.ts index f14b39d..ac6a5f5 100644 --- a/src/partyAdminApiTypes.ts +++ b/src/partyAdminApiTypes.ts @@ -1,16 +1,16 @@ export type ResponseListParties = ResponseCreateParty[]; export type ResponseCreateParty = { - "_id": string, + _id: string, name: string, created: string, - allowedExtra: AllowedExtraLengths, + allowed_extra: AllowedExtraLengths, } export type AllowedExtraLengths = { [key: string]: number }; export type RequestCreateParty = { - name: "string", - "allowed_extra": AllowedExtraLengths, + name: string, + allowed_extra: AllowedExtraLengths, } export type ResponseListGuests = Person[]; @@ -20,11 +20,11 @@ export type GrammaticalGender = "m" | "f" | "d"; export type ComingStatus = "yes" | "no" | "maybe" | null; export type Person = { - "_id": string, + _id: string, token: string, name: string, coming: ComingStatus, - "grammatical_gender": GrammaticalGender, + grammatical_gender: GrammaticalGender, extra: Record, }; @@ -32,6 +32,12 @@ export type RequestCreateGuest = { token: string, name: string, coming: ComingStatus, - "grammatical_gender": GrammaticalGender, + grammatical_gender: GrammaticalGender, extra: Record, -}; \ No newline at end of file +}; + +export type CreatePartyRequest = { + name: string, + allowed_extra: AllowedExtraLengths, +} +