Upgrade banner

This commit is contained in:
binwiederhier 2023-01-04 22:47:12 -05:00
parent a91da7cf2c
commit 3280c2c440
22 changed files with 114 additions and 118 deletions

View file

@ -107,8 +107,8 @@ type Config struct {
EnableSignup bool
EnableLogin bool
EnableEmailConfirm bool
EnableResetPassword bool
EnableAccountUpgrades bool
EnablePasswordReset bool
EnablePayments bool
Version string // injected by App
}

View file

@ -452,7 +452,7 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
AppRoot: appRoot,
EnableLogin: s.config.EnableLogin,
EnableSignup: s.config.EnableSignup,
EnableResetPassword: s.config.EnableResetPassword,
EnablePasswordReset: s.config.EnablePasswordReset,
DisallowedTopics: disallowedTopics,
}
b, err := json.Marshal(response)

View file

@ -80,18 +80,18 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, _ *http.Request, v *vis
}
if v.user.Plan != nil {
response.Plan = &apiAccountPlan{
Code: v.user.Plan.Code,
Upgradable: v.user.Plan.Upgradable,
Code: v.user.Plan.Code,
Upgradeable: v.user.Plan.Upgradeable,
}
} else if v.user.Role == user.RoleAdmin {
response.Plan = &apiAccountPlan{
Code: string(user.PlanUnlimited),
Upgradable: false,
Code: string(user.PlanUnlimited),
Upgradeable: false,
}
} else {
response.Plan = &apiAccountPlan{
Code: string(user.PlanDefault),
Upgradable: true,
Code: string(user.PlanDefault),
Upgradeable: true,
}
}
reservations, err := s.userManager.Reservations(v.user.Name)
@ -111,8 +111,8 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, _ *http.Request, v *vis
response.Username = user.Everyone
response.Role = string(user.RoleAnonymous)
response.Plan = &apiAccountPlan{
Code: string(user.PlanNone),
Upgradable: true,
Code: string(user.PlanNone),
Upgradeable: true,
}
}
w.Header().Set("Content-Type", "application/json")

View file

