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…
	
	Add table
		Add a link
		
	
		Reference in a new issue