JS constants

pull/600/head
binwiederhier 2023-01-30 13:10:45 -05:00
parent ef8f7c9884
commit 259293f9b3
8 changed files with 77 additions and 44 deletions

View File

@ -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
*/

View File

@ -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");

View File

@ -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>

View File

@ -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>

View File

@ -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")}

View File

@ -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>

View File

@ -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

View File

@ -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";