@ -235,8 +235,8 @@ type apiAccountTokenResponse struct {
}
type apiAccountPlan struct {
Code string `json:"code"`
Upgradable bool `json:"upgradable"`
Code string `json:"code"`
Upgradeable bool `json:"upgradeable"`
}
type apiAccountLimits struct {
@ -286,6 +286,7 @@ type apiConfigResponse struct {
AppRoot string `json:"app_root"`
EnableLogin bool `json:"enable_login"`
EnableSignup bool `json:"enable_signup"`
EnableResetPassword bool `json:"enable_reset_password"`
EnablePasswordReset bool `json:"enable_password_reset"`
EnablePayments bool `json:"enable_payments"`
DisallowedTopics []string `json:"disallowed_topics"`
}

View file

@ -503,7 +503,7 @@ func (a *Manager) readUser(rows *sql.Rows) (*User, error) {
if planCode.Valid {
user.Plan = &Plan{
Code: planCode.String,
Upgradable: true, // FIXME
Upgradeable: false,
MessagesLimit: messagesLimit.Int64,
EmailsLimit: emailsLimit.Int64,
TopicsLimit: topicsLimit.Int64,

View file

@ -56,7 +56,7 @@ const (
// Plan represents a user's account type, including its account limits
type Plan struct {
Code string `json:"name"`
Upgradable bool `json:"upgradable"`
Upgradeable bool `json:"upgradeable"`
MessagesLimit int64 `json:"messages_limit"`
EmailsLimit int64 `json:"emails_limit"`
TopicsLimit int64 `json:"topics_limit"`

View file

@ -6,10 +6,11 @@
// During web development, you may change values here for rapid testing.
var config = {
baseUrl: "http://localhost:2586", // window.location.origin FIXME update before merging
appRoot: "/app",
enableLogin: true,
enableSignup: true,
enableResetPassword: false,
disallowedTopics: ["docs", "static", "file", "app", "account", "settings", "pricing", "signup", "login", "reset-password"]
base_url: "http://localhost:2586", // window.location.origin FIXME update before merging
app_root: "/app",
enable_login: true,
enable_signup: true,
enable_password_reset: false,
enable_payments: true,
disallowed_topics: ["docs", "static", "file", "app", "account", "settings", "pricing", "signup", "login", "reset-password"]
};

View file

@ -16,6 +16,7 @@
"action_bar_show_menu": "Show menu",
"action_bar_logo_alt": "ntfy logo",
"action_bar_settings": "Settings",
"action_bar_account": "Account",
"action_bar_subscription_settings": "Subscription settings",
"action_bar_send_test_notification": "Send test notification",
"action_bar_clear_notifications": "Clear all notifications",

View file

@ -34,7 +34,7 @@ class AccountApi {
}
async login(user) {
const url = accountTokenUrl(config.baseUrl);
const url = accountTokenUrl(config.base_url);
console.log(`[AccountApi] Checking auth for ${url}`);
const response = await fetch(url, {
method: "POST",
@ -53,7 +53,7 @@ class AccountApi {
}
async logout() {
const url = accountTokenUrl(config.baseUrl);
const url = accountTokenUrl(config.base_url);
console.log(`[AccountApi] Logging out from ${url} using token ${session.token()}`);
const response = await fetch(url, {
method: "DELETE",
@ -67,7 +67,7 @@ class AccountApi {
}
async create(username, password) {
const url = accountUrl(config.baseUrl);
const url = accountUrl(config.base_url);
const body = JSON.stringify({
username: username,
password: password
@ -87,7 +87,7 @@ class AccountApi {
}
async get() {
const url = accountUrl(config.baseUrl);
const url = accountUrl(config.base_url);
console.log(`[AccountApi] Fetching user account ${url}`);
const response = await fetch(url, {
headers: withBearerAuth({}, session.token())
@ -106,7 +106,7 @@ class AccountApi {
}
async delete() {
const url = accountUrl(config.baseUrl);
const url = accountUrl(config.base_url);
console.log(`[AccountApi] Deleting user account ${url}`);
const response = await fetch(url, {
method: "DELETE",
@ -120,7 +120,7 @@ class AccountApi {
}
async changePassword(newPassword) {
const url = accountPasswordUrl(config.baseUrl);
const url = accountPasswordUrl(config.base_url);
console.log(`[AccountApi] Changing account password ${url}`);
const response = await fetch(url, {
method: "POST",
@ -137,7 +137,7 @@ class AccountApi {
}
async extendToken() {
const url = accountTokenUrl(config.baseUrl);
const url = accountTokenUrl(config.base_url);
console.log(`[AccountApi] Extending user access token ${url}`);
const response = await fetch(url, {
method: "PATCH",
@ -151,7 +151,7 @@ class AccountApi {
}
async updateSettings(payload) {
const url = accountSettingsUrl(config.baseUrl);
const url = accountSettingsUrl(config.base_url);
const body = JSON.stringify(payload);
console.log(`[AccountApi] Updating user account ${url}: ${body}`);
const response = await fetch(url, {
@ -167,7 +167,7 @@ class AccountApi {
}
async addSubscription(payload) {
const url = accountSubscriptionUrl(config.baseUrl);
const url = accountSubscriptionUrl(config.base_url);
const body = JSON.stringify(payload);
console.log(`[AccountApi] Adding user subscription ${url}: ${body}`);
const response = await fetch(url, {
@ -186,7 +186,7 @@ class AccountApi {
}
async updateSubscription(remoteId, payload) {
const url = accountSubscriptionSingleUrl(config.baseUrl, remoteId);
const url = accountSubscriptionSingleUrl(config.base_url, remoteId);
const body = JSON.stringify(payload);
console.log(`[AccountApi] Updating user subscription ${url}: ${body}`);
const response = await fetch(url, {
@ -205,7 +205,7 @@ class AccountApi {
}
async deleteSubscription(remoteId) {
const url = accountSubscriptionSingleUrl(config.baseUrl, remoteId);
const url = accountSubscriptionSingleUrl(config.base_url, remoteId);
console.log(`[AccountApi] Removing user subscription ${url}`);
const response = await fetch(url, {
method: "DELETE",
@ -219,7 +219,7 @@ class AccountApi {
}
async upsertAccess(topic, everyone) {
const url = accountAccessUrl(config.baseUrl);
const url = accountAccessUrl(config.base_url);
console.log(`[AccountApi] Upserting user access to topic ${topic}, everyone=${everyone}`);
const response = await fetch(url, {
method: "POST",
@ -239,7 +239,7 @@ class AccountApi {
}
async deleteAccess(topic) {
const url = accountAccessSingleUrl(config.baseUrl, topic);
const url = accountAccessSingleUrl(config.base_url, topic);
console.log(`[AccountApi] Removing topic reservation ${url}`);
const response = await fetch(url, {
method: "DELETE",

View file

@ -43,7 +43,7 @@ class SubscriptionManager {
for (let i = 0; i < remoteSubscriptions.length; i++) {
const remote = remoteSubscriptions[i];
const local = await this.add(remote.base_url, remote.topic);
const reservation = remoteReservations?.find(r => remote.base_url === config.baseUrl && remote.topic === r.topic) || null;
const reservation = remoteReservations?.find(r => remote.base_url === config.base_url && remote.topic === r.topic) || null;
await this.setRemoteId(local.id, remote.id);
await this.setDisplayName(local.id, remote.display_name);
await this.setReservation(local.id, reservation); // May be null!

View file

@ -11,21 +11,21 @@ class UserManager {
}
async get(baseUrl) {
if (session.exists() && baseUrl === config.baseUrl) {
if (session.exists() && baseUrl === config.base_url) {
return this.localUser();
}
return db.users.get(baseUrl);
}
async save(user) {
if (session.exists() && user.baseUrl === config.baseUrl) {
if (session.exists() && user.baseUrl === config.base_url) {
return;
}
await db.users.put(user);
}
async delete(baseUrl) {
if (session.exists() && baseUrl === config.baseUrl) {
if (session.exists() && baseUrl === config.base_url) {
return;
}
await db.users.delete(baseUrl);
@ -36,7 +36,7 @@ class UserManager {
return null;
}
return {
baseUrl: config.baseUrl,
baseUrl: config.base_url,
username: session.username(),
token: session.token() // Not "password"!
};

View file

@ -42,13 +42,13 @@ export const validTopic = (topic) => {
}
export const disallowedTopic = (topic) => {
return config.disallowedTopics.includes(topic);
return config.disallowed_topics.includes(topic);
}
export const topicDisplayName = (subscription) => {
if (subscription.displayName) {
return subscription.displayName;
} else if (subscription.baseUrl === config.baseUrl) {
} else if (subscription.baseUrl === config.base_url) {
return subscription.topic;
}
return topicShortUrl(subscription.baseUrl, subscription.topic);

View file

@ -5,17 +5,12 @@ import IconButton from "@mui/material/IconButton";
import MenuIcon from "@mui/icons-material/Menu";
import Typography from "@mui/material/Typography";
import * as React from "react";
import {useEffect, useRef, useState} from "react";
import {useState} from "react";
import Box from "@mui/material/Box";
import {formatShortDateTime, shuffle, topicDisplayName} from "../app/utils";
import db from "../app/db";
import {useLocation, useNavigate} from "react-router-dom";
import ClickAwayListener from '@mui/material/ClickAwayListener';
import Grow from '@mui/material/Grow';
import Paper from '@mui/material/Paper';
import Popper from '@mui/material/Popper';
import MenuItem from '@mui/material/MenuItem';
import MenuList from '@mui/material/MenuList';
import MoreVertIcon from "@mui/icons-material/MoreVert";
import NotificationsIcon from '@mui/icons-material/Notifications';
import NotificationsOffIcon from '@mui/icons-material/NotificationsOff';
@ -24,7 +19,7 @@ import routes from "./routes";
import subscriptionManager from "../app/SubscriptionManager";
import logo from "../img/ntfy.svg";
import {useTranslation} from "react-i18next";
import {Menu, Portal, Snackbar} from "@mui/material";
import {Portal, Snackbar} from "@mui/material";
import SubscriptionSettingsDialog from "./SubscriptionSettingsDialog";
import session from "../app/Session";
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
@ -41,8 +36,10 @@ const ActionBar = (props) => {
let title = "ntfy";
if (props.selected) {
title = topicDisplayName(props.selected);
} else if (location.pathname === "/settings") {
} else if (location.pathname === routes.settings) {
title = t("action_bar_settings");
} else if (location.pathname === routes.account) {
title = t("action_bar_account");
}
return (
<AppBar position="fixed" sx={{
@ -250,12 +247,12 @@ const ProfileIcon = () => {
<AccountCircleIcon/>
</IconButton>
}
{!session.exists() && config.enableLogin &&
{!session.exists() && config.enable_login &&
<Button color="inherit" variant="text" onClick={() => navigate(routes.login)} sx={{m: 1}} aria-label={t("action_bar_sign_in")}>
{t("action_bar_sign_in")}
</Button>
}
{!session.exists() && config.enableSignup &&
{!session.exists() && config.enable_signup &&
<Button color="inherit" variant="outlined" onClick={() => navigate(routes.signup)} aria-label={t("action_bar_sign_up")}>
{t("action_bar_sign_up")}
</Button>

View file

@ -79,7 +79,7 @@ const Layout = () => {
const newNotificationsCount = subscriptions?.reduce((prev, cur) => prev + cur.new, 0) || 0;
const [selected] = (subscriptions || []).filter(s => {
return (params.baseUrl && expandUrl(params.baseUrl).includes(s.baseUrl) && params.topic === s.topic)
|| (config.baseUrl === s.baseUrl && params.topic === s.topic)
|| (config.base_url === s.baseUrl && params.topic === s.topic)
});
useConnectionListeners(subscriptions, users);
@ -95,6 +95,7 @@ const Layout = () => {
onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
/>
<Navigation
account={account}
subscriptions={subscriptions}
selectedSubscription={selected}
notificationsGranted={notificationsGranted}

View file

@ -41,7 +41,7 @@ const Login = () => {
}
}
};
if (!config.enableLogin) {
if (!config.enable_login) {
return (
<AvatarBox>
<Typography sx={{ typography: 'h6' }}>{t("Login is disabled")}</Typography>
@ -112,8 +112,8 @@ const Login = () => {
</Box>
}
<Box sx={{width: "100%"}}>
{config.enableResetPassword && <div style={{float: "left"}}><NavLink to={routes.resetPassword} variant="body1">{t("Reset password")}</NavLink></div>}
{config.enableSignup && <div style={{float: "right"}}><NavLink to={routes.signup} variant="body1">{t("login_link_signup")}</NavLink></div>}
{config.enable_password_reset && <div style={{float: "left"}}><NavLink to={routes.resetPassword} variant="body1">{t("Reset password")}</NavLink></div>}
{config.enable_signup && <div style={{float: "right"}}><NavLink to={routes.signup} variant="body1">{t("login_link_signup")}</NavLink></div>}
</Box>
</Box>
</AvatarBox>

View file

@ -38,7 +38,7 @@ const Messaging = (props) => {
<PublishDialog
key={`publishDialog${dialogKey}`} // Resets dialog when canceled/closed
openMode={dialogOpenMode}
baseUrl={subscription?.baseUrl ?? config.baseUrl}
baseUrl={subscription?.baseUrl ?? config.base_url}
topic={subscription?.topic ?? ""}
message={message}
onClose={handleDialogClose}

View file

@ -12,24 +12,15 @@ import List from "@mui/material/List";
import SettingsIcon from "@mui/icons-material/Settings";
import AddIcon from "@mui/icons-material/Add";
import SubscribeDialog from "./SubscribeDialog";
import {
Alert,
AlertTitle,
Badge,
CircularProgress,
Link,
ListItem,
ListItemSecondaryAction,
ListSubheader, Tooltip
} from "@mui/material";
import {Alert, AlertTitle, Badge, CircularProgress, Link, ListSubheader, Tooltip} from "@mui/material";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import {openUrl, topicDisplayName, topicUrl} from "../app/utils";
import routes from "./routes";
import {ConnectionState} from "../app/Connection";
import {useLocation, useNavigate, useOutletContext} from "react-router-dom";
import {useLocation, useNavigate} from "react-router-dom";
import subscriptionManager from "../app/SubscriptionManager";
import {ChatBubble, Lock, MoreVert, NotificationsOffOutlined, Public, PublicOff, Send} from "@mui/icons-material";
import {ChatBubble, Lock, NotificationsOffOutlined, Public, PublicOff, Send} from "@mui/icons-material";
import Box from "@mui/material/Box";
import notifier from "../app/Notifier";
import config from "../app/config";
@ -37,8 +28,7 @@ import ArticleIcon from '@mui/icons-material/Article';
import {Trans, useTranslation} from "react-i18next";
import session from "../app/Session";
import accountApi from "../app/AccountApi";
import IconButton from "@mui/material/IconButton";
import CloseIcon from "@mui/icons-material/Close";
import CelebrationIcon from '@mui/icons-material/Celebration';
const navWidth = 280;
@ -109,6 +99,7 @@ const NavList = (props) => {
navigate(routes.account);
};
const showUpgradeBanner = config.enable_payments && (!props.account || props.account.plan.upgradeable);
const showSubscriptionsList = props.subscriptions?.length > 0;
const showNotificationBrowserNotSupportedBox = !notifier.browserSupported();
const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser
@ -123,14 +114,14 @@ const NavList = (props) => {
{showNotificationContextNotSupportedBox && <NotificationContextNotSupportedAlert/>}
{showNotificationGrantBox && <NotificationGrantAlert onRequestPermissionClick={handleRequestNotificationPermission}/>}
{!showSubscriptionsList &&
<ListItemButton onClick={() => navigate(routes.app)} selected={location.pathname === config.appRoot}>
<ListItemButton onClick={() => navigate(routes.app)} selected={location.pathname === config.app_root}>
<ListItemIcon><ChatBubble/></ListItemIcon>
<ListItemText primary={t("nav_button_all_notifications")}/>
</ListItemButton>}
{showSubscriptionsList &&
<>
<ListSubheader>{t("nav_topics_title")}</ListSubheader>
<ListItemButton onClick={() => navigate(routes.app)} selected={location.pathname === config.appRoot}>
<ListItemButton onClick={() => navigate(routes.app)} selected={location.pathname === config.app_root}>
<ListItemIcon><ChatBubble/></ListItemIcon>
<ListItemText primary={t("nav_button_all_notifications")}/>
</ListItemButton>
@ -162,6 +153,34 @@ const NavList = (props) => {
<ListItemIcon><AddIcon/></ListItemIcon>
<ListItemText primary={t("nav_button_subscribe")}/>
</ListItemButton>
{showUpgradeBanner &&
<Box sx={{
position: "fixed",
width: `${Navigation.width - 1}px`,
bottom: 0,
mt: 'auto',
background: "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)",
}}>
<Divider/>
<ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
<ListItemIcon><CelebrationIcon sx={{ color: "#55b86e" }} fontSize="large"/></ListItemIcon>
<ListItemText
sx={{ ml: 1 }}
primary={"Upgrade to ntfy Pro"}
secondary={"Reserve topics, more messages & emails, bigger attachments"}
primaryTypographyProps={{
style: {
fontWeight: 500,
background: "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent"
}
}}
/>
</ListItemButton>
</Box>
}
</List>
<SubscribeDialog
key={`subscribeDialog${subscribeDialogKey}`} // Resets dialog when canceled/closed

View file

@ -304,7 +304,7 @@ const UserTable = (props) => {
aria-label={t("prefs_users_table_user_header")}>{user.username}</TableCell>
<TableCell aria-label={t("prefs_users_table_base_url_header")}>{user.baseUrl}</TableCell>
<TableCell align="right">
{(!session.exists() || user.baseUrl !== config.baseUrl) &&
{(!session.exists() || user.baseUrl !== config.base_url) &&
<>
<IconButton onClick={() => handleEditClick(user)} aria-label={t("prefs_users_edit_button")}>
<EditIcon/>
@ -314,7 +314,7 @@ const UserTable = (props) => {
</IconButton>
</>
}
{session.exists() && user.baseUrl === config.baseUrl &&
{session.exists() && user.baseUrl === config.base_url &&
<Tooltip title={t("prefs_users_table_cannot_delete_or_edit")}>
<span>
<IconButton disabled><EditIcon/></IconButton>
@ -525,6 +525,9 @@ const Reservations = () => {
{limitReached &&
<Alert severity="info">
You reached your reserved topics limit.
{config.enable_payments &&
<>{" "}<b>Upgrade</b></>
}
</Alert>
}
</CardContent>

View file

@ -43,7 +43,7 @@ const Signup = () => {
}
}
};
if (!config.enableSignup) {
if (!config.enable_signup) {
return (
<AvatarBox>
<Typography sx={{ typography: 'h6' }}>{t("signup_disabled")}</Typography>
@ -114,7 +114,7 @@ const Signup = () => {
</Box>
}
</Box>
{config.enableLogin &&
{config.enable_login &&
<Typography sx={{mb: 4}}>
<NavLink to={routes.login} variant="body1">
{t("signup_already_have_account")}

View file

@ -18,13 +18,8 @@ import {useTranslation} from "react-i18next";
import session from "../app/Session";
import routes from "./routes";
import accountApi, {TopicReservedError, UnauthorizedError} from "../app/AccountApi";
import PublicIcon from '@mui/icons-material/Public';
import LockIcon from '@mui/icons-material/Lock';
import PublicOffIcon from '@mui/icons-material/PublicOff';
import MenuItem from "@mui/material/MenuItem";
import PopupMenu from "./PopupMenu";
import ListItemIcon from "@mui/material/ListItemIcon";
import ReserveTopicSelect from "./ReserveTopicSelect";
import {useOutletContext} from "react-router-dom";
const publicBaseUrl = "https://ntfy.sh";
@ -36,7 +31,7 @@ const SubscribeDialog = (props) => {
const handleSuccess = async () => {
console.log(`[SubscribeDialog] Subscribing to topic ${topic}`);
const actualBaseUrl = (baseUrl) ? baseUrl : config.baseUrl;
const actualBaseUrl = (baseUrl) ? baseUrl : config.base_url;
const subscription = await subscriptionManager.add(actualBaseUrl, topic);
if (session.exists()) {
try {
@ -81,17 +76,18 @@ const SubscribeDialog = (props) => {
const SubscribePage = (props) => {
const { t } = useTranslation();
const { account } = useOutletContext();
const [reserveTopicVisible, setReserveTopicVisible] = useState(false);
const [anotherServerVisible, setAnotherServerVisible] = useState(false);
const [errorText, setErrorText] = useState("");
const [accessAnchorEl, setAccessAnchorEl] = useState(null);
const [everyone, setEveryone] = useState("deny-all");
const baseUrl = (anotherServerVisible) ? props.baseUrl : config.baseUrl;
const baseUrl = (anotherServerVisible) ? props.baseUrl : config.base_url;
const topic = props.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.baseUrl);
.filter(s => s !== config.base_url);
const reserveTopicEnabled = session.exists() && (account?.stats.topics_remaining || 0) > 0;
const handleSubscribe = async () => {
const user = await userManager.get(baseUrl); // May be undefined
@ -111,7 +107,7 @@ const SubscribePage = (props) => {
}
// Reserve topic (if requested)
if (session.exists() && baseUrl === config.baseUrl && reserveTopicVisible) {
if (session.exists() && baseUrl === config.base_url && reserveTopicVisible) {
console.log(`[SubscribeDialog] Reserving topic ${topic} with everyone access ${everyone}`);
try {
await accountApi.upsertAccess(topic, everyone);
@ -141,7 +137,7 @@ const SubscribePage = (props) => {
const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic));
return validTopic(topic) && validUrl(baseUrl) && !isExistingTopicUrl;
} else {
const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(config.baseUrl, topic));
const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(config.base_url, topic));
return validTopic(topic) && !isExistingTopicUrl;
}
})();
@ -180,30 +176,6 @@ const SubscribePage = (props) => {
<Button onClick={() => {props.setTopic(randomAlphanumericString(16))}} style={{flexShrink: "0", marginTop: "0.5em"}}>
{t("subscribe_dialog_subscribe_button_generate_topic_name")}
</Button>
<PopupMenu
anchorEl={accessAnchorEl}
open={!!accessAnchorEl}
onClose={() => setAccessAnchorEl(null)}
>
<MenuItem onClick={() => setEveryone("private")} selected={everyone === "private"}>
<ListItemIcon>
<LockIcon fontSize="small" />
</ListItemIcon>
Only I can publish and subscribe
</MenuItem>
<MenuItem onClick={() => setEveryone("public-read")} selected={everyone === "public-read"}>
<ListItemIcon>
<PublicOffIcon fontSize="small" />
</ListItemIcon>
I can publish, everyone can subscribe
</MenuItem>
<MenuItem onClick={() => setEveryone("public")} selected={everyone === "public"}>
<ListItemIcon>
<PublicIcon fontSize="small" />
</ListItemIcon>
Everyone can publish and subscribe
</MenuItem>
</PopupMenu>
</div>
{session.exists() && !anotherServerVisible &&
<FormGroup>
@ -212,6 +184,7 @@ const SubscribePage = (props) => {
control={
<Checkbox
fullWidth
disabled={account.stats.topics_remaining}
checked={reserveTopicVisible}
onChange={(ev) => setReserveTopicVisible(ev.target.checked)}
inputProps={{
@ -249,7 +222,7 @@ const SubscribePage = (props) => {
renderInput={(params) =>
<TextField
{...params}
placeholder={config.baseUrl}
placeholder={config.base_url}
variant="standard"
aria-label={t("subscribe_dialog_subscribe_base_url_label")}
/>
@ -271,7 +244,7 @@ const LoginPage = (props) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [errorText, setErrorText] = useState("");
const baseUrl = (props.baseUrl) ? props.baseUrl : config.baseUrl;
const baseUrl = (props.baseUrl) ? props.baseUrl : config.base_url;
const topic = props.topic;
const handleLogin = async () => {
const user = {baseUrl, username, password};

View file

@ -60,7 +60,7 @@ export const useAutoSubscribe = (subscriptions, selected) => {
setHasRun(true);
const eligible = params.topic && !selected && !disallowedTopic(params.topic);
if (eligible) {
const baseUrl = (params.baseUrl) ? expandSecureUrl(params.baseUrl) : config.baseUrl;
const baseUrl = (params.baseUrl) ? expandSecureUrl(params.baseUrl) : config.base_url;
console.log(`[App] Auto-subscribing to ${topicUrl(baseUrl, params.topic)}`);
(async () => {
const subscription = await subscriptionManager.add(baseUrl, params.topic);

View file

@ -9,13 +9,13 @@ const routes = {
login: "/login",
signup: "/signup",
resetPassword: "/reset-password",
app: config.appRoot,
app: config.app_root,
account: "/account",
settings: "/settings",
subscription: "/:topic",
subscriptionExternal: "/:baseUrl/:topic",
forSubscription: (subscription) => {
if (subscription.baseUrl !== config.baseUrl) {
if (subscription.baseUrl !== config.base_url) {
return `/${shortUrl(subscription.baseUrl)}/${subscription.topic}`;
}
return `/${subscription.topic}`;