mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-12-22 17:52:30 +01:00
useContext work in JS
This commit is contained in:
parent
a4529617cc
commit
b27c608508
17 changed files with 87 additions and 176 deletions
|
@ -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
4
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
< Return to sign in
|
||||
</NavLink>
|
||||
</Typography>
|
||||
</AvatarBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResetPassword;
|
|
@ -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={{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue