Update web app with SMS and calls stuff

This commit is contained in:
binwiederhier 2023-05-07 22:28:07 -04:00
parent 7677c50b0e
commit eb0805a470
14 changed files with 274 additions and 39 deletions

View file

@ -6,12 +6,14 @@
// During web development, you may change values here for rapid testing.
var config = {
base_url: window.location.origin, // Change to test against a different server
base_url: "http://127.0.0.1:2586",// FIXME window.location.origin, // Change to test against a different server
app_root: "/app",
enable_login: true,
enable_signup: true,
enable_payments: true,
enable_reservations: true,
enable_sms: true,
enable_calls: true,
billing_contact: "",
disallowed_topics: ["docs", "static", "file", "app", "account", "settings", "signup", "login", "v1"]
};

View file

@ -127,6 +127,12 @@
"publish_dialog_email_label": "Email",
"publish_dialog_email_placeholder": "Address to forward the notification to, e.g. phil@example.com",
"publish_dialog_email_reset": "Remove email forward",
"publish_dialog_sms_label": "SMS",
"publish_dialog_sms_placeholder": "Phone number to send SMS to, e.g. +12223334444",
"publish_dialog_sms_reset": "Remove SMS message",
"publish_dialog_call_label": "Phone call",
"publish_dialog_call_placeholder": "Phone number to call with the message, e.g. +12223334444",
"publish_dialog_call_reset": "Remove phone call",
"publish_dialog_attach_label": "Attachment URL",
"publish_dialog_attach_placeholder": "Attach file by URL, e.g. https://f-droid.org/F-Droid.apk",
"publish_dialog_attach_reset": "Remove attachment URL",
@ -138,6 +144,8 @@
"publish_dialog_other_features": "Other features:",
"publish_dialog_chip_click_label": "Click URL",
"publish_dialog_chip_email_label": "Forward to email",
"publish_dialog_chip_sms_label": "Send SMS",
"publish_dialog_chip_call_label": "Phone call",
"publish_dialog_chip_attach_url_label": "Attach file by URL",
"publish_dialog_chip_attach_file_label": "Attach local file",
"publish_dialog_chip_delay_label": "Delay delivery",
@ -203,6 +211,10 @@
"account_basics_tier_manage_billing_button": "Manage billing",
"account_usage_messages_title": "Published messages",
"account_usage_emails_title": "Emails sent",
"account_usage_sms_title": "SMS sent",
"account_usage_sms_none": "No SMS can be sent with this account",
"account_usage_calls_title": "Phone calls made",
"account_usage_calls_none": "No phone calls can be made with this account",
"account_usage_reservations_title": "Reserved topics",
"account_usage_reservations_none": "No reserved topics for this account",
"account_usage_attachment_storage_title": "Attachment storage",
@ -232,6 +244,12 @@
"account_upgrade_dialog_tier_features_messages_other": "{{messages}} daily messages",
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} daily email",
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} daily emails",
"account_upgrade_dialog_tier_features_sms_one": "{{sms}} daily SMS",
"account_upgrade_dialog_tier_features_sms_other": "{{sms}} daily SMS",
"account_upgrade_dialog_tier_features_no_sms": "No daily SMS",
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} daily phone calls",
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} daily phone calls",
"account_upgrade_dialog_tier_features_no_calls": "No daily phone calls",
"account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} per file",
"account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} total storage",
"account_upgrade_dialog_tier_price_per_month": "month",

View file

