1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2024-09-26 18:42:00 +02:00

Width, again

This commit is contained in:
binwiederhier 2023-05-23 20:16:29 -04:00
parent ca5d736a71
commit c87549e71a
20 changed files with 194 additions and 37 deletions

View file

@ -1,2 +1,3 @@
build/ build/
dist/
public/static/langs/ public/static/langs/

View file

@ -45,6 +45,6 @@
] ]
}, },
"prettier": { "prettier": {
"printWidth": 160 "printWidth": 140
} }
} }

View file

@ -39,7 +39,8 @@
<body> <body>
<noscript> <noscript>
ntfy web requires JavaScript, but you can also use the ntfy web requires JavaScript, but you can also use the
<a href="https://ntfy.sh/docs/subscribe/cli/">CLI</a> or <a href="https://ntfy.sh/docs/subscribe/phone/">Android/iOS app</a> to subscribe. <a href="https://ntfy.sh/docs/subscribe/cli/">CLI</a> or <a href="https://ntfy.sh/docs/subscribe/phone/">Android/iOS app</a> to
subscribe.
</noscript> </noscript>
<div id="root"></div> <div id="root"></div>
<script src="%PUBLIC_URL%/config.js"></script> <script src="%PUBLIC_URL%/config.js"></script>

View file

@ -1,4 +1,12 @@
import { fetchLinesIterator, maybeWithAuth, topicShortUrl, topicUrl, topicUrlAuth, topicUrlJsonPoll, topicUrlJsonPollWithSince } from "./utils"; import {
fetchLinesIterator,
maybeWithAuth,
topicShortUrl,
topicUrl,
topicUrlAuth,
topicUrlJsonPoll,
topicUrlJsonPollWithSince,
} from "./utils";
import userManager from "./UserManager"; import userManager from "./UserManager";
import { fetchOrThrow } from "./errors"; import { fetchOrThrow } from "./errors";

View file

