useContext work in JS

This commit is contained in:
binwiederhier 2023-01-09 20:37:13 -05:00
parent a4529617cc
commit b27c608508
17 changed files with 87 additions and 176 deletions

View file

@ -79,7 +79,7 @@ var flagsServe = append(
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-signup", Aliases: []string{"enable_signup"}, EnvVars: []string{"NTFY_ENABLE_SIGNUP"}, Value: false, Usage: "xxx"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-login", Aliases: []string{"enable_login"}, EnvVars: []string{"NTFY_ENABLE_LOGIN"}, Value: false, Usage: "xxx"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-payments", Aliases: []string{"enable_payments"}, EnvVars: []string{"NTFY_ENABLE_PAYMENTS"}, Value: false, Usage: "xxx"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-reserve-topics", Aliases: []string{"enable_reserve_topics"}, EnvVars: []string{"NTFY_ENABLE_RESERVE_TOPICS"}, Value: false, Usage: "xxx"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-reservations", Aliases: []string{"enable_reservations"}, EnvVars: []string{"NTFY_ENABLE_RESERVATIONS"}, Value: false, Usage: "xxx"}),
)
var cmdServe = &cli.Command{
@ -151,7 +151,7 @@ func execServe(c *cli.Context) error {
enableSignup := c.Bool("enable-signup")
enableLogin := c.Bool("enable-login")
enablePayments := c.Bool("enable-payments")
enableReserveTopics := c.Bool("enable-reserve-topics")
enableReservations := c.Bool("enable-reservations")
// Check values
if firebaseKeyFile != "" && !util.FileExists(firebaseKeyFile) {
@ -188,7 +188,7 @@ func execServe(c *cli.Context) error {
return errors.New("if upstream-base-url is set, base-url must also be set")
} else if upstreamBaseURL != "" && baseURL != "" && baseURL == upstreamBaseURL {
return errors.New("base-url and upstream-base-url cannot be identical, you'll likely want to set upstream-base-url to https://ntfy.sh, see https://ntfy.sh/docs/config/#ios-instant-notifications")
} else if authFile == "" && (enableSignup || enableLogin || enableReserveTopics || enablePayments) {
} else if authFile == "" && (enableSignup || enableLogin || enableReservations || enablePayments) {
return errors.New("cannot set enable-signup, enable-login, enable-reserve-topics, or enable-payments if auth-file is not set")
}
@ -284,7 +284,7 @@ func execServe(c *cli.Context) error {
conf.EnableSignup = enableSignup
conf.EnableLogin = enableLogin
conf.EnablePayments = enablePayments
conf.EnableReserveTopics = enableReserveTopics
conf.EnableReservations = enableReservations
conf.Version = c.App.Version
// Set up hot-reloading of config

4
go.sum
View file

@ -35,8 +35,6 @@ github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8b
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8=
github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/emersion/go-smtp v0.16.0 h1:eB9CY9527WdEZSs5sWisTmilDX7gG+Q/2IdRcmubpa8=
github.com/emersion/go-smtp v0.16.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -172,8 +170,6 @@ google.golang.org/appengine/v2 v2.0.2/go.mod h1:PkgRUWz4o1XOvbqtWTkBtCitEJ5Tp4Ho
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70=
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY=
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

View file

@ -110,7 +110,7 @@ type Config struct {
EnableEmailConfirm bool
EnablePasswordReset bool
EnablePayments bool
EnableReserveTopics bool // Allow users with role "user" to own/reserve topics
EnableReservations bool // Allow users with role "user" to own/reserve topics
Version string // injected by App
}

View file

@ -44,7 +44,6 @@ import (
UI:
- flicker of upgrade banner
- JS constants
- useContext for account
Sync:
- "account topic" sync mechanism
- "mute" setting
@ -58,9 +57,7 @@ import (
Refactor:
- rename /access -> /reservation
Later:
- Password reset
- Pricing
- change email
*/
// Server is the main server, providing the UI and API for ntfy
@ -457,10 +454,10 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
EnableSignup: s.config.EnableSignup,
EnablePasswordReset: s.config.EnablePasswordReset,
EnablePayments: s.config.EnablePayments,
EnableReserveTopics: s.config.EnableReserveTopics,
EnableReservations: s.config.EnableReservations,
DisallowedTopics: disallowedTopics,
}
b, err := json.Marshal(response)
b, err := json.MarshalIndent(response, "", " ")
if err != nil {
return err
}

View file

@ -292,6 +292,6 @@ type apiConfigResponse struct {
EnableSignup bool `json:"enable_signup"`
EnablePasswordReset bool `json:"enable_password_reset"`
EnablePayments bool `json:"enable_payments"`
EnableReserveTopics bool `json:"enable_reserve_topics"`
EnableReservations bool `json:"enable_reservations"`
DisallowedTopics []string `json:"disallowed_topics"`
}

View file

@ -12,6 +12,6 @@ var config = {
enable_signup: true,
enable_password_reset: false,
enable_payments: true,
enable_reserve_topics: true,
enable_reservations: true,
disallowed_topics: ["docs", "static", "file", "app", "account", "settings", "pricing", "signup", "login", "reset-password"]
};

View file

@ -243,6 +243,7 @@
"prefs_appearance_language_title": "Language",
"prefs_reservations_title": "Reserved topics",
"prefs_reservations_description": "You can reserve topic names for personal use here. Reserving a topic gives you ownership over the topic, and allows you to define access permissions for other users over the topic.",
"prefs_reservations_limit_reached": "You reached your reserved topics limit.",
"prefs_reservations_add_button": "Add reserved topic",
"prefs_reservations_edit_button": "Edit topic access",
"prefs_reservations_delete_button": "Reset topic access",

View file

@ -1,6 +1,6 @@
import * as React from 'react';
import {useState} from 'react';
import {LinearProgress, Link, Stack, useMediaQuery} from "@mui/material";
import {useContext, useState} from 'react';
import {LinearProgress, Stack, useMediaQuery} from "@mui/material";
import Tooltip from '@mui/material/Tooltip';
import Typography from "@mui/material/Typography";
import EditIcon from '@mui/icons-material/Edit';
@ -18,7 +18,6 @@ import TextField from "@mui/material/TextField";
import DialogActions from "@mui/material/DialogActions";
import routes from "./routes";
import IconButton from "@mui/material/IconButton";
import {useOutletContext} from "react-router-dom";
import {formatBytes} from "../app/utils";
import accountApi, {UnauthorizedError} from "../app/AccountApi";
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
@ -28,6 +27,7 @@ import i18n from "i18next";
import humanizeDuration from "humanize-duration";
import UpgradeDialog from "./UpgradeDialog";
import CelebrationIcon from "@mui/icons-material/Celebration";
import {AccountContext} from "./App";
const Account = () => {
if (!session.exists()) {
@ -62,7 +62,7 @@ const Basics = () => {
const Username = () => {
const { t } = useTranslation();
const { account } = useOutletContext();
const { account } = useContext(AccountContext);
const labelId = "prefUsername";
return (
@ -169,23 +169,12 @@ const ChangePasswordDialog = (props) => {
const Stats = () => {
const { t } = useTranslation();
const { account } = useOutletContext();
const { account } = useContext(AccountContext);
const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false);
if (!account) {
return <></>;
}
const normalize = (value, max) => Math.min(value / max * 100, 100);
const barColor = (remaining, limit) => {
if (account.role === "admin") {
return "primary";
} else if (limit > 0 && remaining === 0) {
return "error";
}
return "primary";
};
return (
<Card sx={{p: 3}} aria-label={t("account_usage_title")}>
<Typography variant="h5" sx={{marginBottom: 2}}>
@ -238,7 +227,6 @@ const Stats = () => {
<LinearProgress
variant="determinate"
value={account.limits.reservations > 0 ? normalize(account.stats.reservations, account.limits.reservations) : 100}
color={barColor(account.stats.reservations_remaining, account.limits.reservations)}
/>
</>
}
@ -260,7 +248,6 @@ const Stats = () => {
<LinearProgress
variant="determinate"
value={account.role === "user" ? normalize(account.stats.messages, account.limits.messages) : 100}
color={account.role === "user" && account.stats.messages_remaining === 0 ? 'error' : 'primary'}
/>
</Pref>
<Pref title={
@ -271,12 +258,11 @@ const Stats = () => {
}>
<div>
<Typography variant="body2" sx={{float: "left"}}>{account.stats.emails}</Typography>
<Typography variant="body2" sx={{float: "right"}}>{account.limits.emails > 0 ? t("account_usage_of_limit", { limit: account.limits.emails }) : t("account_usage_unlimited")}</Typography>
<Typography variant="body2" sx={{float: "right"}}>{account.role === "user" ? t("account_usage_of_limit", { limit: account.limits.emails }) : t("account_usage_unlimited")}</Typography>
</div>
<LinearProgress
variant="determinate"
value={account.limits.emails > 0 ? normalize(account.stats.emails, account.limits.emails) : 100}
color={account?.role !== "admin" && account.stats.emails_remaining === 0 ? 'error' : 'primary'}
value={account.role === "user" ? normalize(account.stats.emails, account.limits.emails) : 100}
/>
</Pref>
<Pref
@ -292,16 +278,15 @@ const Stats = () => {
>
<div>
<Typography variant="body2" sx={{float: "left"}}>{formatBytes(account.stats.attachment_total_size)}</Typography>
<Typography variant="body2" sx={{float: "right"}}>{account.limits.attachment_total_size > 0 ? 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 === "user" ? t("account_usage_of_limit", { limit: formatBytes(account.limits.attachment_total_size) }) : t("account_usage_unlimited")}</Typography>
</div>
<LinearProgress
variant="determinate"
value={account.limits.attachment_total_size > 0 ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100}
color={account.role !== "admin" && account.stats.attachment_total_size_remaining === 0 ? 'error' : 'primary'}
value={account.role === "user" ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100}
/>
</Pref>
</PrefGroup>
{account.limits.basis === "ip" &&
{account.role === "user" && account.limits.basis === "ip" &&
<Typography variant="body1">
{t("account_usage_basis_ip_description")}
</Typography>

View file

@ -1,10 +1,10 @@
import * as React from 'react';
import {Suspense, useEffect, useState} from 'react';
import {createContext, Suspense, useContext, useEffect, useState} from 'react';
import Box from '@mui/material/Box';
import {ThemeProvider} from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import Toolbar from '@mui/material/Toolbar';
import Notifications from "./Notifications";
import {AllSubscriptions, SingleSubscription} from "./Notifications";
import theme from "./theme";
import Navigation from "./Navigation";
import ActionBar from "./ActionBar";
@ -13,11 +13,11 @@ import Preferences from "./Preferences";
import {useLiveQuery} from "dexie-react-hooks";
import subscriptionManager from "../app/SubscriptionManager";
import userManager from "../app/UserManager";
import {BrowserRouter, Outlet, Route, Routes, useOutletContext, useParams} from "react-router-dom";
import {BrowserRouter, Outlet, Route, Routes, useParams} from "react-router-dom";
import {expandUrl} from "../app/utils";
import ErrorBoundary from "./ErrorBoundary";
import routes from "./routes";
import {useAccountListener, useAutoSubscribe, useBackgroundProcesses, useConnectionListeners} from "./hooks";
import {useAccountListener, useBackgroundProcesses, useConnectionListeners} from "./hooks";
import PublishDialog from "./PublishDialog";
import Messaging from "./Messaging";
import "./i18n"; // Translations!
@ -27,53 +27,45 @@ import Login from "./Login";
import Pricing from "./Pricing";
import Signup from "./Signup";
import Account from "./Account";
import ResetPassword from "./ResetPassword";
export const AccountContext = createContext(null);
const App = () => {
const [account, setAccount] = useState(null);
return (
<Suspense fallback={<Loader />}>
<BrowserRouter>
<ThemeProvider theme={theme}>
<CssBaseline/>
<ErrorBoundary>
<Routes>
<Route path={routes.home} element={<Home/>}/>
<Route path={routes.pricing} element={<Pricing/>}/>
<Route path={routes.login} element={<Login/>}/>
<Route path={routes.signup} element={<Signup/>}/>
<Route path={routes.resetPassword} element={<ResetPassword/>}/>
<Route element={<Layout/>}>
<Route path={routes.app} element={<AllSubscriptions/>}/>
<Route path={routes.account} element={<Account/>}/>
<Route path={routes.settings} element={<Preferences/>}/>
<Route path={routes.subscription} element={<SingleSubscription/>}/>
<Route path={routes.subscriptionExternal} element={<SingleSubscription/>}/>
</Route>
</Routes>
</ErrorBoundary>
<AccountContext.Provider value={{ account, setAccount }}>
<CssBaseline/>
<ErrorBoundary>
<Routes>
<Route path={routes.home} element={<Home/>}/>
<Route path={routes.pricing} element={<Pricing/>}/>
<Route path={routes.login} element={<Login/>}/>
<Route path={routes.signup} element={<Signup/>}/>
<Route element={<Layout/>}>
<Route path={routes.app} element={<AllSubscriptions/>}/>
<Route path={routes.account} element={<Account/>}/>
<Route path={routes.settings} element={<Preferences/>}/>
<Route path={routes.subscription} element={<SingleSubscription/>}/>
<Route path={routes.subscriptionExternal} element={<SingleSubscription/>}/>
</Route>
</Routes>
</ErrorBoundary>
</AccountContext.Provider>
</ThemeProvider>
</BrowserRouter>
</Suspense>
);
}
const AllSubscriptions = () => {
const { subscriptions } = useOutletContext();
return <Notifications mode="all" subscriptions={subscriptions}/>;
};
const SingleSubscription = () => {
const { subscriptions, selected } = useOutletContext();
useAutoSubscribe(subscriptions, selected);
return <Notifications mode="one" subscription={selected}/>;
};
const Layout = () => {
const params = useParams();
const { account, setAccount } = useContext(AccountContext);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted());
const [sendDialogOpenMode, setSendDialogOpenMode] = useState("");
const [account, setAccount] = useState(null);
const users = useLiveQuery(() => userManager.all());
const subscriptions = useLiveQuery(() => subscriptionManager.all());
const newNotificationsCount = subscriptions?.reduce((prev, cur) => prev + cur.new, 0) || 0;
@ -94,7 +86,6 @@ const Layout = () => {
onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
/>
<Navigation
account={account}
subscriptions={subscriptions}
selectedSubscription={selected}
notificationsGranted={notificationsGranted}
@ -105,7 +96,7 @@ const Layout = () => {
/>
<Main>
<Toolbar/>
<Outlet context={{ account, subscriptions, selected }}/>
<Outlet context={{ subscriptions, selected }}/>
</Main>
<Messaging
selected={selected}

View file

@ -1,6 +1,6 @@
import Drawer from "@mui/material/Drawer";
import * as React from "react";
import {useState} from "react";
import {useContext, useState} from "react";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ChatBubbleOutlineIcon from "@mui/icons-material/ChatBubbleOutline";
@ -30,6 +30,7 @@ import session from "../app/Session";
import accountApi from "../app/AccountApi";
import CelebrationIcon from '@mui/icons-material/Celebration';
import UpgradeDialog from "./UpgradeDialog";
import {AccountContext} from "./App";
const navWidth = 280;
@ -76,6 +77,7 @@ const NavList = (props) => {
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const { account } = useContext(AccountContext);
const [subscribeDialogKey, setSubscribeDialogKey] = useState(0);
const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false);
@ -100,8 +102,8 @@ const NavList = (props) => {
navigate(routes.account);
};
const isAdmin = props.account?.role === "admin";
const isPaid = props.account?.tier?.paid;
const isAdmin = account?.role === "admin";
const isPaid = account?.tier?.paid;
const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid;// && (!props.account || !props.account.tier || !props.account.tier.paid || props.account);
const showSubscriptionsList = props.subscriptions?.length > 0;
const showNotificationBrowserNotSupportedBox = !notifier.browserSupported();

View file

@ -19,7 +19,8 @@ import {
formatBytes,
formatMessage,
formatShortDateTime,
formatTitle, maybeAppendActionErrors,
formatTitle,
maybeAppendActionErrors,
openUrl,
shortUrl,
topicShortUrl,
@ -41,15 +42,27 @@ import priority5 from "../img/priority-5.svg";
import logoOutline from "../img/ntfy-outline.svg";
import AttachmentIcon from "./AttachmentIcon";
import {Trans, useTranslation} from "react-i18next";
import {useOutletContext} from "react-router-dom";
import {useAutoSubscribe} from "./hooks";
const Notifications = (props) => {
if (props.mode === "all") {
return (props.subscriptions) ? <AllSubscriptions subscriptions={props.subscriptions}/> : <Loading/>;
export const AllSubscriptions = () => {
const { subscriptions } = useOutletContext();
if (!subscriptions) {
return <Loading/>;
}
return (props.subscription) ? <SingleSubscription subscription={props.subscription}/> : <Loading/>;
}
return <AllSubscriptionsList subscriptions={subscriptions}/>;
};
const AllSubscriptions = (props) => {
export const SingleSubscription = () => {
const { subscriptions, selected } = useOutletContext();
useAutoSubscribe(subscriptions, selected);
if (!selected) {
return <Loading/>;
}
return <SingleSubscriptionList subscription={selected}/>;
};
const AllSubscriptionsList = (props) => {
const subscriptions = props.subscriptions;
const notifications = useLiveQuery(() => subscriptionManager.getAllNotifications(), []);
if (notifications === null || notifications === undefined) {
@ -62,7 +75,7 @@ const AllSubscriptions = (props) => {
return <NotificationList key="all" notifications={notifications} messageBar={false}/>;
}
const SingleSubscription = (props) => {
const SingleSubscriptionList = (props) => {
const subscription = props.subscription;
const notifications = useLiveQuery(() => subscriptionManager.getNotifications(subscription.id), [subscription]);
if (notifications === null || notifications === undefined) {
@ -533,5 +546,3 @@ const Loading = () => {
</VerticallyCenteredContainer>
);
};
export default Notifications;

View file

@ -1,5 +1,5 @@
import * as React from 'react';
import {useEffect, useState} from 'react';
import {useContext, useEffect, useState} from 'react';
import {
Alert,
CardActions,
@ -40,13 +40,11 @@ import session from "../app/Session";
import routes from "./routes";
import accountApi, {UnauthorizedError} from "../app/AccountApi";
import {Pref, PrefGroup} from "./Pref";
import {useOutletContext} from "react-router-dom";
import LockIcon from "@mui/icons-material/Lock";
import {Public, PublicOff} from "@mui/icons-material";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import DialogContentText from "@mui/material/DialogContentText";
import ReserveTopicSelect from "./ReserveTopicSelect";
import {AccountContext} from "./App";
const Preferences = () => {
return (
@ -481,11 +479,11 @@ const Language = () => {
const Reservations = () => {
const { t } = useTranslation();
const { account } = useOutletContext();
const { account } = useContext(AccountContext);
const [dialogKey, setDialogKey] = useState(0);
const [dialogOpen, setDialogOpen] = useState(false);
if (!config.enable_reserve_topics || !session.exists() || !account || account.role === "admin") {
if (!config.enable_reservations || !session.exists() || !account || account.role === "admin") {
return <></>;
}
const reservations = account.reservations || [];
@ -522,14 +520,7 @@ const Reservations = () => {
{t("prefs_reservations_description")}
</Paragraph>
{reservations.length > 0 && <ReservationsTable reservations={reservations}/>}
{limitReached &&
<Alert severity="info">
You reached your reserved topics limit.
{config.enable_payments &&
<>{" "}<b>Upgrade</b></>
}
</Alert>
}
{limitReached && <Alert severity="info">{t("prefs_reservations_limit_reached")}</Alert>}
</CardContent>
<CardActions>
<Button onClick={handleAddClick} disabled={limitReached}>{t("prefs_reservations_add_button")}</Button>

View file

@ -1,48 +0,0 @@
import * as React from 'react';
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
import routes from "./routes";
import Typography from "@mui/material/Typography";
import {NavLink} from "react-router-dom";
import AvatarBox from "./AvatarBox";
const ResetPassword = () => {
const handleSubmit = async (event) => {
//
};
return (
<AvatarBox>
<Typography sx={{ typography: 'h6' }}>
Reset password
</Typography>
<Box component="form" onSubmit={handleSubmit} noValidate sx={{mt: 1, maxWidth: 400}}>
<TextField
margin="dense"
required
fullWidth
id="email"
label="Email"
name="email"
autoFocus
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{mt: 2, mb: 2}}
>
Reset password
</Button>
</Box>
<Typography sx={{mb: 4}}>
<NavLink to={routes.login} variant="body1">
&lt; Return to sign in
</NavLink>
</Typography>
</AvatarBox>
);
}
export default ResetPassword;

View file

@ -1,5 +1,5 @@
import * as React from 'react';
import {useState} from 'react';
import {useContext, useState} from 'react';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import Dialog from '@mui/material/Dialog';
@ -19,7 +19,7 @@ import session from "../app/Session";
import routes from "./routes";
import accountApi, {TopicReservedError, UnauthorizedError} from "../app/AccountApi";
import ReserveTopicSelect from "./ReserveTopicSelect";
import {useOutletContext} from "react-router-dom";
import {AccountContext} from "./App";
const publicBaseUrl = "https://ntfy.sh";
@ -76,7 +76,7 @@ const SubscribeDialog = (props) => {
const SubscribePage = (props) => {
const { t } = useTranslation();
//const { account } = useOutletContext();
const { account } = useContext(AccountContext);
const [reserveTopicVisible, setReserveTopicVisible] = useState(false);
const [anotherServerVisible, setAnotherServerVisible] = useState(false);
const [errorText, setErrorText] = useState("");
@ -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?.stats.reservations_remaining || 0) > 0;
const reserveTopicEnabled = session.exists() && account?.role === "user" && (account?.stats.reservations_remaining || 0) > 0;
const handleSubscribe = async () => {
const user = await userManager.get(baseUrl); // May be undefined
@ -177,14 +177,14 @@ const SubscribePage = (props) => {
{t("subscribe_dialog_subscribe_button_generate_topic_name")}
</Button>
</div>
{config.enable_reserve_topics && session.exists() && !anotherServerVisible &&
{config.enable_reservations && session.exists() && !anotherServerVisible &&
<FormGroup>
<FormControlLabel
variant="standard"
control={
<Checkbox
fullWidth
// disabled={account.stats.reservations_remaining}
disabled={!reserveTopicEnabled}
checked={reserveTopicVisible}
onChange={(ev) => setReserveTopicVisible(ev.target.checked)}
inputProps={{

View file

@ -78,7 +78,7 @@ const SubscriptionSettingsDialog = (props) => {
"aria-label": t("subscription_settings_dialog_display_name_placeholder")
}}
/>
{config.enable_reserve_topics && session.exists() &&
{config.enable_reservations && session.exists() &&
<>
<FormControlLabel
fullWidth

View file

@ -1,25 +1,10 @@
import * as React from 'react';
import {useState} from 'react';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import {Autocomplete, Checkbox, FormControlLabel, FormGroup, useMediaQuery} from "@mui/material";
import {useMediaQuery} from "@mui/material";
import theme from "./theme";
import api from "../app/Api";
import {randomAlphanumericString, topicUrl, validTopic, validUrl} from "../app/utils";
import userManager from "../app/UserManager";
import subscriptionManager from "../app/SubscriptionManager";
import poller from "../app/Poller";
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 ReserveTopicSelect from "./ReserveTopicSelect";
import {useOutletContext} from "react-router-dom";
const UpgradeDialog = (props) => {
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));

View file

@ -8,7 +8,7 @@ const routes = {
pricing: "/pricing",
login: "/login",
signup: "/signup",
resetPassword: "/reset-password",
resetPassword: "/reset-password", // Not used (yet)
app: config.app_root,
account: "/account",
settings: "/settings",