Access UI
This commit is contained in:
parent
4b9d40464c
commit
d666cab77a
4 changed files with 87 additions and 36 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}`;
|
||||||
|
|
|
@ -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 />}>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue