From d666cab77a7943aea3e07e70608294f9acc95364 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Mon, 2 Jan 2023 21:52:20 -0500 Subject: [PATCH] Access UI --- web/src/app/AccountApi.js | 34 ++++++++++++- web/src/app/utils.js | 2 + web/src/components/App.js | 3 -- web/src/components/Preferences.js | 84 +++++++++++++++++++------------ 4 files changed, 87 insertions(+), 36 deletions(-) diff --git a/web/src/app/AccountApi.js b/web/src/app/AccountApi.js index 2b057d9a..cc13452d 100644 --- a/web/src/app/AccountApi.js +++ b/web/src/app/AccountApi.js @@ -12,7 +12,7 @@ import { topicUrl, topicUrlAuth, topicUrlJsonPoll, - topicUrlJsonPollWithSince + topicUrlJsonPollWithSince, accountAccessUrl, accountAccessSingleUrl } from "./utils"; import userManager from "./UserManager"; import session from "./Session"; @@ -208,6 +208,38 @@ class AccountApi { } } + async upsertAccess(topic, everyone) { + const url = accountAccessUrl(config.baseUrl); + console.log(`[AccountApi] Upserting user access to topic ${topic}, everyone=${everyone}`); + const response = await fetch(url, { + method: "POST", + headers: withBearerAuth({}, session.token()), + body: JSON.stringify({ + topic: topic, + everyone: everyone + }) + }); + if (response.status === 401 || response.status === 403) { + throw new UnauthorizedError(); + } else if (response.status !== 200) { + throw new Error(`Unexpected server response ${response.status}`); + } + } + + async deleteAccess(topic) { + const url = accountAccessSingleUrl(config.baseUrl, topic); + console.log(`[AccountApi] Removing topic reservation ${url}`); + const response = await fetch(url, { + method: "DELETE", + headers: withBearerAuth({}, session.token()) + }); + if (response.status === 401 || response.status === 403) { + throw new UnauthorizedError(); + } else if (response.status !== 200) { + throw new Error(`Unexpected server response ${response.status}`); + } + } + sync() { // TODO } diff --git a/web/src/app/utils.js b/web/src/app/utils.js index 6170cc25..fdddab05 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -24,6 +24,8 @@ export const accountTokenUrl = (baseUrl) => `${baseUrl}/v1/account/token`; export const accountSettingsUrl = (baseUrl) => `${baseUrl}/v1/account/settings`; export const accountSubscriptionUrl = (baseUrl) => `${baseUrl}/v1/account/subscription`; export const accountSubscriptionSingleUrl = (baseUrl, id) => `${baseUrl}/v1/account/subscription/${id}`; +export const accountAccessUrl = (baseUrl) => `${baseUrl}/v1/account/access`; +export const accountAccessSingleUrl = (baseUrl, topic) => `${baseUrl}/v1/account/access/${topic}`; export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, ""); export const expandUrl = (url) => [`https://${url}`, `http://${url}`]; export const expandSecureUrl = (url) => `https://${url}`; diff --git a/web/src/components/App.js b/web/src/components/App.js index 6c714bfb..1374d8dd 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -33,9 +33,6 @@ import Account from "./Account"; import ResetPassword from "./ResetPassword"; import accountApi, {UnauthorizedError} from "../app/AccountApi"; -// TODO races when two tabs are open -// TODO investigate service workers - const App = () => { return ( }> diff --git a/web/src/components/Preferences.js b/web/src/components/Preferences.js index b46e4617..32c48d2f 100644 --- a/web/src/components/Preferences.js +++ b/web/src/components/Preferences.js @@ -33,7 +33,7 @@ import DialogTitle from "@mui/material/DialogTitle"; import DialogContent from "@mui/material/DialogContent"; import DialogActions from "@mui/material/DialogActions"; import userManager from "../app/UserManager"; -import {playSound, shuffle, sounds, validUrl} from "../app/utils"; +import {playSound, shuffle, sounds, validTopic, validUrl} from "../app/utils"; import {useTranslation} from "react-i18next"; import session from "../app/Session"; import routes from "./routes"; @@ -491,14 +491,15 @@ const Reservations = () => { setDialogOpen(false); }; - const handleDialogSubmit = async (entry) => { + const handleDialogSubmit = async (reservation) => { setDialogOpen(false); try { - await accountApi.addAccessEntry(); - console.debug(`[Preferences] Added entry ${entry.topic}`); + await accountApi.upsertAccess(reservation.topic, reservation.everyone); + console.debug(`[Preferences] Added topic reservation`, reservation); } catch (e) { - console.log(`[Preferences] Error adding access entry.`, e); + console.log(`[Preferences] Error topic reservation.`, e); } + // FIXME handle 401/403 }; if (!session.exists() || !account) { @@ -519,10 +520,10 @@ const Reservations = () => { @@ -535,11 +536,11 @@ const ReservationsTable = (props) => { const { t } = useTranslation(); const [dialogKey, setDialogKey] = useState(0); const [dialogOpen, setDialogOpen] = useState(false); - const [dialogEntry, setDialogEntry] = useState(null); + const [dialogReservation, setDialogReservation] = useState(null); - const handleEditClick = (entry) => { + const handleEditClick = (reservation) => { setDialogKey(prev => prev+1); - setDialogEntry(entry); + setDialogReservation(reservation); setDialogOpen(true); }; @@ -547,13 +548,25 @@ const ReservationsTable = (props) => { setDialogOpen(false); }; - const handleDialogSubmit = async (user) => { + const handleDialogSubmit = async (reservation) => { setDialogOpen(false); - // FIXME + try { + await accountApi.upsertAccess(reservation.topic, reservation.everyone); + console.debug(`[Preferences] Added topic reservation`, reservation); + } catch (e) { + console.log(`[Preferences] Error topic reservation.`, e); + } + // FIXME handle 401/403 }; - const handleDeleteClick = async (user) => { - // FIXME + const handleDeleteClick = async (reservation) => { + try { + await accountApi.deleteAccess(reservation.topic); + console.debug(`[Preferences] Deleted topic reservation`, reservation); + } catch (e) { + console.log(`[Preferences] Error topic reservation.`, e); + } + // FIXME handle 401/403 }; return ( @@ -575,25 +588,25 @@ const ReservationsTable = (props) => { {reservation.everyone === "read-write" && <> - + {t("prefs_reservations_table_everyone_read_write")} } {reservation.everyone === "read-only" && <> - + {t("prefs_reservations_table_everyone_read_only")} } {reservation.everyone === "write-only" && <> - + {t("prefs_reservations_table_everyone_write_only")} } {reservation.everyone === "deny-all" && <> - + {t("prefs_reservations_table_everyone_deny_all")} } @@ -610,10 +623,10 @@ const ReservationsTable = (props) => { ))} @@ -624,24 +637,31 @@ const ReservationsTable = (props) => { const ReservationsDialog = (props) => { const { t } = useTranslation(); const [topic, setTopic] = useState(""); - const [access, setAccess] = useState("private"); + const [everyone, setEveryone] = useState("deny-all"); const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); - const editMode = props.entry !== null; + const editMode = props.reservation !== null; const addButtonEnabled = (() => { - // FIXME + if (editMode) { + return true; + } else if (!validTopic(topic)) { + return false; + } + return props.reservations + .filter(r => r.topic === topic) + .length === 0; })(); const handleSubmit = async () => { props.onSubmit({ - topic: topic, - // FIXME + topic: (editMode) ? props.reservation.topic : topic, + everyone: everyone }) }; useEffect(() => { if (editMode) { - setTopic(props.topic); - //setAccess(props.access); + setTopic(props.reservation.topic); + setEveryone(props.reservation.everyone); } - }, [editMode, props]); + }, [editMode, props.reservation]); return ( {editMode ? t("prefs_reservations_dialog_title_edit") : t("prefs_reservations_dialog_title_add")} @@ -660,8 +680,8 @@ const ReservationsDialog = (props) => { />}