Access UI

This commit is contained in:
binwiederhier 2023-01-02 21:52:20 -05:00
parent 4b9d40464c
commit d666cab77a
4 changed files with 87 additions and 36 deletions

View file

@ -12,7 +12,7 @@ import {
topicUrl, topicUrl,
topicUrlAuth, topicUrlAuth,
topicUrlJsonPoll, topicUrlJsonPoll,
topicUrlJsonPollWithSince topicUrlJsonPollWithSince, accountAccessUrl, accountAccessSingleUrl
} from "./utils"; } from "./utils";
import userManager from "./UserManager"; import userManager from "./UserManager";
import session from "./Session"; 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() { sync() {
// TODO // TODO
} }

View file

@ -24,6 +24,8 @@ export const accountTokenUrl = (baseUrl) => `${baseUrl}/v1/account/token`;
export const accountSettingsUrl = (baseUrl) => `${baseUrl}/v1/account/settings`; export const accountSettingsUrl = (baseUrl) => `${baseUrl}/v1/account/settings`;
export const accountSubscriptionUrl = (baseUrl) => `${baseUrl}/v1/account/subscription`; export const accountSubscriptionUrl = (baseUrl) => `${baseUrl}/v1/account/subscription`;
export const accountSubscriptionSingleUrl = (baseUrl, id) => `${baseUrl}/v1/account/subscription/${id}`; 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 shortUrl = (url) => url.replaceAll(/https?:\/\//g, "");
export const expandUrl = (url) => [`https://${url}`, `http://${url}`]; export const expandUrl = (url) => [`https://${url}`, `http://${url}`];
export const expandSecureUrl = (url) => `https://${url}`; export const expandSecureUrl = (url) => `https://${url}`;

View file

@ -33,9 +33,6 @@ import Account from "./Account";
import ResetPassword from "./ResetPassword"; import ResetPassword from "./ResetPassword";
import accountApi, {UnauthorizedError} from "../app/AccountApi"; import accountApi, {UnauthorizedError} from "../app/AccountApi";
// TODO races when two tabs are open
// TODO investigate service workers
const App = () => { const App = () => {
return ( return (
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>

View file

@ -33,7 +33,7 @@ import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent"; import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions"; import DialogActions from "@mui/material/DialogActions";
import userManager from "../app/UserManager"; 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 {useTranslation} from "react-i18next";
import session from "../app/Session"; import session from "../app/Session";
import routes from "./routes"; import routes from "./routes";
@ -491,14 +491,15 @@ const Reservations = () => {
setDialogOpen(false); setDialogOpen(false);
}; };
const handleDialogSubmit = async (entry) => { const handleDialogSubmit = async (reservation) => {
setDialogOpen(false); setDialogOpen(false);
try { try {
await accountApi.addAccessEntry(); await accountApi.upsertAccess(reservation.topic, reservation.everyone);
console.debug(`[Preferences] Added entry ${entry.topic}`); console.debug(`[Preferences] Added topic reservation`, reservation);
} catch (e) { } 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) { if (!session.exists() || !account) {
@ -519,10 +520,10 @@ const Reservations = () => {
<CardActions> <CardActions>
<Button onClick={handleAddClick}>{t("prefs_reservations_add_button")}</Button> <Button onClick={handleAddClick}>{t("prefs_reservations_add_button")}</Button>
<ReservationsDialog <ReservationsDialog
key={`accessAddDialog${dialogKey}`} key={`reservationAddDialog${dialogKey}`}
open={dialogOpen} open={dialogOpen}
entry={null} reservation={null}
entries={account.access} reservations={account.reservations}
onCancel={handleDialogCancel} onCancel={handleDialogCancel}
onSubmit={handleDialogSubmit} onSubmit={handleDialogSubmit}
/> />
@ -535,11 +536,11 @@ const ReservationsTable = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [dialogKey, setDialogKey] = useState(0); const [dialogKey, setDialogKey] = useState(0);
const [dialogOpen, setDialogOpen] = useState(false); 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); setDialogKey(prev => prev+1);
setDialogEntry(entry); setDialogReservation(reservation);
setDialogOpen(true); setDialogOpen(true);
}; };
@ -547,13 +548,25 @@ const ReservationsTable = (props) => {
setDialogOpen(false); setDialogOpen(false);
}; };
const handleDialogSubmit = async (user) => { const handleDialogSubmit = async (reservation) => {
setDialogOpen(false); 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) => { const handleDeleteClick = async (reservation) => {
// FIXME 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 ( return (
@ -575,25 +588,25 @@ const ReservationsTable = (props) => {
<TableCell aria-label={t("prefs_reservations_table_access_header")}> <TableCell aria-label={t("prefs_reservations_table_access_header")}>
{reservation.everyone === "read-write" && {reservation.everyone === "read-write" &&
<> <>
<Public fontSize="small" sx={{verticalAlign: "bottom", mr: 0.5}}/> <Public fontSize="small" sx={{color: "grey", verticalAlign: "bottom", mr: 0.5}}/>
{t("prefs_reservations_table_everyone_read_write")} {t("prefs_reservations_table_everyone_read_write")}
</> </>
} }
{reservation.everyone === "read-only" && {reservation.everyone === "read-only" &&
<> <>
<PublicOff fontSize="small" sx={{verticalAlign: "bottom", mr: 0.5}}/> <PublicOff fontSize="small" sx={{color: "grey", verticalAlign: "bottom", mr: 0.5}}/>
{t("prefs_reservations_table_everyone_read_only")} {t("prefs_reservations_table_everyone_read_only")}
</> </>
} }
{reservation.everyone === "write-only" && {reservation.everyone === "write-only" &&
<> <>
<PublicOff fontSize="small" sx={{verticalAlign: "bottom", mr: 0.5}}/> <PublicOff fontSize="small" sx={{color: "grey", verticalAlign: "bottom", mr: 0.5}}/>
{t("prefs_reservations_table_everyone_write_only")} {t("prefs_reservations_table_everyone_write_only")}
</> </>
} }
{reservation.everyone === "deny-all" && {reservation.everyone === "deny-all" &&
<> <>
<LockIcon fontSize="small" sx={{verticalAlign: "bottom", mr: 0.5}}/> <LockIcon fontSize="small" sx={{color: "grey", verticalAlign: "bottom", mr: 0.5}}/>
{t("prefs_reservations_table_everyone_deny_all")} {t("prefs_reservations_table_everyone_deny_all")}
</> </>
} }
@ -610,10 +623,10 @@ const ReservationsTable = (props) => {
))} ))}
</TableBody> </TableBody>
<ReservationsDialog <ReservationsDialog
key={`accessEditDialog${dialogKey}`} key={`reservationEditDialog${dialogKey}`}
open={dialogOpen} open={dialogOpen}
entry={dialogEntry} reservation={dialogReservation}
entries={props.entries} reservations={props.reservations}
onCancel={handleDialogCancel} onCancel={handleDialogCancel}
onSubmit={handleDialogSubmit} onSubmit={handleDialogSubmit}
/> />
@ -624,24 +637,31 @@ const ReservationsTable = (props) => {
const ReservationsDialog = (props) => { const ReservationsDialog = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [topic, setTopic] = useState(""); const [topic, setTopic] = useState("");
const [access, setAccess] = useState("private"); const [everyone, setEveryone] = useState("deny-all");
const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
const editMode = props.entry !== null; const editMode = props.reservation !== null;
const addButtonEnabled = (() => { 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 () => { const handleSubmit = async () => {
props.onSubmit({ props.onSubmit({
topic: topic, topic: (editMode) ? props.reservation.topic : topic,
// FIXME everyone: everyone
}) })
}; };
useEffect(() => { useEffect(() => {
if (editMode) { if (editMode) {
setTopic(props.topic); setTopic(props.reservation.topic);
//setAccess(props.access); setEveryone(props.reservation.everyone);
} }
}, [editMode, props]); }, [editMode, props.reservation]);
return ( return (
<Dialog open={props.open} onClose={props.onCancel} maxWidth="sm" fullWidth fullScreen={fullScreen}> <Dialog open={props.open} onClose={props.onCancel} maxWidth="sm" fullWidth fullScreen={fullScreen}>
<DialogTitle>{editMode ? t("prefs_reservations_dialog_title_edit") : t("prefs_reservations_dialog_title_add")}</DialogTitle> <DialogTitle>{editMode ? t("prefs_reservations_dialog_title_edit") : t("prefs_reservations_dialog_title_add")}</DialogTitle>
@ -660,8 +680,8 @@ const ReservationsDialog = (props) => {
/>} />}
<FormControl fullWidth variant="standard"> <FormControl fullWidth variant="standard">
<Select <Select
value={access} value={everyone}
onChange={(ev) => setAccess(ev.target.value)} onChange={(ev) => setEveryone(ev.target.value)}
aria-label={t("prefs_reservations_dialog_access_label")} aria-label={t("prefs_reservations_dialog_access_label")}
sx={{ sx={{
marginTop: 1, marginTop: 1,