@ -57,7 +57,9 @@ class Connection {
}; };
this.ws.onclose = (event) => { this.ws.onclose = (event) => {
if (event.wasClean) { if (event.wasClean) {
console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`); console.log(
`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`
);
this.ws = null; this.ws = null;
} else { } else {
const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length - 1)]; const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length - 1)];

View file

@ -74,7 +74,9 @@ class ConnectionManager {
); );
this.connections.set(connectionId, connection); this.connections.set(connectionId, connection);
console.log( console.log(
`[ConnectionManager] Starting new connection ${connectionId} (subscription ${subscriptionId} with user ${user ? user.username : "anonymous"})` `[ConnectionManager] Starting new connection ${connectionId} (subscription ${subscriptionId} with user ${
user ? user.username : "anonymous"
})`
); );
connection.start(); connection.start();
} }

View file

@ -10,7 +10,8 @@ import config from "./config";
import { Base64 } from "js-base64"; import { Base64 } from "js-base64";
export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`; export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws`.replaceAll("https://", "wss://").replaceAll("http://", "ws://"); export const topicUrlWs = (baseUrl, topic) =>
`${topicUrl(baseUrl, topic)}/ws`.replaceAll("https://", "wss://").replaceAll("http://", "ws://");
export const topicUrlJson = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/json`; export const topicUrlJson = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/json`;
export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, topic)}?poll=1`; export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, topic)}?poll=1`;
export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`; export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`;

View file

@ -211,7 +211,10 @@ const ChangePasswordDialog = (props) => {
</DialogContent> </DialogContent>
<DialogFooter status={error}> <DialogFooter status={error}>
<Button onClick={props.onClose}>{t("common_cancel")}</Button> <Button onClick={props.onClose}>{t("common_cancel")}</Button>
<Button onClick={handleDialogSubmit} disabled={newPassword.length === 0 || currentPassword.length === 0 || newPassword !== confirmPassword}> <Button
onClick={handleDialogSubmit}
disabled={newPassword.length === 0 || currentPassword.length === 0 || newPassword !== confirmPassword}
>
{t("account_basics_password_dialog_button_submit")} {t("account_basics_password_dialog_button_submit")}
</Button> </Button>
</DialogFooter> </DialogFooter>
@ -288,7 +291,13 @@ const AccountType = () => {
</Tooltip> </Tooltip>
)} )}
{config.enable_payments && account.role === Role.USER && !account.billing?.subscription && ( {config.enable_payments && account.role === Role.USER && !account.billing?.subscription && (
<Button variant="outlined" size="small" startIcon={<CelebrationIcon sx={{ color: "#55b86e" }} />} onClick={handleUpgradeClick} sx={{ ml: 1 }}> <Button
variant="outlined"
size="small"
startIcon={<CelebrationIcon sx={{ color: "#55b86e" }} />}
onClick={handleUpgradeClick}
sx={{ ml: 1 }}
>
{t("account_basics_tier_upgrade_button")} {t("account_basics_tier_upgrade_button")}
</Button> </Button>
)} )}
@ -303,7 +312,11 @@ const AccountType = () => {
</Button> </Button>
)} )}
{config.enable_payments && ( {config.enable_payments && (
<UpgradeDialog key={`upgradeDialogFromAccount${upgradeDialogKey}`} open={upgradeDialogOpen} onCancel={() => setUpgradeDialogOpen(false)} /> <UpgradeDialog
key={`upgradeDialogFromAccount${upgradeDialogKey}`}
open={upgradeDialogOpen}
onCancel={() => setUpgradeDialogOpen(false)}
/>
)} )}
</div> </div>
{account.billing?.status === SubscriptionStatus.PAST_DUE && ( {account.billing?.status === SubscriptionStatus.PAST_DUE && (
@ -574,7 +587,11 @@ const Stats = () => {
</div> </div>
<LinearProgress <LinearProgress
variant="determinate" variant="determinate"
value={account.role === Role.USER && account.limits.reservations > 0 ? normalize(account.stats.reservations, account.limits.reservations) : 100} value={
account.role === Role.USER && account.limits.reservations > 0
? normalize(account.stats.reservations, account.limits.reservations)
: 100
}
/> />
</Pref> </Pref>
)} )}
@ -602,7 +619,10 @@ const Stats = () => {
: t("account_usage_unlimited")} : t("account_usage_unlimited")}
</Typography> </Typography>
</div> </div>
<LinearProgress variant="determinate" value={account.role === Role.USER ? normalize(account.stats.messages, account.limits.messages) : 100} /> <LinearProgress
variant="determinate"
value={account.role === Role.USER ? normalize(account.stats.messages, account.limits.messages) : 100}
/>
</Pref> </Pref>
{config.enable_emails && ( {config.enable_emails && (
<Pref <Pref
@ -629,7 +649,10 @@ const Stats = () => {
: t("account_usage_unlimited")} : t("account_usage_unlimited")}
</Typography> </Typography>
</div> </div>
<LinearProgress variant="determinate" value={account.role === Role.USER ? normalize(account.stats.emails, account.limits.emails) : 100} /> <LinearProgress
variant="determinate"
value={account.role === Role.USER ? normalize(account.stats.emails, account.limits.emails) : 100}
/>
</Pref> </Pref>
)} )}
{config.enable_calls && (account.role === Role.ADMIN || account.limits.calls > 0) && ( {config.enable_calls && (account.role === Role.ADMIN || account.limits.calls > 0) && (
@ -833,7 +856,12 @@ const TokensTable = (props) => {
<TableBody> <TableBody>
{tokens.map((token) => ( {tokens.map((token) => (
<TableRow key={token.token} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}> <TableRow key={token.token} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
<TableCell component="th" scope="row" sx={{ paddingLeft: 0, whiteSpace: "nowrap" }} aria-label={t("account_tokens_table_token_header")}> <TableCell
component="th"
scope="row"
sx={{ paddingLeft: 0, whiteSpace: "nowrap" }}
aria-label={t("account_tokens_table_token_header")}
>
<span> <span>
<span style={{ fontFamily: "Monospace", fontSize: "0.9rem" }}>{token.token.slice(0, 12)}</span> <span style={{ fontFamily: "Monospace", fontSize: "0.9rem" }}>{token.token.slice(0, 12)}</span>
... ...
@ -893,7 +921,12 @@ const TokensTable = (props) => {
))} ))}
</TableBody> </TableBody>
<Portal> <Portal>
<Snackbar open={snackOpen} autoHideDuration={3000} onClose={() => setSnackOpen(false)} message={t("account_tokens_table_copied_to_clipboard")} /> <Snackbar
open={snackOpen}
autoHideDuration={3000}
onClose={() => setSnackOpen(false)}
message={t("account_tokens_table_copied_to_clipboard")}
/>
</Portal> </Portal>
<TokenDialog key={`tokenDialogEdit${upsertDialogKey}`} open={upsertDialogOpen} token={selectedToken} onClose={handleDialogClose} /> <TokenDialog key={`tokenDialogEdit${upsertDialogKey}`} open={upsertDialogOpen} token={selectedToken} onClose={handleDialogClose} />
<TokenDeleteDialog open={deleteDialogOpen} token={selectedToken} onClose={handleDialogClose} /> <TokenDeleteDialog open={deleteDialogOpen} token={selectedToken} onClose={handleDialogClose} />
@ -958,7 +991,9 @@ const TokenDialog = (props) => {
</DialogContent> </DialogContent>
<DialogFooter status={error}> <DialogFooter status={error}>
<Button onClick={props.onClose}>{t("account_tokens_dialog_button_cancel")}</Button> <Button onClick={props.onClose}>{t("account_tokens_dialog_button_cancel")}</Button>
<Button onClick={handleSubmit}>{editMode ? t("account_tokens_dialog_button_update") : t("account_tokens_dialog_button_create")}</Button> <Button onClick={handleSubmit}>
{editMode ? t("account_tokens_dialog_button_update") : t("account_tokens_dialog_button_create")}
</Button>
</DialogFooter> </DialogFooter>
</Dialog> </Dialog>
); );

View file

@ -98,7 +98,13 @@ const SettingsIcons = (props) => {
<IconButton color="inherit" size="large" edge="end" onClick={handleToggleMute} aria-label={t("action_bar_toggle_mute")}> <IconButton color="inherit" size="large" edge="end" onClick={handleToggleMute} aria-label={t("action_bar_toggle_mute")}>
{subscription.mutedUntil ? <NotificationsOffIcon /> : <NotificationsIcon />} {subscription.mutedUntil ? <NotificationsOffIcon /> : <NotificationsIcon />}
</IconButton> </IconButton>
<IconButton color="inherit" size="large" edge="end" onClick={(ev) => setAnchorEl(ev.currentTarget)} aria-label={t("action_bar_toggle_action_menu")}> <IconButton
color="inherit"
size="large"
edge="end"
onClick={(ev) => setAnchorEl(ev.currentTarget)}
aria-label={t("action_bar_toggle_action_menu")}
>
<MoreVertIcon /> <MoreVertIcon />
</IconButton> </IconButton>
<SubscriptionPopup subscription={subscription} anchor={anchorEl} placement="right" onClose={() => setAnchorEl(null)} /> <SubscriptionPopup subscription={subscription} anchor={anchorEl} placement="right" onClose={() => setAnchorEl(null)} />

View file

@ -99,7 +99,13 @@ const EmojiPicker = (props) => {
}} }}
> >
{Object.keys(emojisByCategory).map((category) => ( {Object.keys(emojisByCategory).map((category) => (
<Category key={category} title={category} emojis={emojisByCategory[category]} search={searchFields} onPick={props.onEmojiPick} /> <Category
key={category}
title={category}
emojis={emojisByCategory[category]}
search={searchFields}
onPick={props.onEmojiPick}
/>
))} ))}
</Box> </Box>
</Box> </Box>

View file

@ -46,7 +46,9 @@ class ErrorBoundaryImpl extends React.Component {
// Fetch additional info and a better stack trace // Fetch additional info and a better stack trace
StackTrace.fromError(error).then((stack) => { StackTrace.fromError(error).then((stack) => {
console.error("[ErrorBoundary] Stacktrace fetched", stack); console.error("[ErrorBoundary] Stacktrace fetched", stack);
const niceStack = `${error.toString()}\n` + stack.map((el) => ` at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})`).join("\n"); const niceStack =
`${error.toString()}\n` +
stack.map((el) => ` at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})`).join("\n");
this.setState({ niceStack }); this.setState({ niceStack });
}); });
} }

View file

@ -29,7 +29,9 @@ const Messaging = (props) => {
return ( return (
<> <>
{subscription && <MessageBar subscription={subscription} message={message} onMessageChange={setMessage} onOpenDialogClick={handleOpenDialogClick} />} {subscription && (
<MessageBar subscription={subscription} message={message} onMessageChange={setMessage} onOpenDialogClick={handleOpenDialogClick} />
)}
<PublishDialog <PublishDialog
key={`publishDialog${dialogKey}`} // Resets dialog when canceled/closed key={`publishDialog${dialogKey}`} // Resets dialog when canceled/closed
openMode={dialogOpenMode} openMode={dialogOpenMode}
@ -95,7 +97,12 @@ const MessageBar = (props) => {
<SendIcon /> <SendIcon />
</IconButton> </IconButton>
<Portal> <Portal>
<Snackbar open={snackOpen} autoHideDuration={3000} onClose={() => setSnackOpen(false)} message={t("message_bar_error_publishing")} /> <Snackbar
open={snackOpen}
autoHideDuration={3000}
onClose={() => setSnackOpen(false)}
message={t("message_bar_error_publishing")}
/>
</Portal> </Portal>
</Paper> </Paper>
); );

View file

@ -108,7 +108,8 @@ const NavList = (props) => {
const showNotificationBrowserNotSupportedBox = !notifier.browserSupported(); const showNotificationBrowserNotSupportedBox = !notifier.browserSupported();
const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser
const showNotificationGrantBox = notifier.supported() && props.subscriptions?.length > 0 && !props.notificationsGranted; const showNotificationGrantBox = notifier.supported() && props.subscriptions?.length > 0 && !props.notificationsGranted;
const navListPadding = showNotificationGrantBox || showNotificationBrowserNotSupportedBox || showNotificationContextNotSupportedBox ? "0" : ""; const navListPadding =
showNotificationGrantBox || showNotificationBrowserNotSupportedBox || showNotificationContextNotSupportedBox ? "0" : "";
return ( return (
<> <>

View file

@ -115,7 +115,12 @@ const NotificationList = (props) => {
{notifications.slice(0, count).map((notification) => ( {notifications.slice(0, count).map((notification) => (
<NotificationItem key={notification.id} notification={notification} onShowSnack={() => setSnackOpen(true)} /> <NotificationItem key={notification.id} notification={notification} onShowSnack={() => setSnackOpen(true)} />
))} ))}
<Snackbar open={snackOpen} autoHideDuration={3000} onClose={() => setSnackOpen(false)} message={t("notifications_copied_to_clipboard")} /> <Snackbar
open={snackOpen}
autoHideDuration={3000}
onClose={() => setSnackOpen(false)}
message={t("notifications_copied_to_clipboard")}
/>
</Stack> </Stack>
</Container> </Container>
</InfiniteScroll> </InfiniteScroll>
@ -156,7 +161,11 @@ const NotificationItem = (props) => {
</Tooltip> </Tooltip>
{notification.new === 1 && ( {notification.new === 1 && (
<Tooltip title={t("notifications_mark_read")} enterDelay={500}> <Tooltip title={t("notifications_mark_read")} enterDelay={500}>
<IconButton onClick={handleMarkRead} sx={{ float: "right", marginRight: -0.5, marginTop: -1 }} aria-label={t("notifications_mark_read")}> <IconButton
onClick={handleMarkRead}
sx={{ float: "right", marginRight: -0.5, marginTop: -1 }}
aria-label={t("notifications_mark_read")}
>
<CheckIcon /> <CheckIcon />
</IconButton> </IconButton>
</Tooltip> </Tooltip>

View file

@ -251,7 +251,14 @@ const Users = () => {
</CardContent> </CardContent>
<CardActions> <CardActions>
<Button onClick={handleAddClick}>{t("prefs_users_add_button")}</Button> <Button onClick={handleAddClick}>{t("prefs_users_add_button")}</Button>
<UserDialog key={`userAddDialog${dialogKey}`} open={dialogOpen} user={null} users={users} onCancel={handleDialogCancel} onSubmit={handleDialogSubmit} /> <UserDialog
key={`userAddDialog${dialogKey}`}
open={dialogOpen}
user={null}
users={users}
onCancel={handleDialogCancel}
onSubmit={handleDialogSubmit}
/>
</CardActions> </CardActions>
</Card> </Card>
); );
@ -449,7 +456,26 @@ const Language = () => {
// Country flags are displayed using emoji. Emoji rendering is handled by platform fonts. // Country flags are displayed using emoji. Emoji rendering is handled by platform fonts.
// Windows in particular does not yet play nicely with flag emoji so for now, hide flags on Windows. // Windows in particular does not yet play nicely with flag emoji so for now, hide flags on Windows.
const randomFlags = shuffle(["🇬🇧", "🇺🇸", "🇪🇸", "🇫🇷", "🇧🇬", "🇨🇿", "🇩🇪", "🇵🇱", "🇺🇦", "🇨🇳", "🇮🇹", "🇭🇺", "🇧🇷", "🇳🇱", "🇮🇩", "🇯🇵", "🇷🇺", "🇹🇷"]).slice(0, 3); const randomFlags = shuffle([
"🇬🇧",
"🇺🇸",
"🇪🇸",
"🇫🇷",
"🇧🇬",
"🇨🇿",
"🇩🇪",
"🇵🇱",
"🇺🇦",
"🇨🇳",
"🇮🇹",
"🇭🇺",
"🇧🇷",
"🇳🇱",
"🇮🇩",
"🇯🇵",
"🇷🇺",
"🇹🇷",
]).slice(0, 3);
const showFlags = !navigator.userAgent.includes("Windows"); const showFlags = !navigator.userAgent.includes("Windows");
let title = t("prefs_appearance_language_title"); let title = t("prefs_appearance_language_title");
if (showFlags) { if (showFlags) {
@ -531,7 +557,12 @@ const Reservations = () => {
<Button onClick={handleAddClick} disabled={limitReached}> <Button onClick={handleAddClick} disabled={limitReached}>
{t("prefs_reservations_add_button")} {t("prefs_reservations_add_button")}
</Button> </Button>
<ReserveAddDialog key={`reservationAddDialog${dialogKey}`} open={dialogOpen} reservations={reservations} onClose={() => setDialogOpen(false)} /> <ReserveAddDialog
key={`reservationAddDialog${dialogKey}`}
open={dialogOpen}
reservations={reservations}
onClose={() => setDialogOpen(false)}
/>
</CardActions> </CardActions>
</Card> </Card>
); );
@ -545,7 +576,9 @@ const ReservationsTable = (props) => {
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const { subscriptions } = useOutletContext(); const { subscriptions } = useOutletContext();
const localSubscriptions = const localSubscriptions =
subscriptions?.length > 0 ? Object.assign({}, ...subscriptions.filter((s) => s.baseUrl === config.base_url).map((s) => ({ [s.topic]: s }))) : {}; subscriptions?.length > 0
? Object.assign({}, ...subscriptions.filter((s) => s.baseUrl === config.base_url).map((s) => ({ [s.topic]: s })))
: {};
const handleEditClick = (reservation) => { const handleEditClick = (reservation) => {
setDialogKey((prev) => prev + 1); setDialogKey((prev) => prev + 1);

View file

@ -783,7 +783,12 @@ const AttachmentBox = (props) => {
)} )}
</Typography> </Typography>
</Box> </Box>
<DialogIconButton disabled={props.disabled} onClick={props.onClose} sx={{ marginLeft: "6px" }} aria-label={t("publish_dialog_attached_file_remove")}> <DialogIconButton
disabled={props.disabled}
onClick={props.onClose}
sx={{ marginLeft: "6px" }}
aria-label={t("publish_dialog_attached_file_remove")}
>
<Close /> <Close />
</DialogIconButton> </DialogIconButton>
</Box> </Box>
@ -806,7 +811,13 @@ const ExpandingTextField = (props) => {
}, [props.value]); }, [props.value]);
return ( return (
<> <>
<Typography ref={invisibleFieldRef} component="span" variant={props.variant} aria-hidden={true} sx={{ position: "absolute", left: "-200%" }}> <Typography
ref={invisibleFieldRef}
component="span"
variant={props.variant}
aria-hidden={true}
sx={{ position: "absolute", left: "-200%" }}
>
{props.value} {props.value}
</Typography> </Typography>
<TextField <TextField

View file

@ -121,7 +121,13 @@ const Signup = () => {
), ),
}} }}
/> />
<Button type="submit" fullWidth variant="contained" disabled={username === "" || password === "" || password !== confirm} sx={{ mt: 2, mb: 2 }}> <Button
type="submit"
fullWidth
variant="contained"
disabled={username === "" || password === "" || password !== confirm}
sx={{ mt: 2, mb: 2 }}
>
{t("signup_form_button_submit")} {t("signup_form_button_submit")}
</Button> </Button>
{error && ( {error && (

View file

@ -68,7 +68,9 @@ const SubscribePage = (props) => {
const baseUrl = anotherServerVisible ? props.baseUrl : config.base_url; const baseUrl = anotherServerVisible ? props.baseUrl : config.base_url;
const topic = props.topic; const topic = props.topic;
const existingTopicUrls = props.subscriptions.map((s) => topicUrl(s.baseUrl, s.topic)); const existingTopicUrls = props.subscriptions.map((s) => topicUrl(s.baseUrl, s.topic));
const existingBaseUrls = Array.from(new Set([publicBaseUrl, ...props.subscriptions.map((s) => s.baseUrl)])).filter((s) => s !== config.base_url); const existingBaseUrls = Array.from(new Set([publicBaseUrl, ...props.subscriptions.map((s) => s.baseUrl)])).filter(
(s) => s !== config.base_url
);
const showReserveTopicCheckbox = config.enable_reservations && !anotherServerVisible && (config.enable_payments || account); const showReserveTopicCheckbox = config.enable_reservations && !anotherServerVisible && (config.enable_payments || account);
const reserveTopicEnabled = const reserveTopicEnabled =
session.exists() && (account?.role === Role.ADMIN || (account?.role === Role.USER && (account?.stats.reservations_remaining || 0) > 0)); session.exists() && (account?.role === Role.ADMIN || (account?.role === Role.USER && (account?.stats.reservations_remaining || 0) > 0));
@ -212,7 +214,12 @@ const SubscribePage = (props) => {
inputValue={props.baseUrl} inputValue={props.baseUrl}
onInputChange={updateBaseUrl} onInputChange={updateBaseUrl}
renderInput={(params) => ( renderInput={(params) => (
<TextField {...params} placeholder={config.base_url} variant="standard" aria-label={t("subscribe_dialog_subscribe_base_url_label")} /> <TextField
{...params}
placeholder={config.base_url}
variant="standard"
aria-label={t("subscribe_dialog_subscribe_base_url_label")}
/>
)} )}
/> />
)} )}

View file

@ -40,7 +40,10 @@ export const SubscriptionPopup = (props) => {
const showReservationAdd = config.enable_reservations && !subscription?.reservation && account?.stats.reservations_remaining > 0; const showReservationAdd = config.enable_reservations && !subscription?.reservation && account?.stats.reservations_remaining > 0;
const showReservationAddDisabled = const showReservationAddDisabled =
!showReservationAdd && config.enable_reservations && !subscription?.reservation && (config.enable_payments || account?.stats.reservations_remaining === 0); !showReservationAdd &&
config.enable_reservations &&
!subscription?.reservation &&
(config.enable_payments || account?.stats.reservations_remaining === 0);
const showReservationEdit = config.enable_reservations && !!subscription?.reservation; const showReservationEdit = config.enable_reservations && !!subscription?.reservation;
const showReservationDelete = config.enable_reservations && !!subscription?.reservation; const showReservationDelete = config.enable_reservations && !!subscription?.reservation;
@ -161,10 +164,20 @@ export const SubscriptionPopup = (props) => {
<MenuItem onClick={handleUnsubscribe}>{t("action_bar_unsubscribe")}</MenuItem> <MenuItem onClick={handleUnsubscribe}>{t("action_bar_unsubscribe")}</MenuItem>
</PopupMenu> </PopupMenu>
<Portal> <Portal>
<Snackbar open={showPublishError} autoHideDuration={3000} onClose={() => setShowPublishError(false)} message={t("message_bar_error_publishing")} /> <Snackbar
open={showPublishError}
autoHideDuration={3000}
onClose={() => setShowPublishError(false)}
message={t("message_bar_error_publishing")}
/>
<DisplayNameDialog open={displayNameDialogOpen} subscription={subscription} onClose={() => setDisplayNameDialogOpen(false)} /> <DisplayNameDialog open={displayNameDialogOpen} subscription={subscription} onClose={() => setDisplayNameDialogOpen(false)} />
{showReservationAdd && ( {showReservationAdd && (
<ReserveAddDialog open={reserveAddDialogOpen} topic={subscription.topic} reservations={reservations} onClose={() => setReserveAddDialogOpen(false)} /> <ReserveAddDialog
open={reserveAddDialogOpen}
topic={subscription.topic}
reservations={reservations}
onClose={() => setReserveAddDialogOpen(false)}
/>
)} )}
{showReservationEdit && ( {showReservationEdit && (
<ReserveEditDialog <ReserveEditDialog
@ -175,7 +188,11 @@ export const SubscriptionPopup = (props) => {
/> />
)} )}
{showReservationDelete && ( {showReservationDelete && (
<ReserveDeleteDialog open={reserveDeleteDialogOpen} topic={subscription.topic} onClose={() => setReserveDeleteDialogOpen(false)} /> <ReserveDeleteDialog
open={reserveDeleteDialogOpen}
topic={subscription.topic}
onClose={() => setReserveDeleteDialogOpen(false)}
/>
)} )}
</Portal> </Portal>
</> </>

View file

@ -363,7 +363,9 @@ const TierCard = (props) => {
</Feature> </Feature>
)} )}
<Feature> <Feature>
{t("account_upgrade_dialog_tier_features_attachment_file_size", { filesize: formatBytes(tier.limits.attachment_file_size, 0) })} {t("account_upgrade_dialog_tier_features_attachment_file_size", {
filesize: formatBytes(tier.limits.attachment_file_size, 0),
})}
</Feature> </Feature>
{tier.limits.reservations === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_reservations")}</NoFeature>} {tier.limits.reservations === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_reservations")}</NoFeature>}
{tier.limits.calls === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_calls")}</NoFeature>} {tier.limits.calls === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_calls")}</NoFeature>}