mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-11-01 01:21:15 +01:00
JS constants
This commit is contained in:
parent
ef8f7c9884
commit
259293f9b3
8 changed files with 77 additions and 44 deletions
|
@ -43,7 +43,6 @@ import (
|
|||
- MEDIUM: Reservation (UI): Ask for confirmation when removing reservation (deadcade)
|
||||
- MEDIUM: Reservation table delete button: dialog "keep or delete messages?"
|
||||
- LOW: UI: Flickering upgrade banner when logging in
|
||||
- LOW: JS constants
|
||||
|
||||
*/
|
||||
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
import {
|
||||
accountBillingPortalUrl,
|
||||
accountBillingSubscriptionUrl,
|
||||
accountPasswordUrl,
|
||||
accountReservationSingleUrl,
|
||||
accountReservationUrl,
|
||||
accountPasswordUrl,
|
||||
accountSettingsUrl,
|
||||
accountSubscriptionSingleUrl,
|
||||
accountSubscriptionUrl,
|
||||
accountTokenUrl,
|
||||
accountUrl, maybeWithAuth, topicUrl,
|
||||
accountUrl,
|
||||
tiersUrl,
|
||||
withBasicAuth,
|
||||
withBearerAuth, accountBillingSubscriptionUrl, accountBillingPortalUrl, tiersUrl
|
||||
withBearerAuth
|
||||
} from "./utils";
|
||||
import session from "./Session";
|
||||
import subscriptionManager from "./SubscriptionManager";
|
||||
import i18n from "i18next";
|
||||
import prefs from "./Prefs";
|
||||
import routes from "../components/routes";
|
||||
import userManager from "./UserManager";
|
||||
|
||||
const delayMillis = 45000; // 45 seconds
|
||||
const intervalMillis = 900000; // 15 minutes
|
||||
|
@ -441,6 +443,32 @@ class AccountApi {
|
|||
}
|
||||
}
|
||||
|
||||
// Maps to user.Role in user/types.go
|
||||
export const Role = {
|
||||
ADMIN: "admin",
|
||||
USER: "user"
|
||||
};
|
||||
|
||||
// Maps to server.visitorLimitBasis in server/visitor.go
|
||||
export const LimitBasis = {
|
||||
IP: "ip",
|
||||
TIER: "tier"
|
||||
};
|
||||
|
||||
// Maps to stripe.SubscriptionStatus
|
||||
export const SubscriptionStatus = {
|
||||
ACTIVE: "active",
|
||||
PAST_DUE: "past_due"
|
||||
};
|
||||
|
||||
// Maps to user.Permission in user/types.go
|
||||
export const Permission = {
|
||||
READ_WRITE: "read-write",
|
||||
READ_ONLY: "read-only",
|
||||
WRITE_ONLY: "write-only",
|
||||
DENY_ALL: "deny-all"
|
||||
};
|
||||
|
||||
export class UsernameTakenError extends Error {
|
||||
constructor(username) {
|
||||
super("Username taken");
|
||||
|
|
|
@ -28,7 +28,13 @@ import TextField from "@mui/material/TextField";
|
|||
import routes from "./routes";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import {formatBytes, formatShortDate, formatShortDateTime, openUrl, truncateString, validUrl} from "../app/utils";
|
||||
import accountApi, {IncorrectPasswordError, UnauthorizedError} from "../app/AccountApi";
|
||||
import accountApi, {
|
||||
IncorrectPasswordError,
|
||||
LimitBasis,
|
||||
Role,
|
||||
SubscriptionStatus,
|
||||
UnauthorizedError
|
||||
} from "../app/AccountApi";
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import {Pref, PrefGroup} from "./Pref";
|
||||
import db from "../app/db";
|
||||
|
@ -92,7 +98,7 @@ const Username = () => {
|
|||
<Pref labelId={labelId} title={t("account_basics_username_title")} description={t("account_basics_username_description")}>
|
||||
<div aria-labelledby={labelId}>
|
||||
{session.username()}
|
||||
{account?.role === "admin"
|
||||
{account?.role === Role.ADMIN
|
||||
? <>{" "}<Tooltip title={t("account_basics_username_admin_tooltip")}><span style={{cursor: "default"}}>👑</span></Tooltip></>
|
||||
: ""}
|
||||
</div>
|
||||
|
@ -237,7 +243,7 @@ const AccountType = () => {
|
|||
};
|
||||
|
||||
let accountType;
|
||||
if (account.role === "admin") {
|
||||
if (account.role === Role.ADMIN) {
|
||||
const tierSuffix = (account.tier) ? `(with ${account.tier.name} tier)` : `(no tier)`;
|
||||
accountType = `${t("account_usage_tier_admin")} ${tierSuffix}`;
|
||||
} else if (!account.tier) {
|
||||
|
@ -248,7 +254,7 @@ const AccountType = () => {
|
|||
|
||||
return (
|
||||
<Pref
|
||||
alignTop={account.billing?.status === "past_due" || account.billing?.cancel_at > 0}
|
||||
alignTop={account.billing?.status === SubscriptionStatus.PAST_DUE || account.billing?.cancel_at > 0}
|
||||
title={t("account_usage_tier_title")}
|
||||
description={t("account_usage_tier_description")}
|
||||
>
|
||||
|
@ -259,7 +265,7 @@ const AccountType = () => {
|
|||
<span><InfoIcon/></span>
|
||||
</Tooltip>
|
||||
}
|
||||
{config.enable_payments && account.role === "user" && !account.billing?.subscription &&
|
||||
{config.enable_payments && account.role === Role.USER && !account.billing?.subscription &&
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
|
@ -268,7 +274,7 @@ const AccountType = () => {
|
|||
sx={{ml: 1}}
|
||||
>{t("account_usage_tier_upgrade_button")}</Button>
|
||||
}
|
||||
{config.enable_payments && account.role === "user" && account.billing?.subscription &&
|
||||
{config.enable_payments && account.role === Role.USER && account.billing?.subscription &&
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
|
@ -276,7 +282,7 @@ const AccountType = () => {
|
|||
sx={{ml: 1}}
|
||||
>{t("account_usage_tier_change_button")}</Button>
|
||||
}
|
||||
{config.enable_payments && account.role === "user" && account.billing?.customer &&
|
||||
{config.enable_payments && account.role === Role.USER && account.billing?.customer &&
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
|
@ -290,7 +296,7 @@ const AccountType = () => {
|
|||
onCancel={() => setUpgradeDialogOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
{account.billing?.status === "past_due" &&
|
||||
{account.billing?.status === SubscriptionStatus.PAST_DUE &&
|
||||
<Alert severity="error" sx={{mt: 1}}>{t("account_usage_tier_payment_overdue")}</Alert>
|
||||
}
|
||||
{account.billing?.cancel_at > 0 &&
|
||||
|
@ -318,7 +324,7 @@ const Stats = () => {
|
|||
{t("account_usage_title")}
|
||||
</Typography>
|
||||
<PrefGroup>
|
||||
{account.role !== "admin" &&
|
||||
{account.role === Role.USER &&
|
||||
<Pref title={t("account_usage_reservations_title")}>
|
||||
{account.limits.reservations > 0 &&
|
||||
<>
|
||||
|
@ -326,7 +332,7 @@ const Stats = () => {
|
|||
<Typography variant="body2"
|
||||
sx={{float: "left"}}>{account.stats.reservations}</Typography>
|
||||
<Typography variant="body2"
|
||||
sx={{float: "right"}}>{account.role === "user" ? t("account_usage_of_limit", {limit: account.limits.reservations}) : t("account_usage_unlimited")}</Typography>
|
||||
sx={{float: "right"}}>{account.role === Role.USER ? t("account_usage_of_limit", {limit: account.limits.reservations}) : t("account_usage_unlimited")}</Typography>
|
||||
</div>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
|
@ -347,11 +353,11 @@ const Stats = () => {
|
|||
}>
|
||||
<div>
|
||||
<Typography variant="body2" sx={{float: "left"}}>{account.stats.messages}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.role === "user" ? t("account_usage_of_limit", { limit: account.limits.messages }) : t("account_usage_unlimited")}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.role === Role.USER ? t("account_usage_of_limit", { limit: account.limits.messages }) : t("account_usage_unlimited")}</Typography>
|
||||
</div>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={account.role === "user" ? normalize(account.stats.messages, account.limits.messages) : 100}
|
||||
value={account.role === Role.USER ? normalize(account.stats.messages, account.limits.messages) : 100}
|
||||
/>
|
||||
</Pref>
|
||||
<Pref title={
|
||||
|
@ -362,11 +368,11 @@ const Stats = () => {
|
|||
}>
|
||||
<div>
|
||||
<Typography variant="body2" sx={{float: "left"}}>{account.stats.emails}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.role === "user" ? t("account_usage_of_limit", { limit: account.limits.emails }) : t("account_usage_unlimited")}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.role === Role.USER ? t("account_usage_of_limit", { limit: account.limits.emails }) : t("account_usage_unlimited")}</Typography>
|
||||
</div>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={account.role === "user" ? normalize(account.stats.emails, account.limits.emails) : 100}
|
||||
value={account.role === Role.USER ? normalize(account.stats.emails, account.limits.emails) : 100}
|
||||
/>
|
||||
</Pref>
|
||||
<Pref
|
||||
|
@ -382,15 +388,15 @@ const Stats = () => {
|
|||
>
|
||||
<div>
|
||||
<Typography variant="body2" sx={{float: "left"}}>{formatBytes(account.stats.attachment_total_size)}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.role === "user" ? t("account_usage_of_limit", { limit: formatBytes(account.limits.attachment_total_size) }) : t("account_usage_unlimited")}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.role === Role.USER ? t("account_usage_of_limit", { limit: formatBytes(account.limits.attachment_total_size) }) : t("account_usage_unlimited")}</Typography>
|
||||
</div>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={account.role === "user" ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100}
|
||||
value={account.role === Role.USER ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100}
|
||||
/>
|
||||
</Pref>
|
||||
</PrefGroup>
|
||||
{account.role === "user" && account.limits.basis === "ip" &&
|
||||
{account.role === Role.USER && account.limits.basis === LimitBasis.IP &&
|
||||
<Typography variant="body1">
|
||||
{t("account_usage_basis_ip_description")}
|
||||
</Typography>
|
||||
|
|
|
@ -28,7 +28,7 @@ import config from "../app/config";
|
|||
import ArticleIcon from '@mui/icons-material/Article';
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import session from "../app/Session";
|
||||
import accountApi from "../app/AccountApi";
|
||||
import accountApi, {Permission, Role} from "../app/AccountApi";
|
||||
import CelebrationIcon from '@mui/icons-material/Celebration';
|
||||
import UpgradeDialog from "./UpgradeDialog";
|
||||
import {AccountContext} from "./App";
|
||||
|
@ -104,7 +104,7 @@ const NavList = (props) => {
|
|||
navigate(routes.account);
|
||||
};
|
||||
|
||||
const isAdmin = account?.role === "admin";
|
||||
const isAdmin = account?.role === Role.ADMIN;
|
||||
const isPaid = account?.billing?.subscription;
|
||||
const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid;
|
||||
const showSubscriptionsList = props.subscriptions?.length > 0;
|
||||
|
@ -264,16 +264,16 @@ const SubscriptionItem = (props) => {
|
|||
<ListItemText primary={displayName} primaryTypographyProps={{ style: { overflow: "hidden", textOverflow: "ellipsis" } }}/>
|
||||
{subscription.reservation?.everyone &&
|
||||
<ListItemIcon edge="end" sx={{ minWidth: "26px" }}>
|
||||
{subscription.reservation?.everyone === "read-write" &&
|
||||
{subscription.reservation?.everyone === Permission.READ_WRITE &&
|
||||
<Tooltip title={t("prefs_reservations_table_everyone_read_write")}><PermissionReadWrite size="small"/></Tooltip>
|
||||
}
|
||||
{subscription.reservation?.everyone === "read-only" &&
|
||||
{subscription.reservation?.everyone === Permission.READ_ONLY &&
|
||||
<Tooltip title={t("prefs_reservations_table_everyone_read_only")}><PermissionRead size="small"/></Tooltip>
|
||||
}
|
||||
{subscription.reservation?.everyone === "write-only" &&
|
||||
{subscription.reservation?.everyone === Permission.WRITE_ONLY &&
|
||||
<Tooltip title={t("prefs_reservations_table_everyone_write_only")}><PermissionWrite size="small"/></Tooltip>
|
||||
}
|
||||
{subscription.reservation?.everyone === "deny-all" &&
|
||||
{subscription.reservation?.everyone === Permission.DENY_ALL &&
|
||||
<Tooltip title={t("prefs_reservations_table_everyone_deny_all")}><PermissionDenyAll size="small"/></Tooltip>
|
||||
}
|
||||
</ListItemIcon>
|
||||
|
|
|
@ -39,7 +39,7 @@ import {playSound, shuffle, sounds, validTopic, validUrl} from "../app/utils";
|
|||
import {useTranslation} from "react-i18next";
|
||||
import session from "../app/Session";
|
||||
import routes from "./routes";
|
||||
import accountApi, {UnauthorizedError} from "../app/AccountApi";
|
||||
import accountApi, {Permission, Role, UnauthorizedError} from "../app/AccountApi";
|
||||
import {Pref, PrefGroup} from "./Pref";
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import {Info, Public, PublicOff} from "@mui/icons-material";
|
||||
|
@ -485,11 +485,11 @@ const Reservations = () => {
|
|||
const [dialogKey, setDialogKey] = useState(0);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
|
||||
if (!config.enable_reservations || !session.exists() || !account || account.role === "admin") {
|
||||
if (!config.enable_reservations || !session.exists() || !account || account.role === Role.ADMIN) {
|
||||
return <></>;
|
||||
}
|
||||
const reservations = account.reservations || [];
|
||||
const limitReached = account.role === "user" && account.stats.reservations_remaining === 0;
|
||||
const limitReached = account.role === Role.USER && account.stats.reservations_remaining === 0;
|
||||
|
||||
const handleAddClick = () => {
|
||||
setDialogKey(prev => prev+1);
|
||||
|
@ -602,25 +602,25 @@ const ReservationsTable = (props) => {
|
|||
{reservation.topic}
|
||||
</TableCell>
|
||||
<TableCell aria-label={t("prefs_reservations_table_access_header")}>
|
||||
{reservation.everyone === "read-write" &&
|
||||
{reservation.everyone === Permission.READ_WRITE &&
|
||||
<>
|
||||
<Public fontSize="small" sx={{color: "grey", verticalAlign: "bottom", mr: 0.5}}/>
|
||||
{t("prefs_reservations_table_everyone_read_write")}
|
||||
</>
|
||||
}
|
||||
{reservation.everyone === "read-only" &&
|
||||
{reservation.everyone === Permission.READ_ONLY &&
|
||||
<>
|
||||
<PublicOff fontSize="small" sx={{color: "grey", verticalAlign: "bottom", mr: 0.5}}/>
|
||||
{t("prefs_reservations_table_everyone_read_only")}
|
||||
</>
|
||||
}
|
||||
{reservation.everyone === "write-only" &&
|
||||
{reservation.everyone === Permission.WRITE_ONLY &&
|
||||
<>
|
||||
<PublicOff fontSize="small" sx={{color: "grey", verticalAlign: "bottom", mr: 0.5}}/>
|
||||
{t("prefs_reservations_table_everyone_write_only")}
|
||||
</>
|
||||
}
|
||||
{reservation.everyone === "deny-all" &&
|
||||
{reservation.everyone === Permission.DENY_ALL &&
|
||||
<>
|
||||
<LockIcon fontSize="small" sx={{color: "grey", verticalAlign: "bottom", mr: 0.5}}/>
|
||||
{t("prefs_reservations_table_everyone_deny_all")}
|
||||
|
|
|
@ -5,6 +5,7 @@ import MenuItem from "@mui/material/MenuItem";
|
|||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import {PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite} from "./ReserveIcons";
|
||||
import {Permission} from "../app/AccountApi";
|
||||
|
||||
const ReserveTopicSelect = (props) => {
|
||||
const { t } = useTranslation();
|
||||
|
@ -24,19 +25,19 @@ const ReserveTopicSelect = (props) => {
|
|||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem value="deny-all">
|
||||
<MenuItem value={Permission.DENY_ALL}>
|
||||
<ListItemIcon><PermissionDenyAll/></ListItemIcon>
|
||||
<ListItemText primary={t("prefs_reservations_table_everyone_deny_all")}/>
|
||||
</MenuItem>
|
||||
<MenuItem value="read-only">
|
||||
<MenuItem value={Permission.READ_ONLY}>
|
||||
<ListItemIcon><PermissionRead/></ListItemIcon>
|
||||
<ListItemText primary={t("prefs_reservations_table_everyone_read_only")}/>
|
||||
</MenuItem>
|
||||
<MenuItem value="write-only">
|
||||
<MenuItem value={Permission.WRITE_ONLY}>
|
||||
<ListItemIcon><PermissionWrite/></ListItemIcon>
|
||||
<ListItemText primary={t("prefs_reservations_table_everyone_write_only")}/>
|
||||
</MenuItem>
|
||||
<MenuItem value="read-write">
|
||||
<MenuItem value={Permission.READ_WRITE}>
|
||||
<ListItemIcon><PermissionReadWrite/></ListItemIcon>
|
||||
<ListItemText primary={t("prefs_reservations_table_everyone_read_write")}/>
|
||||
</MenuItem>
|
||||
|
|
|
@ -17,7 +17,7 @@ import DialogFooter from "./DialogFooter";
|
|||
import {useTranslation} from "react-i18next";
|
||||
import session from "../app/Session";
|
||||
import routes from "./routes";
|
||||
import accountApi, {TopicReservedError, UnauthorizedError} from "../app/AccountApi";
|
||||
import accountApi, {Role, TopicReservedError, UnauthorizedError} from "../app/AccountApi";
|
||||
import ReserveTopicSelect from "./ReserveTopicSelect";
|
||||
import {AccountContext} from "./App";
|
||||
|
||||
|
@ -87,7 +87,7 @@ const SubscribePage = (props) => {
|
|||
const existingBaseUrls = Array
|
||||
.from(new Set([publicBaseUrl, ...props.subscriptions.map(s => s.baseUrl)]))
|
||||
.filter(s => s !== config.base_url);
|
||||
const reserveTopicEnabled = session.exists() && account?.role === "user" && (account?.stats.reservations_remaining || 0) > 0;
|
||||
const reserveTopicEnabled = session.exists() && account?.role === Role.USER && (account?.stats.reservations_remaining || 0) > 0;
|
||||
|
||||
const handleSubscribe = async () => {
|
||||
const user = await userManager.get(baseUrl); // May be undefined
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import {useContext, useEffect, useState} from 'react';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
|
@ -6,16 +7,14 @@ import {Alert, CardActionArea, CardContent, ListItem, useMediaQuery} from "@mui/
|
|||
import theme from "./theme";
|
||||
import DialogFooter from "./DialogFooter";
|
||||
import Button from "@mui/material/Button";
|
||||
import accountApi, {TopicReservedError, UnauthorizedError} from "../app/AccountApi";
|
||||
import accountApi, {UnauthorizedError} from "../app/AccountApi";
|
||||
import session from "../app/Session";
|
||||
import routes from "./routes";
|
||||
import {useContext, useEffect, useState} from "react";
|
||||
import Card from "@mui/material/Card";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import {AccountContext} from "./App";
|
||||
import {formatBytes, formatNumber, formatShortDate} from "../app/utils";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import subscriptionManager from "../app/SubscriptionManager";
|
||||
import List from "@mui/material/List";
|
||||
import {Check} from "@mui/icons-material";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
|
|
Loading…
Reference in a new issue