@ -206,10 +206,12 @@ export const formatBytes = (bytes, decimals = 2) => {
}
export const formatNumber = (n) => {
if (n % 1000 === 0) {
if (n === 0) {
return n;
} else if (n % 1000 === 0) {
return `${n/1000}k`;
}
return n;
return n.toLocaleString();
}
export const formatPrice = (n) => {

View file

@ -51,6 +51,7 @@ import {ContentCopy, Public} from "@mui/icons-material";
import MenuItem from "@mui/material/MenuItem";
import DialogContentText from "@mui/material/DialogContentText";
import {IncorrectPasswordError, UnauthorizedError} from "../app/errors";
import {ProChip} from "./SubscriptionPopup";
const Account = () => {
if (!session.exists()) {
@ -337,23 +338,18 @@ const Stats = () => {
{t("account_usage_title")}
</Typography>
<PrefGroup>
<Pref title={t("account_usage_reservations_title")}>
{(account.role === Role.ADMIN || account.limits.reservations > 0) &&
<>
<div>
<Typography variant="body2" sx={{float: "left"}}>{account.stats.reservations}</Typography>
<Typography variant="body2" 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"
value={account.role === Role.USER && account.limits.reservations > 0 ? normalize(account.stats.reservations, account.limits.reservations) : 100}
/>
</>
}
{account.role === Role.USER && account.limits.reservations === 0 &&
<em>{t("account_usage_reservations_none")}</em>
}
</Pref>
{(account.role === Role.ADMIN || account.limits.reservations > 0) &&
<Pref title={t("account_usage_reservations_title")}>
<div>
<Typography variant="body2" sx={{float: "left"}}>{account.stats.reservations.toLocaleString()}</Typography>
<Typography variant="body2" sx={{float: "right"}}>{account.role === Role.USER ? t("account_usage_of_limit", { limit: account.limits.reservations.toLocaleString() }) : t("account_usage_unlimited")}</Typography>
</div>
<LinearProgress
variant="determinate"
value={account.role === Role.USER && account.limits.reservations > 0 ? normalize(account.stats.reservations, account.limits.reservations) : 100}
/>
</Pref>
}
<Pref title={
<>
{t("account_usage_messages_title")}
@ -361,8 +357,8 @@ const Stats = () => {
</>
}>
<div>
<Typography variant="body2" sx={{float: "left"}}>{account.stats.messages}</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>
<Typography variant="body2" sx={{float: "left"}}>{account.stats.messages.toLocaleString()}</Typography>
<Typography variant="body2" sx={{float: "right"}}>{account.role === Role.USER ? t("account_usage_of_limit", { limit: account.limits.messages.toLocaleString() }) : t("account_usage_unlimited")}</Typography>
</div>
<LinearProgress
variant="determinate"
@ -376,14 +372,48 @@ const Stats = () => {
</>
}>
<div>
<Typography variant="body2" sx={{float: "left"}}>{account.stats.emails}</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>
<Typography variant="body2" sx={{float: "left"}}>{account.stats.emails.toLocaleString()}</Typography>
<Typography variant="body2" sx={{float: "right"}}>{account.role === Role.USER ? t("account_usage_of_limit", { limit: account.limits.emails.toLocaleString() }) : t("account_usage_unlimited")}</Typography>
</div>
<LinearProgress
variant="determinate"
value={account.role === Role.USER ? normalize(account.stats.emails, account.limits.emails) : 100}
/>
</Pref>
{(account.role === Role.ADMIN || account.limits.sms > 0) &&
<Pref title={
<>
{t("account_usage_sms_title")}
<Tooltip title={t("account_usage_limits_reset_daily")}><span><InfoIcon/></span></Tooltip>
</>
}>
<div>
<Typography variant="body2" sx={{float: "left"}}>{account.stats.sms.toLocaleString()}</Typography>
<Typography variant="body2" sx={{float: "right"}}>{account.role === Role.USER ? t("account_usage_of_limit", { limit: account.limits.sms.toLocaleString() }) : t("account_usage_unlimited")}</Typography>
</div>
<LinearProgress
variant="determinate"
value={account.role === Role.USER && account.limits.sms > 0 ? normalize(account.stats.sms, account.limits.sms) : 100}
/>
</Pref>
}
{(account.role === Role.ADMIN || account.limits.calls > 0) &&
<Pref title={
<>
{t("account_usage_calls_title")}
<Tooltip title={t("account_usage_limits_reset_daily")}><span><InfoIcon/></span></Tooltip>
</>
}>
<div>
<Typography variant="body2" sx={{float: "left"}}>{account.stats.calls.toLocaleString()}</Typography>
<Typography variant="body2" sx={{float: "right"}}>{account.role === Role.USER ? t("account_usage_of_limit", { limit: account.limits.calls.toLocaleString() }) : t("account_usage_unlimited")}</Typography>
</div>
<LinearProgress
variant="determinate"
value={account.role === Role.USER && account.limits.sms > 0 ? normalize(account.stats.calls, account.limits.calls) : 100}
/>
</Pref>
}
<Pref
alignTop
title={t("account_usage_attachment_storage_title")}
@ -404,6 +434,21 @@ const Stats = () => {
value={account.role === Role.USER ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100}
/>
</Pref>
{config.enable_reservations && account.role === Role.USER && account.limits.reservations === 0 &&
<Pref title={<>{t("account_usage_reservations_title")}{config.enable_payments && <ProChip/>}</>}>
<em>{t("account_usage_reservations_none")}</em>
</Pref>
}
{config.enable_sms && account.role === Role.USER && account.limits.sms === 0 &&
<Pref title={<>{t("account_usage_sms_title")}{config.enable_payments && <ProChip/>}</>}>
<em>{t("account_usage_sms_none")}</em>
</Pref>
}
{config.enable_calls && account.role === Role.USER && account.limits.calls === 0 &&
<Pref title={<>{t("account_usage_calls_title")}{config.enable_payments && <ProChip/>}</>}>
<em>{t("account_usage_calls_none")}</em>
</Pref>
}
</PrefGroup>
{account.role === Role.USER && account.limits.basis === LimitBasis.IP &&
<Typography variant="body1">

View file

@ -45,6 +45,8 @@ const PublishDialog = (props) => {
const [filename, setFilename] = useState("");
const [filenameEdited, setFilenameEdited] = useState(false);
const [email, setEmail] = useState("");
const [sms, setSms] = useState("");
const [call, setCall] = useState("");
const [delay, setDelay] = useState("");
const [publishAnother, setPublishAnother] = useState(false);
@ -52,6 +54,8 @@ const PublishDialog = (props) => {
const [showClickUrl, setShowClickUrl] = useState(false);
const [showAttachUrl, setShowAttachUrl] = useState(false);
const [showEmail, setShowEmail] = useState(false);
const [showSms, setShowSms] = useState(false);
const [showCall, setShowCall] = useState(false);
const [showDelay, setShowDelay] = useState(false);
const showAttachFile = !!attachFile && !showAttachUrl;
@ -124,6 +128,12 @@ const PublishDialog = (props) => {
if (email.trim()) {
url.searchParams.append("email", email.trim());
}
if (sms.trim()) {
url.searchParams.append("sms", sms.trim());
}
if (call.trim()) {
url.searchParams.append("call", call.trim());
}
if (delay.trim()) {
url.searchParams.append("delay", delay.trim());
}
@ -406,6 +416,48 @@ const PublishDialog = (props) => {
/>
</ClosableRow>
}
{showSms &&
<ClosableRow disabled={disabled} closeLabel={t("publish_dialog_sms_reset")} onClose={() => {
setSms("");
setShowSms(false);
}}>
<TextField
margin="dense"
label={t("publish_dialog_sms_label")}
placeholder={t("publish_dialog_sms_placeholder")}
value={sms}
onChange={ev => setSms(ev.target.value)}
disabled={disabled}
type="tel"
variant="standard"
fullWidth
inputProps={{
"aria-label": t("publish_dialog_sms_label")
}}
/>
</ClosableRow>
}
{showCall &&
<ClosableRow disabled={disabled} closeLabel={t("publish_dialog_call_reset")} onClose={() => {
setCall("");
setShowCall(false);
}}>
<TextField
margin="dense"
label={t("publish_dialog_call_label")}
placeholder={t("publish_dialog_call_placeholder")}
value={call}
onChange={ev => setCall(ev.target.value)}
disabled={disabled}
type="tel"
variant="standard"
fullWidth
inputProps={{
"aria-label": t("publish_dialog_call_label")
}}
/>
</ClosableRow>
}
{showAttachUrl &&
<ClosableRow disabled={disabled} closeLabel={t("publish_dialog_attach_reset")} onClose={() => {
setAttachUrl("");
@ -510,6 +562,8 @@ const PublishDialog = (props) => {
<div>
{!showClickUrl && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_click_label")} aria-label={t("publish_dialog_chip_click_label")} onClick={() => setShowClickUrl(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
{!showEmail && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_email_label")} aria-label={t("publish_dialog_chip_email_label")} onClick={() => setShowEmail(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
{!showSms && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_sms_label")} aria-label={t("publish_dialog_chip_sms_label")} onClick={() => setShowSms(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
{!showCall && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_call_label")} aria-label={t("publish_dialog_chip_call_label")} onClick={() => setShowCall(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
{!showAttachUrl && !showAttachFile && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_attach_url_label")} aria-label={t("publish_dialog_chip_attach_url_label")} onClick={() => setShowAttachUrl(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
{!showAttachFile && !showAttachUrl && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_attach_file_label")} aria-label={t("publish_dialog_chip_attach_file_label")} onClick={() => handleAttachFileClick()} sx={{marginRight: 1, marginBottom: 1}}/>}
{!showDelay && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_delay_label")} aria-label={t("publish_dialog_chip_delay_label")} onClick={() => setShowDelay(true)} sx={{marginRight: 1, marginBottom: 1}}/>}

View file

@ -277,14 +277,14 @@ const LimitReachedChip = () => {
);
};
const ProChip = () => {
export const ProChip = () => {
const { t } = useTranslation();
return (
<Chip
label={"ntfy Pro"}
variant="outlined"
color="primary"
sx={{ opacity: 0.8, borderWidth: "2px", height: "24px", marginLeft: "5px" }}
sx={{ opacity: 0.8, fontWeight: "bold", borderWidth: "2px", height: "24px", marginLeft: "5px" }}
/>
);
};

View file

@ -298,11 +298,14 @@ const TierCard = (props) => {
</div>
<List dense>
{tier.limits.reservations > 0 && <Feature>{t("account_upgrade_dialog_tier_features_reservations", { reservations: tier.limits.reservations, count: tier.limits.reservations })}</Feature>}
{tier.limits.reservations === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_reservations")}</NoFeature>}
<Feature>{t("account_upgrade_dialog_tier_features_messages", { messages: formatNumber(tier.limits.messages), count: tier.limits.messages })}</Feature>
<Feature>{t("account_upgrade_dialog_tier_features_emails", { emails: formatNumber(tier.limits.emails), count: tier.limits.emails })}</Feature>
{tier.limits.sms > 0 && <Feature>{t("account_upgrade_dialog_tier_features_sms", { sms: formatNumber(tier.limits.sms), count: tier.limits.sms })}</Feature>}
{tier.limits.calls > 0 && <Feature>{t("account_upgrade_dialog_tier_features_calls", { calls: formatNumber(tier.limits.calls), count: tier.limits.calls })}</Feature>}
<Feature>{t("account_upgrade_dialog_tier_features_attachment_file_size", { filesize: formatBytes(tier.limits.attachment_file_size, 0) })}</Feature>
<Feature>{t("account_upgrade_dialog_tier_features_attachment_total_size", { totalsize: formatBytes(tier.limits.attachment_total_size, 0) })}</Feature>
{tier.limits.reservations === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_reservations")}</NoFeature>}
{tier.limits.sms === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_sms")}</NoFeature>}
{tier.limits.calls === 0 && <NoFeature>{t("account_upgrade_dialog_tier_features_no_calls")}</NoFeature>}
</List>
{tier.prices && props.interval === SubscriptionInterval.MONTH &&
<Typography variant="body2" color="gray">