generated from partypages/party-template
Implement party editing, Creation and Deletion
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
74661bdb42
commit
fd5d6fa71b
@ -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;
|
||||
|
@ -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<ResponseListParties>([]);
|
||||
const [selectedParty, setSelectedParty] = useState<number | undefined>(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<RequestCreateGuest|Person>();
|
||||
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<RequestCreateGuest | Person>();
|
||||
|
||||
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<HTMLDivElement, 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 ?
|
||||
<div id="backdrop" onClick={dismissBackdrop} />
|
||||
: null}
|
||||
<dialog id="dialog-edit-party">
|
||||
<div>
|
||||
<span className="lg">Edit Parties</span>
|
||||
<div className="dialog-party-table">
|
||||
{partyList.map((p, i)=> <>
|
||||
<b>{p.name}</b>
|
||||
<label htmlFor={`edit-keys-${p.name}`}>ExtraData:</label>
|
||||
<input type="text" id={`payload-${p.name}`} defaultValue={JSON.stringify(p.allowed_extra)} />
|
||||
<button onClick={() => updateExtras(p.name)}>update</button>
|
||||
</>)}
|
||||
</div>
|
||||
<span className="danger-hint">To delete a party, enter <pre>deletethis</pre> and press update!</span>
|
||||
<div className="dialog-bottom">
|
||||
<span>Create a party:</span>
|
||||
<input type="text" id={"create-party"} />
|
||||
<button onClick={() => { createParty() }}>Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog id="dialog-edit-user">
|
||||
<div>
|
||||
{editUserData ?
|
||||
<>
|
||||
<span className="lg">{"_id" in editUserData ? `Editing ${editUserData.name}`: "New Invitation"}</span>
|
||||
<div className="dialog-table">
|
||||
<span className="lg">{"_id" in editUserData ? `Editing ${editUserData.name}` : "New Invitation"}</span>
|
||||
<div className="dialog-person-table">
|
||||
<label htmlFor="edit-token">Token</label>
|
||||
<input type="text" id="edit-token" value={editUserData.token} onChange={(e) => { setEditUserData({ ...editUserData, token: e.target.value }) }} />
|
||||
<label htmlFor="edit-name">Name</label>
|
||||
@ -108,12 +163,12 @@ export const AdminUI: React.FC = () => {
|
||||
</div>
|
||||
<div className="select-party-box">
|
||||
{partyList.map((p, index) =>
|
||||
<div className={`select-party ${index === selectedParty ? "selected" : ""}`}>
|
||||
<span onClick={() => setSelectedParty(index)} key={index}>{p.name}</span>
|
||||
<div key={index} onClick={() => setSelectedParty(index)} className={`select-party ${index === selectedParty ? "selected" : ""}`}>
|
||||
<span>{p.name}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div title="Create New Party" className="create-party-label">
|
||||
<div title="Create New Party" className="create-party-label" onClick={() => editParty()}>
|
||||
<span>
|
||||
🎉
|
||||
</span>
|
||||
|
@ -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\/(?<token>.+)/);
|
||||
@ -20,7 +20,7 @@ export const listPartyRequest = async (adminToken: string): Promise<ResponseList
|
||||
};
|
||||
|
||||
export const createPartyRequest = async (adminToken: string, payload: RequestCreateParty): Promise<ResponseCreateParty> => {
|
||||
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<Person> => {
|
||||
export const deleteGuestRequest = async (adminToken: string, partyName: string, payload: Person): Promise<void> => {
|
||||
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<void> => {
|
||||
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<AllowedExtraLengths> => {
|
||||
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;
|
||||
};
|
||||
return data as AllowedExtraLengths;
|
||||
};
|
||||
|
||||
export const setAllowedExtras = async (adminToken: string, partyName: string, payload: AllowedExtraLengths): Promise<void> => {
|
||||
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");
|
||||
};
|
||||
|
||||
|
||||
|
@ -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<string, string>,
|
||||
};
|
||||
|
||||
@ -32,6 +32,12 @@ export type RequestCreateGuest = {
|
||||
token: string,
|
||||
name: string,
|
||||
coming: ComingStatus,
|
||||
"grammatical_gender": GrammaticalGender,
|
||||
grammatical_gender: GrammaticalGender,
|
||||
extra: Record<string, string>,
|
||||
};
|
||||
};
|
||||
|
||||
export type CreatePartyRequest = {
|
||||
name: string,
|
||||
allowed_extra: AllowedExtraLengths,
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user