Run eslint autofixes
This commit is contained in:
		
							parent
							
								
									f558b4dbe9
								
							
						
					
					
						commit
						8319f1cf26
					
				
					 32 changed files with 394 additions and 435 deletions
				
			
		|  | @ -29,34 +29,34 @@ import Container from "@mui/material/Container"; | |||
| import Card from "@mui/material/Card"; | ||||
| import Button from "@mui/material/Button"; | ||||
| import { Trans, useTranslation } from "react-i18next"; | ||||
| import session from "../app/Session"; | ||||
| import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; | ||||
| import theme from "./theme"; | ||||
| import Dialog from "@mui/material/Dialog"; | ||||
| import DialogTitle from "@mui/material/DialogTitle"; | ||||
| import DialogContent from "@mui/material/DialogContent"; | ||||
| import TextField from "@mui/material/TextField"; | ||||
| import routes from "./routes"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import { formatBytes, formatShortDate, formatShortDateTime, openUrl } from "../app/utils"; | ||||
| import accountApi, { LimitBasis, Role, SubscriptionInterval, SubscriptionStatus } from "../app/AccountApi"; | ||||
| import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; | ||||
| import { Pref, PrefGroup } from "./Pref"; | ||||
| import db from "../app/db"; | ||||
| import i18n from "i18next"; | ||||
| import humanizeDuration from "humanize-duration"; | ||||
| import UpgradeDialog from "./UpgradeDialog"; | ||||
| import CelebrationIcon from "@mui/icons-material/Celebration"; | ||||
| import { AccountContext } from "./App"; | ||||
| import DialogFooter from "./DialogFooter"; | ||||
| import { Paragraph } from "./styles"; | ||||
| import CloseIcon from "@mui/icons-material/Close"; | ||||
| import { ContentCopy, Public } from "@mui/icons-material"; | ||||
| import MenuItem from "@mui/material/MenuItem"; | ||||
| import DialogContentText from "@mui/material/DialogContentText"; | ||||
| import AddIcon from "@mui/icons-material/Add"; | ||||
| import routes from "./routes"; | ||||
| import { formatBytes, formatShortDate, formatShortDateTime, openUrl } from "../app/utils"; | ||||
| import accountApi, { LimitBasis, Role, SubscriptionInterval, SubscriptionStatus } from "../app/AccountApi"; | ||||
| import { Pref, PrefGroup } from "./Pref"; | ||||
| import db from "../app/db"; | ||||
| import UpgradeDialog from "./UpgradeDialog"; | ||||
| import { AccountContext } from "./App"; | ||||
| import DialogFooter from "./DialogFooter"; | ||||
| import { Paragraph } from "./styles"; | ||||
| import { IncorrectPasswordError, UnauthorizedError } from "../app/errors"; | ||||
| import { ProChip } from "./SubscriptionPopup"; | ||||
| import AddIcon from "@mui/icons-material/Add"; | ||||
| import theme from "./theme"; | ||||
| import session from "../app/Session"; | ||||
| 
 | ||||
| const Account = () => { | ||||
|   if (!session.exists()) { | ||||
|  | @ -561,9 +561,7 @@ const Stats = () => { | |||
|     return <></>; | ||||
|   } | ||||
| 
 | ||||
|   const normalize = (value, max) => { | ||||
|     return Math.min((value / max) * 100, 100); | ||||
|   }; | ||||
|   const normalize = (value, max) => Math.min((value / max) * 100, 100); | ||||
| 
 | ||||
|   return ( | ||||
|     <Card sx={{ p: 3 }} aria-label={t("account_usage_title")}> | ||||
|  | @ -746,18 +744,16 @@ const Stats = () => { | |||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const InfoIcon = () => { | ||||
|   return ( | ||||
|     <InfoOutlinedIcon | ||||
|       sx={{ | ||||
|         verticalAlign: "middle", | ||||
|         width: "18px", | ||||
|         marginLeft: "4px", | ||||
|         color: "gray", | ||||
|       }} | ||||
|     /> | ||||
|   ); | ||||
| }; | ||||
| const InfoIcon = () => ( | ||||
|   <InfoOutlinedIcon | ||||
|     sx={{ | ||||
|       verticalAlign: "middle", | ||||
|       width: "18px", | ||||
|       marginLeft: "4px", | ||||
|       color: "gray", | ||||
|     }} | ||||
|   /> | ||||
| ); | ||||
| 
 | ||||
| const Tokens = () => { | ||||
|   const { t } = useTranslation(); | ||||
|  | @ -814,7 +810,8 @@ const TokensTable = (props) => { | |||
|   const tokens = (props.tokens || []).sort((a, b) => { | ||||
|     if (a.token === session.token()) { | ||||
|       return -1; | ||||
|     } else if (b.token === session.token()) { | ||||
|     } | ||||
|     if (b.token === session.token()) { | ||||
|       return 1; | ||||
|     } | ||||
|     return a.token.localeCompare(b.token); | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import AppBar from "@mui/material/AppBar"; | ||||
| import Navigation from "./Navigation"; | ||||
| import Toolbar from "@mui/material/Toolbar"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import MenuIcon from "@mui/icons-material/Menu"; | ||||
|  | @ -7,23 +6,24 @@ import Typography from "@mui/material/Typography"; | |||
| import * as React from "react"; | ||||
| import { useState } from "react"; | ||||
| import Box from "@mui/material/Box"; | ||||
| import { topicDisplayName } from "../app/utils"; | ||||
| import db from "../app/db"; | ||||
| import { useLocation, useNavigate } from "react-router-dom"; | ||||
| import MenuItem from "@mui/material/MenuItem"; | ||||
| import MoreVertIcon from "@mui/icons-material/MoreVert"; | ||||
| import NotificationsIcon from "@mui/icons-material/Notifications"; | ||||
| import NotificationsOffIcon from "@mui/icons-material/NotificationsOff"; | ||||
| import routes from "./routes"; | ||||
| import subscriptionManager from "../app/SubscriptionManager"; | ||||
| import logo from "../img/ntfy.svg"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import session from "../app/Session"; | ||||
| import AccountCircleIcon from "@mui/icons-material/AccountCircle"; | ||||
| import Button from "@mui/material/Button"; | ||||
| import Divider from "@mui/material/Divider"; | ||||
| import { Logout, Person, Settings } from "@mui/icons-material"; | ||||
| import ListItemIcon from "@mui/material/ListItemIcon"; | ||||
| import session from "../app/Session"; | ||||
| import logo from "../img/ntfy.svg"; | ||||
| import subscriptionManager from "../app/SubscriptionManager"; | ||||
| import routes from "./routes"; | ||||
| import db from "../app/db"; | ||||
| import { topicDisplayName } from "../app/utils"; | ||||
| import Navigation from "./Navigation"; | ||||
| import accountApi from "../app/AccountApi"; | ||||
| import PopupMenu from "./PopupMenu"; | ||||
| import { SubscriptionPopup } from "./SubscriptionPopup"; | ||||
|  | @ -86,7 +86,7 @@ const ActionBar = (props) => { | |||
| const SettingsIcons = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const [anchorEl, setAnchorEl] = useState(null); | ||||
|   const subscription = props.subscription; | ||||
|   const { subscription } = props; | ||||
| 
 | ||||
|   const handleToggleMute = async () => { | ||||
|     const mutedUntil = subscription.mutedUntil ? 0 : 1; // Make this a timestamp in the future | ||||
|  |  | |||
|  | @ -4,16 +4,17 @@ 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 { useLiveQuery } from "dexie-react-hooks"; | ||||
| import { BrowserRouter, Outlet, Route, Routes, useParams } from "react-router-dom"; | ||||
| import { Backdrop, CircularProgress } from "@mui/material"; | ||||
| import { AllSubscriptions, SingleSubscription } from "./Notifications"; | ||||
| import theme from "./theme"; | ||||
| import Navigation from "./Navigation"; | ||||
| import ActionBar from "./ActionBar"; | ||||
| import notifier from "../app/Notifier"; | ||||
| 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, useParams } from "react-router-dom"; | ||||
| import { expandUrl } from "../app/utils"; | ||||
| import ErrorBoundary from "./ErrorBoundary"; | ||||
| import routes from "./routes"; | ||||
|  | @ -21,7 +22,6 @@ import { useAccountListener, useBackgroundProcesses, useConnectionListeners } fr | |||
| import PublishDialog from "./PublishDialog"; | ||||
| import Messaging from "./Messaging"; | ||||
| import "./i18n"; // Translations! | ||||
| import { Backdrop, CircularProgress } from "@mui/material"; | ||||
| import Login from "./Login"; | ||||
| import Signup from "./Signup"; | ||||
| import Account from "./Account"; | ||||
|  | @ -66,12 +66,11 @@ const Layout = () => { | |||
|   const subscriptions = useLiveQuery(() => subscriptionManager.all()); | ||||
|   const subscriptionsWithoutInternal = subscriptions?.filter((s) => !s.internal); | ||||
|   const newNotificationsCount = subscriptionsWithoutInternal?.reduce((prev, cur) => prev + cur.new, 0) || 0; | ||||
|   const [selected] = (subscriptionsWithoutInternal || []).filter((s) => { | ||||
|     return ( | ||||
|   const [selected] = (subscriptionsWithoutInternal || []).filter( | ||||
|     (s) => | ||||
|       (params.baseUrl && expandUrl(params.baseUrl).includes(s.baseUrl) && params.topic === s.topic) || | ||||
|       (config.base_url === s.baseUrl && params.topic === s.topic) | ||||
|     ); | ||||
|   }); | ||||
|   ); | ||||
| 
 | ||||
|   useConnectionListeners(account, subscriptions, users); | ||||
|   useAccountListener(setAccount); | ||||
|  | @ -95,7 +94,7 @@ const Layout = () => { | |||
|         <Outlet | ||||
|           context={{ | ||||
|             subscriptions: subscriptionsWithoutInternal, | ||||
|             selected: selected, | ||||
|             selected, | ||||
|           }} | ||||
|         /> | ||||
|       </Main> | ||||
|  | @ -104,30 +103,28 @@ const Layout = () => { | |||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const Main = (props) => { | ||||
|   return ( | ||||
|     <Box | ||||
|       id="main" | ||||
|       component="main" | ||||
|       sx={{ | ||||
|         display: "flex", | ||||
|         flexGrow: 1, | ||||
|         flexDirection: "column", | ||||
|         padding: 3, | ||||
|         width: { sm: `calc(100% - ${Navigation.width}px)` }, | ||||
|         height: "100vh", | ||||
|         overflow: "auto", | ||||
|         backgroundColor: (theme) => (theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]), | ||||
|       }} | ||||
|     > | ||||
|       {props.children} | ||||
|     </Box> | ||||
|   ); | ||||
| }; | ||||
| const Main = (props) => ( | ||||
|   <Box | ||||
|     id="main" | ||||
|     component="main" | ||||
|     sx={{ | ||||
|       display: "flex", | ||||
|       flexGrow: 1, | ||||
|       flexDirection: "column", | ||||
|       padding: 3, | ||||
|       width: { sm: `calc(100% - ${Navigation.width}px)` }, | ||||
|       height: "100vh", | ||||
|       overflow: "auto", | ||||
|       backgroundColor: (theme) => (theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]), | ||||
|     }} | ||||
|   > | ||||
|     {props.children} | ||||
|   </Box> | ||||
| ); | ||||
| 
 | ||||
| const Loader = () => ( | ||||
|   <Backdrop | ||||
|     open={true} | ||||
|     open | ||||
|     sx={{ | ||||
|       zIndex: 100000, | ||||
|       backgroundColor: (theme) => (theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]), | ||||
|  |  | |||
|  | @ -1,16 +1,17 @@ | |||
| import * as React from "react"; | ||||
| import Box from "@mui/material/Box"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import fileDocument from "../img/file-document.svg"; | ||||
| import fileImage from "../img/file-image.svg"; | ||||
| import fileVideo from "../img/file-video.svg"; | ||||
| import fileAudio from "../img/file-audio.svg"; | ||||
| import fileApp from "../img/file-app.svg"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| 
 | ||||
| const AttachmentIcon = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const type = props.type; | ||||
|   let imageFile, imageLabel; | ||||
|   const { type } = props; | ||||
|   let imageFile; | ||||
|   let imageLabel; | ||||
|   if (!type) { | ||||
|     imageFile = fileDocument; | ||||
|     imageLabel = t("notifications_attachment_file_image"); | ||||
|  |  | |||
|  | @ -3,23 +3,21 @@ import { Avatar } from "@mui/material"; | |||
| import Box from "@mui/material/Box"; | ||||
| import logo from "../img/ntfy-filled.svg"; | ||||
| 
 | ||||
| const AvatarBox = (props) => { | ||||
|   return ( | ||||
|     <Box | ||||
|       sx={{ | ||||
|         display: "flex", | ||||
|         flexGrow: 1, | ||||
|         justifyContent: "center", | ||||
|         flexDirection: "column", | ||||
|         alignContent: "center", | ||||
|         alignItems: "center", | ||||
|         height: "100vh", | ||||
|       }} | ||||
|     > | ||||
|       <Avatar sx={{ m: 2, width: 64, height: 64, borderRadius: 3 }} src={logo} variant="rounded" /> | ||||
|       {props.children} | ||||
|     </Box> | ||||
|   ); | ||||
| }; | ||||
| const AvatarBox = (props) => ( | ||||
|   <Box | ||||
|     sx={{ | ||||
|       display: "flex", | ||||
|       flexGrow: 1, | ||||
|       justifyContent: "center", | ||||
|       flexDirection: "column", | ||||
|       alignContent: "center", | ||||
|       alignItems: "center", | ||||
|       height: "100vh", | ||||
|     }} | ||||
|   > | ||||
|     <Avatar sx={{ m: 2, width: 64, height: 64, borderRadius: 3 }} src={logo} variant="rounded" /> | ||||
|     {props.children} | ||||
|   </Box> | ||||
| ); | ||||
| 
 | ||||
| export default AvatarBox; | ||||
|  |  | |||
|  | @ -3,31 +3,29 @@ import Box from "@mui/material/Box"; | |||
| import DialogContentText from "@mui/material/DialogContentText"; | ||||
| import DialogActions from "@mui/material/DialogActions"; | ||||
| 
 | ||||
| const DialogFooter = (props) => { | ||||
|   return ( | ||||
|     <Box | ||||
| const DialogFooter = (props) => ( | ||||
|   <Box | ||||
|     sx={{ | ||||
|       display: "flex", | ||||
|       flexDirection: "row", | ||||
|       justifyContent: "space-between", | ||||
|       paddingLeft: "24px", | ||||
|       paddingBottom: "8px", | ||||
|     }} | ||||
|   > | ||||
|     <DialogContentText | ||||
|       component="div" | ||||
|       aria-live="polite" | ||||
|       sx={{ | ||||
|         display: "flex", | ||||
|         flexDirection: "row", | ||||
|         justifyContent: "space-between", | ||||
|         paddingLeft: "24px", | ||||
|         paddingBottom: "8px", | ||||
|         margin: "0px", | ||||
|         paddingTop: "12px", | ||||
|         paddingBottom: "4px", | ||||
|       }} | ||||
|     > | ||||
|       <DialogContentText | ||||
|         component="div" | ||||
|         aria-live="polite" | ||||
|         sx={{ | ||||
|           margin: "0px", | ||||
|           paddingTop: "12px", | ||||
|           paddingBottom: "4px", | ||||
|         }} | ||||
|       > | ||||
|         {props.status} | ||||
|       </DialogContentText> | ||||
|       <DialogActions sx={{ paddingRight: 2 }}>{props.children}</DialogActions> | ||||
|     </Box> | ||||
|   ); | ||||
| }; | ||||
|       {props.status} | ||||
|     </DialogContentText> | ||||
|     <DialogActions sx={{ paddingRight: 2 }}>{props.children}</DialogActions> | ||||
|   </Box> | ||||
| ); | ||||
| 
 | ||||
| export default DialogFooter; | ||||
|  |  | |||
|  | @ -1,15 +1,15 @@ | |||
| import * as React from "react"; | ||||
| import { useRef, useState } from "react"; | ||||
| import Typography from "@mui/material/Typography"; | ||||
| import { rawEmojis } from "../app/emojis"; | ||||
| import Box from "@mui/material/Box"; | ||||
| import TextField from "@mui/material/TextField"; | ||||
| import { ClickAwayListener, Fade, InputAdornment, styled } from "@mui/material"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import { Close } from "@mui/icons-material"; | ||||
| import Popper from "@mui/material/Popper"; | ||||
| import { splitNoEmpty } from "../app/utils"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import { splitNoEmpty } from "../app/utils"; | ||||
| import { rawEmojis } from "../app/emojis"; | ||||
| 
 | ||||
| // Create emoji list by category and create a search base (string with all search words) | ||||
| // | ||||
|  | @ -28,7 +28,7 @@ rawEmojis.forEach((emoji) => { | |||
|     const supportedEmoji = unicodeVersion <= maxSupportedVersionForDesktopChrome || !isDesktopChrome; | ||||
|     if (supportedEmoji) { | ||||
|       const searchBase = `${emoji.description.toLowerCase()} ${emoji.aliases.join(" ")} ${emoji.tags.join(" ")}`; | ||||
|       const emojiWithSearchBase = { ...emoji, searchBase: searchBase }; | ||||
|       const emojiWithSearchBase = { ...emoji, searchBase }; | ||||
|       emojisByCategory[emoji.category].push(emojiWithSearchBase); | ||||
|     } | ||||
|   } catch (e) { | ||||
|  | @ -133,7 +133,7 @@ const Category = (props) => { | |||
| }; | ||||
| 
 | ||||
| const Emoji = (props) => { | ||||
|   const emoji = props.emoji; | ||||
|   const { emoji } = props; | ||||
|   const matches = emojiMatches(emoji, props.search); | ||||
|   const title = `${emoji.description} (${emoji.aliases[0]})`; | ||||
|   return ( | ||||
|  |  | |||
|  | @ -46,9 +46,9 @@ class ErrorBoundaryImpl extends React.Component { | |||
|     // Fetch additional info and a better stack trace | ||||
|     StackTrace.fromError(error).then((stack) => { | ||||
|       console.error("[ErrorBoundary] Stacktrace fetched", stack); | ||||
|       const niceStack = | ||||
|         `${error.toString()}\n` + | ||||
|         stack.map((el) => `  at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})`).join("\n"); | ||||
|       const niceStack = `${error.toString()}\n${stack | ||||
|         .map((el) => `  at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})`) | ||||
|         .join("\n")}`; | ||||
|       this.setState({ niceStack }); | ||||
|     }); | ||||
|   } | ||||
|  | @ -73,9 +73,8 @@ class ErrorBoundaryImpl extends React.Component { | |||
|     if (this.state.error) { | ||||
|       if (this.state.unsupportedIndexedDB) { | ||||
|         return this.renderUnsupportedIndexedDB(); | ||||
|       } else { | ||||
|         return this.renderError(); | ||||
|       } | ||||
|       return this.renderError(); | ||||
|     } | ||||
|     return this.props.children; | ||||
|   } | ||||
|  |  | |||
|  | @ -5,15 +5,15 @@ import WarningAmberIcon from "@mui/icons-material/WarningAmber"; | |||
| import TextField from "@mui/material/TextField"; | ||||
| import Button from "@mui/material/Button"; | ||||
| import Box from "@mui/material/Box"; | ||||
| import routes from "./routes"; | ||||
| import session from "../app/Session"; | ||||
| import { NavLink } from "react-router-dom"; | ||||
| import AvatarBox from "./AvatarBox"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import accountApi from "../app/AccountApi"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import { InputAdornment } from "@mui/material"; | ||||
| import { Visibility, VisibilityOff } from "@mui/icons-material"; | ||||
| import accountApi from "../app/AccountApi"; | ||||
| import AvatarBox from "./AvatarBox"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| import { UnauthorizedError } from "../app/errors"; | ||||
| 
 | ||||
| const Login = () => { | ||||
|  |  | |||
|  | @ -1,21 +1,21 @@ | |||
| import * as React from "react"; | ||||
| import { useState } from "react"; | ||||
| import Navigation from "./Navigation"; | ||||
| import Paper from "@mui/material/Paper"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import TextField from "@mui/material/TextField"; | ||||
| import SendIcon from "@mui/icons-material/Send"; | ||||
| import api from "../app/Api"; | ||||
| import PublishDialog from "./PublishDialog"; | ||||
| import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | ||||
| import { Portal, Snackbar } from "@mui/material"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import PublishDialog from "./PublishDialog"; | ||||
| import api from "../app/Api"; | ||||
| import Navigation from "./Navigation"; | ||||
| 
 | ||||
| const Messaging = (props) => { | ||||
|   const [message, setMessage] = useState(""); | ||||
|   const [dialogKey, setDialogKey] = useState(0); | ||||
| 
 | ||||
|   const dialogOpenMode = props.dialogOpenMode; | ||||
|   const { dialogOpenMode } = props; | ||||
|   const subscription = props.selected; | ||||
| 
 | ||||
|   const handleOpenDialogClick = () => { | ||||
|  | @ -39,7 +39,7 @@ const Messaging = (props) => { | |||
|         topic={subscription?.topic ?? ""} | ||||
|         message={message} | ||||
|         onClose={handleDialogClose} | ||||
|         onDragEnter={() => props.onDialogOpenModeChange((prev) => (prev ? prev : PublishDialog.OPEN_MODE_DRAG))} // Only update if not already open | ||||
|         onDragEnter={() => props.onDialogOpenModeChange((prev) => prev || PublishDialog.OPEN_MODE_DRAG)} // Only update if not already open | ||||
|         onResetOpenMode={() => props.onDialogOpenModeChange(PublishDialog.OPEN_MODE_DEFAULT)} | ||||
|       /> | ||||
|     </> | ||||
|  | @ -48,7 +48,7 @@ const Messaging = (props) => { | |||
| 
 | ||||
| const MessageBar = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const subscription = props.subscription; | ||||
|   const { subscription } = props; | ||||
|   const [snackOpen, setSnackOpen] = useState(false); | ||||
|   const handleSendClick = async () => { | ||||
|     try { | ||||
|  |  | |||
|  | @ -11,28 +11,28 @@ import Divider from "@mui/material/Divider"; | |||
| 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, ListSubheader, Portal, Tooltip } from "@mui/material"; | ||||
| import Button from "@mui/material/Button"; | ||||
| import Typography from "@mui/material/Typography"; | ||||
| import { useLocation, useNavigate } from "react-router-dom"; | ||||
| import { ChatBubble, MoreVert, NotificationsOffOutlined, Send } from "@mui/icons-material"; | ||||
| import Box from "@mui/material/Box"; | ||||
| import ArticleIcon from "@mui/icons-material/Article"; | ||||
| import { Trans, useTranslation } from "react-i18next"; | ||||
| import CelebrationIcon from "@mui/icons-material/Celebration"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import SubscribeDialog from "./SubscribeDialog"; | ||||
| import { openUrl, topicDisplayName, topicUrl } from "../app/utils"; | ||||
| import routes from "./routes"; | ||||
| import { ConnectionState } from "../app/Connection"; | ||||
| import { useLocation, useNavigate } from "react-router-dom"; | ||||
| import subscriptionManager from "../app/SubscriptionManager"; | ||||
| import { ChatBubble, MoreVert, NotificationsOffOutlined, Send } from "@mui/icons-material"; | ||||
| import Box from "@mui/material/Box"; | ||||
| import notifier from "../app/Notifier"; | ||||
| import config from "../app/config"; | ||||
| import ArticleIcon from "@mui/icons-material/Article"; | ||||
| import { Trans, useTranslation } from "react-i18next"; | ||||
| import session from "../app/Session"; | ||||
| import accountApi, { Permission, Role } from "../app/AccountApi"; | ||||
| import CelebrationIcon from "@mui/icons-material/Celebration"; | ||||
| import UpgradeDialog from "./UpgradeDialog"; | ||||
| import { AccountContext } from "./App"; | ||||
| import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import { SubscriptionPopup } from "./SubscriptionPopup"; | ||||
| 
 | ||||
| const navWidth = 280; | ||||
|  | @ -237,9 +237,7 @@ const UpgradeBanner = () => { | |||
| const SubscriptionList = (props) => { | ||||
|   const sortedSubscriptions = props.subscriptions | ||||
|     .filter((s) => !s.internal) | ||||
|     .sort((a, b) => { | ||||
|       return topicUrl(a.baseUrl, a.topic) < topicUrl(b.baseUrl, b.topic) ? -1 : 1; | ||||
|     }); | ||||
|     .sort((a, b) => (topicUrl(a.baseUrl, a.topic) < topicUrl(b.baseUrl, b.topic) ? -1 : 1)); | ||||
|   return ( | ||||
|     <> | ||||
|       {sortedSubscriptions.map((subscription) => ( | ||||
|  | @ -258,7 +256,7 @@ const SubscriptionItem = (props) => { | |||
|   const navigate = useNavigate(); | ||||
|   const [menuAnchorEl, setMenuAnchorEl] = useState(null); | ||||
| 
 | ||||
|   const subscription = props.subscription; | ||||
|   const { subscription } = props; | ||||
|   const iconBadge = subscription.new <= 99 ? subscription.new : "99+"; | ||||
|   const displayName = topicDisplayName(subscription); | ||||
|   const ariaLabel = subscription.state === ConnectionState.Connecting ? `${displayName} (${t("nav_button_connecting")})` : displayName; | ||||
|  |  | |||
|  | @ -4,6 +4,15 @@ import Card from "@mui/material/Card"; | |||
| import Typography from "@mui/material/Typography"; | ||||
| import * as React from "react"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import CheckIcon from "@mui/icons-material/Check"; | ||||
| import CloseIcon from "@mui/icons-material/Close"; | ||||
| import { useLiveQuery } from "dexie-react-hooks"; | ||||
| import Box from "@mui/material/Box"; | ||||
| import Button from "@mui/material/Button"; | ||||
| import InfiniteScroll from "react-infinite-scroll-component"; | ||||
| import { Trans, useTranslation } from "react-i18next"; | ||||
| import { useOutletContext } from "react-router-dom"; | ||||
| import { | ||||
|   formatBytes, | ||||
|   formatMessage, | ||||
|  | @ -15,23 +24,14 @@ import { | |||
|   topicShortUrl, | ||||
|   unmatchedTags, | ||||
| } from "../app/utils"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import CheckIcon from "@mui/icons-material/Check"; | ||||
| import CloseIcon from "@mui/icons-material/Close"; | ||||
| import { LightboxBackdrop, Paragraph, VerticallyCenteredContainer } from "./styles"; | ||||
| import { useLiveQuery } from "dexie-react-hooks"; | ||||
| import Box from "@mui/material/Box"; | ||||
| import Button from "@mui/material/Button"; | ||||
| import subscriptionManager from "../app/SubscriptionManager"; | ||||
| import InfiniteScroll from "react-infinite-scroll-component"; | ||||
| import priority1 from "../img/priority-1.svg"; | ||||
| import priority2 from "../img/priority-2.svg"; | ||||
| import priority4 from "../img/priority-4.svg"; | ||||
| 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"; | ||||
| 
 | ||||
| export const AllSubscriptions = () => { | ||||
|  | @ -52,46 +52,50 @@ export const SingleSubscription = () => { | |||
| }; | ||||
| 
 | ||||
| const AllSubscriptionsList = (props) => { | ||||
|   const subscriptions = props.subscriptions; | ||||
|   const { subscriptions } = props; | ||||
|   const notifications = useLiveQuery(() => subscriptionManager.getAllNotifications(), []); | ||||
|   if (notifications === null || notifications === undefined) { | ||||
|     return <Loading />; | ||||
|   } else if (subscriptions.length === 0) { | ||||
|   } | ||||
|   if (subscriptions.length === 0) { | ||||
|     return <NoSubscriptions />; | ||||
|   } else if (notifications.length === 0) { | ||||
|   } | ||||
|   if (notifications.length === 0) { | ||||
|     return <NoNotificationsWithoutSubscription subscriptions={subscriptions} />; | ||||
|   } | ||||
|   return <NotificationList key="all" notifications={notifications} messageBar={false} />; | ||||
| }; | ||||
| 
 | ||||
| const SingleSubscriptionList = (props) => { | ||||
|   const subscription = props.subscription; | ||||
|   const { subscription } = props; | ||||
|   const notifications = useLiveQuery(() => subscriptionManager.getNotifications(subscription.id), [subscription]); | ||||
|   if (notifications === null || notifications === undefined) { | ||||
|     return <Loading />; | ||||
|   } else if (notifications.length === 0) { | ||||
|   } | ||||
|   if (notifications.length === 0) { | ||||
|     return <NoNotifications subscription={subscription} />; | ||||
|   } | ||||
|   return <NotificationList id={subscription.id} notifications={notifications} messageBar={true} />; | ||||
|   return <NotificationList id={subscription.id} notifications={notifications} messageBar />; | ||||
| }; | ||||
| 
 | ||||
| const NotificationList = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const pageSize = 20; | ||||
|   const notifications = props.notifications; | ||||
|   const { notifications } = props; | ||||
|   const [snackOpen, setSnackOpen] = useState(false); | ||||
|   const [maxCount, setMaxCount] = useState(pageSize); | ||||
|   const count = Math.min(notifications.length, maxCount); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     return () => { | ||||
|   useEffect( | ||||
|     () => () => { | ||||
|       setMaxCount(pageSize); | ||||
|       const main = document.getElementById("main"); | ||||
|       if (main) { | ||||
|         main.scrollTo(0, 0); | ||||
|       } | ||||
|     }; | ||||
|   }, [props.id]); | ||||
|     }, | ||||
|     [props.id] | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <InfiniteScroll | ||||
|  | @ -129,8 +133,8 @@ const NotificationList = (props) => { | |||
| 
 | ||||
| const NotificationItem = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const notification = props.notification; | ||||
|   const attachment = notification.attachment; | ||||
|   const { notification } = props; | ||||
|   const { attachment } = notification; | ||||
|   const date = formatShortDateTime(notification.time); | ||||
|   const otherTags = unmatchedTags(notification.tags); | ||||
|   const tags = otherTags.length > 0 ? otherTags.join(", ") : null; | ||||
|  | @ -272,7 +276,7 @@ const priorityFiles = { | |||
| 
 | ||||
| const Attachment = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const attachment = props.attachment; | ||||
|   const { attachment } = props; | ||||
|   const expired = attachment.expires && attachment.expires < Date.now() / 1000; | ||||
|   const expires = attachment.expires && attachment.expires > Date.now() / 1000; | ||||
|   const displayableImage = !expired && attachment.type && attachment.type.startsWith("image/"); | ||||
|  | @ -402,20 +406,18 @@ const Image = (props) => { | |||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const UserActions = (props) => { | ||||
|   return ( | ||||
|     <> | ||||
|       {props.notification.actions.map((action) => ( | ||||
|         <UserAction key={action.id} notification={props.notification} action={action} /> | ||||
|       ))} | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| const UserActions = (props) => ( | ||||
|   <> | ||||
|     {props.notification.actions.map((action) => ( | ||||
|       <UserAction key={action.id} notification={props.notification} action={action} /> | ||||
|     ))} | ||||
|   </> | ||||
| ); | ||||
| 
 | ||||
| const UserAction = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const notification = props.notification; | ||||
|   const action = props.action; | ||||
|   const { notification } = props; | ||||
|   const { action } = props; | ||||
|   if (action.action === "broadcast") { | ||||
|     return ( | ||||
|       <Tooltip title={t("notifications_actions_not_supported")}> | ||||
|  | @ -426,7 +428,8 @@ const UserAction = (props) => { | |||
|         </span> | ||||
|       </Tooltip> | ||||
|     ); | ||||
|   } else if (action.action === "view") { | ||||
|   } | ||||
|   if (action.action === "view") { | ||||
|     return ( | ||||
|       <Tooltip title={t("notifications_actions_open_url_title", { url: action.url })}> | ||||
|         <Button | ||||
|  | @ -439,20 +442,21 @@ const UserAction = (props) => { | |||
|         </Button> | ||||
|       </Tooltip> | ||||
|     ); | ||||
|   } else if (action.action === "http") { | ||||
|   } | ||||
|   if (action.action === "http") { | ||||
|     const method = action.method ?? "POST"; | ||||
|     const label = action.label + (ACTION_LABEL_SUFFIX[action.progress ?? 0] ?? ""); | ||||
|     return ( | ||||
|       <Tooltip | ||||
|         title={t("notifications_actions_http_request_title", { | ||||
|           method: method, | ||||
|           method, | ||||
|           url: action.url, | ||||
|         })} | ||||
|       > | ||||
|         <Button | ||||
|           onClick={() => performHttpAction(notification, action)} | ||||
|           aria-label={t("notifications_actions_http_request_title", { | ||||
|             method: method, | ||||
|             method, | ||||
|             url: action.url, | ||||
|           })} | ||||
|         > | ||||
|  | @ -493,7 +497,7 @@ const updateActionStatus = (notification, action, progress, error) => { | |||
|     if (a.id !== action.id) { | ||||
|       return a; | ||||
|     } | ||||
|     return { ...a, progress: progress, error: error }; | ||||
|     return { ...a, progress, error }; | ||||
|   }); | ||||
|   subscriptionManager.updateNotification(notification); | ||||
| }; | ||||
|  | @ -574,17 +578,15 @@ const NoSubscriptions = () => { | |||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const ForMoreDetails = () => { | ||||
|   return ( | ||||
|     <Trans | ||||
|       i18nKey="notifications_more_details" | ||||
|       components={{ | ||||
|         websiteLink: <Link href="https://ntfy.sh" target="_blank" rel="noopener" />, | ||||
|         docsLink: <Link href="https://ntfy.sh/docs" target="_blank" rel="noopener" />, | ||||
|       }} | ||||
|     /> | ||||
|   ); | ||||
| }; | ||||
| const ForMoreDetails = () => ( | ||||
|   <Trans | ||||
|     i18nKey="notifications_more_details" | ||||
|     components={{ | ||||
|       websiteLink: <Link href="https://ntfy.sh" target="_blank" rel="noopener" />, | ||||
|       docsLink: <Link href="https://ntfy.sh/docs" target="_blank" rel="noopener" />, | ||||
|     }} | ||||
|   /> | ||||
| ); | ||||
| 
 | ||||
| const Loading = () => { | ||||
|   const { t } = useTranslation(); | ||||
|  |  | |||
|  | @ -37,8 +37,8 @@ const PopupMenu = (props) => { | |||
|           }, | ||||
|         }, | ||||
|       }} | ||||
|       transformOrigin={{ horizontal: horizontal, vertical: "top" }} | ||||
|       anchorOrigin={{ horizontal: horizontal, vertical: "bottom" }} | ||||
|       transformOrigin={{ horizontal, vertical: "top" }} | ||||
|       anchorOrigin={{ horizontal, vertical: "bottom" }} | ||||
|     > | ||||
|       {props.children} | ||||
|     </Menu> | ||||
|  |  | |||
|  | @ -1,8 +1,6 @@ | |||
| import * as React from "react"; | ||||
| 
 | ||||
| export const PrefGroup = (props) => { | ||||
|   return <div role="table">{props.children}</div>; | ||||
| }; | ||||
| export const PrefGroup = (props) => <div role="table">{props.children}</div>; | ||||
| 
 | ||||
| export const Pref = (props) => { | ||||
|   const justifyContent = props.alignTop ? "normal" : "center"; | ||||
|  | @ -24,7 +22,7 @@ export const Pref = (props) => { | |||
|           flex: "1 0 40%", | ||||
|           display: "flex", | ||||
|           flexDirection: "column", | ||||
|           justifyContent: justifyContent, | ||||
|           justifyContent, | ||||
|           paddingRight: "30px", | ||||
|         }} | ||||
|       > | ||||
|  | @ -44,7 +42,7 @@ export const Pref = (props) => { | |||
|           flex: "1 0 calc(60% - 50px)", | ||||
|           display: "flex", | ||||
|           flexDirection: "column", | ||||
|           justifyContent: justifyContent, | ||||
|           justifyContent, | ||||
|         }} | ||||
|       > | ||||
|         {props.children} | ||||
|  |  | |||
|  | @ -17,8 +17,6 @@ import { | |||
|   useMediaQuery, | ||||
| } from "@mui/material"; | ||||
| import Typography from "@mui/material/Typography"; | ||||
| import prefs from "../app/Prefs"; | ||||
| import { Paragraph } from "./styles"; | ||||
| import EditIcon from "@mui/icons-material/Edit"; | ||||
| import CloseIcon from "@mui/icons-material/Close"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
|  | @ -29,39 +27,39 @@ import MenuItem from "@mui/material/MenuItem"; | |||
| import Card from "@mui/material/Card"; | ||||
| import Button from "@mui/material/Button"; | ||||
| import { useLiveQuery } from "dexie-react-hooks"; | ||||
| import theme from "./theme"; | ||||
| import Dialog from "@mui/material/Dialog"; | ||||
| import DialogTitle from "@mui/material/DialogTitle"; | ||||
| import DialogContent from "@mui/material/DialogContent"; | ||||
| import DialogActions from "@mui/material/DialogActions"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import { Info } from "@mui/icons-material"; | ||||
| import { useOutletContext } from "react-router-dom"; | ||||
| import theme from "./theme"; | ||||
| import userManager from "../app/UserManager"; | ||||
| import { playSound, shuffle, sounds, validUrl } from "../app/utils"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| import accountApi, { Permission, Role } from "../app/AccountApi"; | ||||
| import { Pref, PrefGroup } from "./Pref"; | ||||
| import { Info } from "@mui/icons-material"; | ||||
| import { AccountContext } from "./App"; | ||||
| import { useOutletContext } from "react-router-dom"; | ||||
| import { Paragraph } from "./styles"; | ||||
| import prefs from "../app/Prefs"; | ||||
| import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons"; | ||||
| import { ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog } from "./ReserveDialogs"; | ||||
| import { UnauthorizedError } from "../app/errors"; | ||||
| import subscriptionManager from "../app/SubscriptionManager"; | ||||
| import { subscribeTopic } from "./SubscribeDialog"; | ||||
| 
 | ||||
| const Preferences = () => { | ||||
|   return ( | ||||
|     <Container maxWidth="md" sx={{ marginTop: 3, marginBottom: 3 }}> | ||||
|       <Stack spacing={3}> | ||||
|         <Notifications /> | ||||
|         <Reservations /> | ||||
|         <Users /> | ||||
|         <Appearance /> | ||||
|       </Stack> | ||||
|     </Container> | ||||
|   ); | ||||
| }; | ||||
| const Preferences = () => ( | ||||
|   <Container maxWidth="md" sx={{ marginTop: 3, marginBottom: 3 }}> | ||||
|     <Stack spacing={3}> | ||||
|       <Notifications /> | ||||
|       <Reservations /> | ||||
|       <Users /> | ||||
|       <Appearance /> | ||||
|     </Stack> | ||||
|   </Container> | ||||
| ); | ||||
| 
 | ||||
| const Notifications = () => { | ||||
|   const { t } = useTranslation(); | ||||
|  | @ -107,7 +105,7 @@ const Sound = () => { | |||
|       <div style={{ display: "flex", width: "100%" }}> | ||||
|         <FormControl fullWidth variant="standard" sx={{ margin: 1 }}> | ||||
|           <Select value={sound} onChange={handleChange} aria-labelledby={labelId}> | ||||
|             <MenuItem value={"none"}>{t("prefs_notifications_sound_no_sound")}</MenuItem> | ||||
|             <MenuItem value="none">{t("prefs_notifications_sound_no_sound")}</MenuItem> | ||||
|             {Object.entries(sounds).map((s) => ( | ||||
|               <MenuItem key={s[0]} value={s[0]}> | ||||
|                 {s[1].label} | ||||
|  | @ -245,7 +243,7 @@ const Users = () => { | |||
|         </Typography> | ||||
|         <Paragraph> | ||||
|           {t("prefs_users_description")} | ||||
|           {session.exists() && <>{" " + t("prefs_users_description_no_sync")}</>} | ||||
|           {session.exists() && <>{` ${t("prefs_users_description_no_sync")}`}</>} | ||||
|         </Paragraph> | ||||
|         {users?.length > 0 && <UserTable users={users} />} | ||||
|       </CardContent> | ||||
|  | @ -371,9 +369,9 @@ const UserDialog = (props) => { | |||
|   })(); | ||||
|   const handleSubmit = async () => { | ||||
|     props.onSubmit({ | ||||
|       baseUrl: baseUrl, | ||||
|       username: username, | ||||
|       password: password, | ||||
|       baseUrl, | ||||
|       username, | ||||
|       password, | ||||
|     }); | ||||
|   }; | ||||
|   useEffect(() => { | ||||
|  | @ -479,7 +477,7 @@ const Language = () => { | |||
|   const showFlags = !navigator.userAgent.includes("Windows"); | ||||
|   let title = t("prefs_appearance_language_title"); | ||||
|   if (showFlags) { | ||||
|     title += " " + randomFlags.join(" "); | ||||
|     title += ` ${randomFlags.join(" ")}`; | ||||
|   } | ||||
| 
 | ||||
|   const handleChange = async (ev) => { | ||||
|  |  | |||
|  | @ -1,13 +1,7 @@ | |||
| import * as React from "react"; | ||||
| import { useContext, useEffect, useRef, useState } from "react"; | ||||
| import theme from "./theme"; | ||||
| import { Checkbox, Chip, FormControl, FormControlLabel, InputLabel, Link, Select, Tooltip, useMediaQuery } from "@mui/material"; | ||||
| import TextField from "@mui/material/TextField"; | ||||
| import priority1 from "../img/priority-1.svg"; | ||||
| import priority2 from "../img/priority-2.svg"; | ||||
| import priority3 from "../img/priority-3.svg"; | ||||
| import priority4 from "../img/priority-4.svg"; | ||||
| import priority5 from "../img/priority-5.svg"; | ||||
| import Dialog from "@mui/material/Dialog"; | ||||
| import DialogTitle from "@mui/material/DialogTitle"; | ||||
| import DialogContent from "@mui/material/DialogContent"; | ||||
|  | @ -17,14 +11,20 @@ import IconButton from "@mui/material/IconButton"; | |||
| import InsertEmoticonIcon from "@mui/icons-material/InsertEmoticon"; | ||||
| import { Close } from "@mui/icons-material"; | ||||
| import MenuItem from "@mui/material/MenuItem"; | ||||
| import { formatBytes, maybeWithAuth, topicShortUrl, topicUrl, validTopic, validUrl } from "../app/utils"; | ||||
| import Box from "@mui/material/Box"; | ||||
| import { Trans, useTranslation } from "react-i18next"; | ||||
| import priority1 from "../img/priority-1.svg"; | ||||
| import priority2 from "../img/priority-2.svg"; | ||||
| import priority3 from "../img/priority-3.svg"; | ||||
| import priority4 from "../img/priority-4.svg"; | ||||
| import priority5 from "../img/priority-5.svg"; | ||||
| import { formatBytes, maybeWithAuth, topicShortUrl, topicUrl, validTopic, validUrl } from "../app/utils"; | ||||
| import AttachmentIcon from "./AttachmentIcon"; | ||||
| import DialogFooter from "./DialogFooter"; | ||||
| import api from "../app/Api"; | ||||
| import userManager from "../app/UserManager"; | ||||
| import EmojiPicker from "./EmojiPicker"; | ||||
| import { Trans, useTranslation } from "react-i18next"; | ||||
| import theme from "./theme"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| import accountApi from "../app/AccountApi"; | ||||
|  | @ -137,7 +137,7 @@ const PublishDialog = (props) => { | |||
|     if (attachFile && message.trim()) { | ||||
|       url.searchParams.append("message", message.replaceAll("\n", "\\n").trim()); | ||||
|     } | ||||
|     const body = attachFile ? attachFile : message; | ||||
|     const body = attachFile || message; | ||||
|     try { | ||||
|       const user = await userManager.get(baseUrl); | ||||
|       const headers = maybeWithAuth({}, user); | ||||
|  | @ -183,13 +183,15 @@ const PublishDialog = (props) => { | |||
|             remainingBytes: formatBytes(remainingBytes), | ||||
|           }) | ||||
|         ); | ||||
|       } else if (fileSizeLimitReached) { | ||||
|       } | ||||
|       if (fileSizeLimitReached) { | ||||
|         return setAttachFileError( | ||||
|           t("publish_dialog_attachment_limits_file_reached", { | ||||
|             fileSizeLimit: formatBytes(fileSizeLimit), | ||||
|           }) | ||||
|         ); | ||||
|       } else if (quotaReached) { | ||||
|       } | ||||
|       if (quotaReached) { | ||||
|         return setAttachFileError( | ||||
|           t("publish_dialog_attachment_limits_quota_reached", { | ||||
|             remainingBytes: formatBytes(remainingBytes), | ||||
|  | @ -377,7 +379,7 @@ const PublishDialog = (props) => { | |||
|                     key={`priorityMenuItem${priority}`} | ||||
|                     value={priority} | ||||
|                     aria-label={t("notifications_priority_x", { | ||||
|                       priority: priority, | ||||
|                       priority, | ||||
|                     })} | ||||
|                   > | ||||
|                     <div style={{ display: "flex", alignItems: "center" }}> | ||||
|  | @ -385,7 +387,7 @@ const PublishDialog = (props) => { | |||
|                         src={priorities[priority].file} | ||||
|                         style={{ marginRight: "8px" }} | ||||
|                         alt={t("notifications_priority_x", { | ||||
|                           priority: priority, | ||||
|                           priority, | ||||
|                         })} | ||||
|                       /> | ||||
|                       <div>{priorities[priority].label}</div> | ||||
|  | @ -533,7 +535,7 @@ const PublishDialog = (props) => { | |||
|               /> | ||||
|             </ClosableRow> | ||||
|           )} | ||||
|           <input type="file" ref={attachFileInput} onChange={handleAttachFileChanged} style={{ display: "none" }} aria-hidden={true} /> | ||||
|           <input type="file" ref={attachFileInput} onChange={handleAttachFileChanged} style={{ display: "none" }} aria-hidden /> | ||||
|           {showAttachFile && ( | ||||
|             <AttachmentBox | ||||
|               file={attachFile} | ||||
|  | @ -707,13 +709,11 @@ const PublishDialog = (props) => { | |||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const Row = (props) => { | ||||
|   return ( | ||||
|     <div style={{ display: "flex" }} role="row"> | ||||
|       {props.children} | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| const Row = (props) => ( | ||||
|   <div style={{ display: "flex" }} role="row"> | ||||
|     {props.children} | ||||
|   </div> | ||||
| ); | ||||
| 
 | ||||
| const ClosableRow = (props) => { | ||||
|   const closable = props.hasOwnProperty("closable") ? props.closable : true; | ||||
|  | @ -748,7 +748,7 @@ const DialogIconButton = (props) => { | |||
| 
 | ||||
| const AttachmentBox = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const file = props.file; | ||||
|   const { file } = props; | ||||
|   return ( | ||||
|     <> | ||||
|       <Typography variant="body1" sx={{ marginTop: 2 }}> | ||||
|  | @ -811,13 +811,7 @@ const ExpandingTextField = (props) => { | |||
|   }, [props.value]); | ||||
|   return ( | ||||
|     <> | ||||
|       <Typography | ||||
|         ref={invisibleFieldRef} | ||||
|         component="span" | ||||
|         variant={props.variant} | ||||
|         aria-hidden={true} | ||||
|         sx={{ position: "absolute", left: "-200%" }} | ||||
|       > | ||||
|       <Typography ref={invisibleFieldRef} component="span" variant={props.variant} aria-hidden sx={{ position: "absolute", left: "-200%" }}> | ||||
|         {props.value} | ||||
|       </Typography> | ||||
|       <TextField | ||||
|  |  | |||
|  | @ -7,18 +7,18 @@ import DialogContent from "@mui/material/DialogContent"; | |||
| import DialogContentText from "@mui/material/DialogContentText"; | ||||
| import DialogTitle from "@mui/material/DialogTitle"; | ||||
| import { Alert, FormControl, Select, useMediaQuery } from "@mui/material"; | ||||
| import theme from "./theme"; | ||||
| import { validTopic } from "../app/utils"; | ||||
| import DialogFooter from "./DialogFooter"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| import accountApi, { Permission } from "../app/AccountApi"; | ||||
| import ReserveTopicSelect from "./ReserveTopicSelect"; | ||||
| import MenuItem from "@mui/material/MenuItem"; | ||||
| import ListItemIcon from "@mui/material/ListItemIcon"; | ||||
| import ListItemText from "@mui/material/ListItemText"; | ||||
| import { Check, DeleteForever } from "@mui/icons-material"; | ||||
| import theme from "./theme"; | ||||
| import { validTopic } from "../app/utils"; | ||||
| import DialogFooter from "./DialogFooter"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| import accountApi, { Permission } from "../app/AccountApi"; | ||||
| import ReserveTopicSelect from "./ReserveTopicSelect"; | ||||
| import { TopicReservedError, UnauthorizedError } from "../app/errors"; | ||||
| 
 | ||||
| export const ReserveAddDialog = (props) => { | ||||
|  | @ -164,7 +164,7 @@ export const ReserveDeleteDialog = (props) => { | |||
|               </ListItemIcon> | ||||
|               <ListItemText primary={t("reservation_delete_dialog_action_keep_title")} /> | ||||
|             </MenuItem> | ||||
|             <MenuItem value={true}> | ||||
|             <MenuItem value> | ||||
|               <ListItemIcon> | ||||
|                 <DeleteForever /> | ||||
|               </ListItemIcon> | ||||
|  |  | |||
|  | @ -2,21 +2,13 @@ import * as React from "react"; | |||
| import { Lock, Public } from "@mui/icons-material"; | ||||
| import Box from "@mui/material/Box"; | ||||
| 
 | ||||
| export const PermissionReadWrite = React.forwardRef((props, ref) => { | ||||
|   return <PermissionInternal icon={Public} ref={ref} {...props} />; | ||||
| }); | ||||
| export const PermissionReadWrite = React.forwardRef((props, ref) => <PermissionInternal icon={Public} ref={ref} {...props} />); | ||||
| 
 | ||||
| export const PermissionDenyAll = React.forwardRef((props, ref) => { | ||||
|   return <PermissionInternal icon={Lock} ref={ref} {...props} />; | ||||
| }); | ||||
| export const PermissionDenyAll = React.forwardRef((props, ref) => <PermissionInternal icon={Lock} ref={ref} {...props} />); | ||||
| 
 | ||||
| export const PermissionRead = React.forwardRef((props, ref) => { | ||||
|   return <PermissionInternal icon={Public} text="R" ref={ref} {...props} />; | ||||
| }); | ||||
| export const PermissionRead = React.forwardRef((props, ref) => <PermissionInternal icon={Public} text="R" ref={ref} {...props} />); | ||||
| 
 | ||||
| export const PermissionWrite = React.forwardRef((props, ref) => { | ||||
|   return <PermissionInternal icon={Public} text="W" ref={ref} {...props} />; | ||||
| }); | ||||
| export const PermissionWrite = React.forwardRef((props, ref) => <PermissionInternal icon={Public} text="W" ref={ref} {...props} />); | ||||
| 
 | ||||
| const PermissionInternal = React.forwardRef((props, ref) => { | ||||
|   const size = props.size ?? "medium"; | ||||
|  |  | |||
|  | @ -3,17 +3,17 @@ import { useState } 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 session from "../app/Session"; | ||||
| import Typography from "@mui/material/Typography"; | ||||
| import { NavLink } from "react-router-dom"; | ||||
| import AvatarBox from "./AvatarBox"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import WarningAmberIcon from "@mui/icons-material/WarningAmber"; | ||||
| import accountApi from "../app/AccountApi"; | ||||
| import { InputAdornment } from "@mui/material"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import { Visibility, VisibilityOff } from "@mui/icons-material"; | ||||
| import accountApi from "../app/AccountApi"; | ||||
| import AvatarBox from "./AvatarBox"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| import { AccountCreateLimitReachedError, UserExistsError } from "../app/errors"; | ||||
| 
 | ||||
| const Signup = () => { | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ 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 { useTranslation } from "react-i18next"; | ||||
| import theme from "./theme"; | ||||
| import api from "../app/Api"; | ||||
| import { randomAlphanumericString, topicUrl, validTopic, validUrl } from "../app/utils"; | ||||
|  | @ -14,7 +15,6 @@ 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, { Permission, Role } from "../app/AccountApi"; | ||||
|  | @ -33,7 +33,7 @@ const SubscribeDialog = (props) => { | |||
| 
 | ||||
|   const handleSuccess = async () => { | ||||
|     console.log(`[SubscribeDialog] Subscribing to topic ${topic}`); | ||||
|     const actualBaseUrl = baseUrl ? baseUrl : config.base_url; | ||||
|     const actualBaseUrl = baseUrl || config.base_url; | ||||
|     const subscription = await subscribeTopic(actualBaseUrl, topic); | ||||
|     poller.pollInBackground(subscription); // Dangle! | ||||
|     props.onSuccess(subscription); | ||||
|  | @ -66,7 +66,7 @@ const SubscribePage = (props) => { | |||
|   const [anotherServerVisible, setAnotherServerVisible] = useState(false); | ||||
|   const [everyone, setEveryone] = useState(Permission.DENY_ALL); | ||||
|   const baseUrl = anotherServerVisible ? props.baseUrl : config.base_url; | ||||
|   const topic = props.topic; | ||||
|   const { topic } = props; | ||||
|   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.base_url | ||||
|  | @ -86,14 +86,13 @@ const SubscribePage = (props) => { | |||
|       if (user) { | ||||
|         setError( | ||||
|           t("subscribe_dialog_error_user_not_authorized", { | ||||
|             username: username, | ||||
|             username, | ||||
|           }) | ||||
|         ); | ||||
|         return; | ||||
|       } else { | ||||
|         props.onNeedsLogin(); | ||||
|         return; | ||||
|       } | ||||
|       props.onNeedsLogin(); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // Reserve topic (if requested) | ||||
|  | @ -125,10 +124,9 @@ const SubscribePage = (props) => { | |||
|     if (anotherServerVisible) { | ||||
|       const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic)); | ||||
|       return validTopic(topic) && validUrl(baseUrl) && !isExistingTopicUrl; | ||||
|     } else { | ||||
|       const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(config.base_url, topic)); | ||||
|       return validTopic(topic) && !isExistingTopicUrl; | ||||
|     } | ||||
|     const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(config.base_url, topic)); | ||||
|     return validTopic(topic) && !isExistingTopicUrl; | ||||
|   })(); | ||||
| 
 | ||||
|   const updateBaseUrl = (ev, newVal) => { | ||||
|  | @ -242,14 +240,14 @@ const LoginPage = (props) => { | |||
|   const [password, setPassword] = useState(""); | ||||
|   const [error, setError] = useState(""); | ||||
|   const baseUrl = props.baseUrl ? props.baseUrl : config.base_url; | ||||
|   const topic = props.topic; | ||||
|   const { topic } = props; | ||||
| 
 | ||||
|   const handleLogin = async () => { | ||||
|     const user = { baseUrl, username, password }; | ||||
|     const success = await api.topicAuth(baseUrl, topic, user); | ||||
|     if (!success) { | ||||
|       console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`); | ||||
|       setError(t("subscribe_dialog_error_user_not_authorized", { username: username })); | ||||
|       setError(t("subscribe_dialog_error_user_not_authorized", { username })); | ||||
|       return; | ||||
|     } | ||||
|     console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`); | ||||
|  |  | |||
|  | @ -7,20 +7,20 @@ import DialogContent from "@mui/material/DialogContent"; | |||
| import DialogContentText from "@mui/material/DialogContentText"; | ||||
| import DialogTitle from "@mui/material/DialogTitle"; | ||||
| import { Chip, InputAdornment, Portal, Snackbar, useMediaQuery } from "@mui/material"; | ||||
| import theme from "./theme"; | ||||
| import subscriptionManager from "../app/SubscriptionManager"; | ||||
| import DialogFooter from "./DialogFooter"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import accountApi, { Role } from "../app/AccountApi"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| import MenuItem from "@mui/material/MenuItem"; | ||||
| import PopupMenu from "./PopupMenu"; | ||||
| import { formatShortDateTime, shuffle } from "../app/utils"; | ||||
| import api from "../app/Api"; | ||||
| import { useNavigate } from "react-router-dom"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import { Clear } from "@mui/icons-material"; | ||||
| import theme from "./theme"; | ||||
| import subscriptionManager from "../app/SubscriptionManager"; | ||||
| import DialogFooter from "./DialogFooter"; | ||||
| import accountApi, { Role } from "../app/AccountApi"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| import PopupMenu from "./PopupMenu"; | ||||
| import { formatShortDateTime, shuffle } from "../app/utils"; | ||||
| import api from "../app/Api"; | ||||
| import { AccountContext } from "./App"; | ||||
| import { ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog } from "./ReserveDialogs"; | ||||
| import { UnauthorizedError } from "../app/errors"; | ||||
|  | @ -34,7 +34,7 @@ export const SubscriptionPopup = (props) => { | |||
|   const [reserveEditDialogOpen, setReserveEditDialogOpen] = useState(false); | ||||
|   const [reserveDeleteDialogOpen, setReserveDeleteDialogOpen] = useState(false); | ||||
|   const [showPublishError, setShowPublishError] = useState(false); | ||||
|   const subscription = props.subscription; | ||||
|   const { subscription } = props; | ||||
|   const placement = props.placement ?? "left"; | ||||
|   const reservations = account?.reservations || []; | ||||
| 
 | ||||
|  | @ -64,8 +64,8 @@ export const SubscriptionPopup = (props) => { | |||
|   }; | ||||
| 
 | ||||
|   const handleSendTestMessage = async () => { | ||||
|     const baseUrl = props.subscription.baseUrl; | ||||
|     const topic = props.subscription.topic; | ||||
|     const { baseUrl } = props.subscription; | ||||
|     const { topic } = props.subscription; | ||||
|     const tags = shuffle([ | ||||
|       "grinning", | ||||
|       "octopus", | ||||
|  | @ -110,9 +110,9 @@ export const SubscriptionPopup = (props) => { | |||
|     ])[0]; | ||||
|     try { | ||||
|       await api.publish(baseUrl, topic, message, { | ||||
|         title: title, | ||||
|         priority: priority, | ||||
|         tags: tags, | ||||
|         title, | ||||
|         priority, | ||||
|         tags, | ||||
|       }); | ||||
|     } catch (e) { | ||||
|       console.log(`[SubscriptionPopup] Error publishing message`, e); | ||||
|  | @ -201,7 +201,7 @@ export const SubscriptionPopup = (props) => { | |||
| 
 | ||||
| const DisplayNameDialog = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const subscription = props.subscription; | ||||
|   const { subscription } = props; | ||||
|   const [error, setError] = useState(""); | ||||
|   const [displayName, setDisplayName] = useState(subscription.displayName ?? ""); | ||||
|   const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); | ||||
|  | @ -265,9 +265,11 @@ export const ReserveLimitChip = () => { | |||
|   const { account } = useContext(AccountContext); | ||||
|   if (account?.role === Role.ADMIN || account?.stats.reservations_remaining > 0) { | ||||
|     return <></>; | ||||
|   } else if (config.enable_payments) { | ||||
|   } | ||||
|   if (config.enable_payments) { | ||||
|     return account?.limits.reservations > 0 ? <LimitReachedChip /> : <ProChip />; | ||||
|   } else if (account) { | ||||
|   } | ||||
|   if (account) { | ||||
|     return <LimitReachedChip />; | ||||
|   } | ||||
|   return <></>; | ||||
|  | @ -294,7 +296,7 @@ export const ProChip = () => { | |||
|   const { t } = useTranslation(); | ||||
|   return ( | ||||
|     <Chip | ||||
|       label={"ntfy Pro"} | ||||
|       label="ntfy Pro" | ||||
|       variant="outlined" | ||||
|       color="primary" | ||||
|       sx={{ | ||||
|  |  | |||
|  | @ -4,15 +4,9 @@ import Dialog from "@mui/material/Dialog"; | |||
| import DialogContent from "@mui/material/DialogContent"; | ||||
| import DialogTitle from "@mui/material/DialogTitle"; | ||||
| import { Alert, CardActionArea, CardContent, Chip, Link, ListItem, Switch, useMediaQuery } from "@mui/material"; | ||||
| import theme from "./theme"; | ||||
| import Button from "@mui/material/Button"; | ||||
| import accountApi, { SubscriptionInterval } from "../app/AccountApi"; | ||||
| import session from "../app/Session"; | ||||
| import routes from "./routes"; | ||||
| import Card from "@mui/material/Card"; | ||||
| import Typography from "@mui/material/Typography"; | ||||
| import { AccountContext } from "./App"; | ||||
| import { formatBytes, formatNumber, formatPrice, formatShortDate } from "../app/utils"; | ||||
| import { Trans, useTranslation } from "react-i18next"; | ||||
| import List from "@mui/material/List"; | ||||
| import { Check, Close } from "@mui/icons-material"; | ||||
|  | @ -20,9 +14,15 @@ import ListItemIcon from "@mui/material/ListItemIcon"; | |||
| import ListItemText from "@mui/material/ListItemText"; | ||||
| import Box from "@mui/material/Box"; | ||||
| import { NavLink } from "react-router-dom"; | ||||
| import { UnauthorizedError } from "../app/errors"; | ||||
| import DialogContentText from "@mui/material/DialogContentText"; | ||||
| import DialogActions from "@mui/material/DialogActions"; | ||||
| import { UnauthorizedError } from "../app/errors"; | ||||
| import { formatBytes, formatNumber, formatPrice, formatShortDate } from "../app/utils"; | ||||
| import { AccountContext } from "./App"; | ||||
| import routes from "./routes"; | ||||
| import session from "../app/Session"; | ||||
| import accountApi, { SubscriptionInterval } from "../app/AccountApi"; | ||||
| import theme from "./theme"; | ||||
| 
 | ||||
| const UpgradeDialog = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|  | @ -52,7 +52,9 @@ const UpgradeDialog = (props) => { | |||
|   const currentTierCode = currentTier?.code; // May be undefined | ||||
| 
 | ||||
|   // Figure out buttons, labels and the submit action | ||||
|   let submitAction, submitButtonLabel, banner; | ||||
|   let submitAction; | ||||
|   let submitButtonLabel; | ||||
|   let banner; | ||||
|   if (!account) { | ||||
|     submitButtonLabel = t("account_upgrade_dialog_button_redirect_signup"); | ||||
|     submitAction = Action.REDIRECT_SIGNUP; | ||||
|  | @ -112,8 +114,8 @@ const UpgradeDialog = (props) => { | |||
|   }; | ||||
| 
 | ||||
|   // Figure out discount | ||||
|   let discount = 0, | ||||
|     upto = false; | ||||
|   let discount = 0; | ||||
|   let upto = false; | ||||
|   if (newTier?.prices) { | ||||
|     discount = Math.round(((newTier.prices.month * 12) / newTier.prices.year - 1) * 100); | ||||
|   } else { | ||||
|  | @ -157,8 +159,8 @@ const UpgradeDialog = (props) => { | |||
|               <Chip | ||||
|                 label={ | ||||
|                   upto | ||||
|                     ? t("account_upgrade_dialog_interval_yearly_discount_save_up_to", { discount: discount }) | ||||
|                     : t("account_upgrade_dialog_interval_yearly_discount_save", { discount: discount }) | ||||
|                     ? t("account_upgrade_dialog_interval_yearly_discount_save_up_to", { discount }) | ||||
|                     : t("account_upgrade_dialog_interval_yearly_discount_save", { discount }) | ||||
|                 } | ||||
|                 color="primary" | ||||
|                 size="small" | ||||
|  | @ -269,9 +271,11 @@ const UpgradeDialog = (props) => { | |||
| 
 | ||||
| const TierCard = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const tier = props.tier; | ||||
|   const { tier } = props; | ||||
| 
 | ||||
|   let cardStyle, labelStyle, labelText; | ||||
|   let cardStyle; | ||||
|   let labelStyle; | ||||
|   let labelText; | ||||
|   if (props.selected) { | ||||
|     cardStyle = { background: "#eee", border: "3px solid #338574" }; | ||||
|     labelStyle = { background: "#338574", color: "white" }; | ||||
|  | @ -392,25 +396,19 @@ const TierCard = (props) => { | |||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const Feature = (props) => { | ||||
|   return <FeatureItem feature={true}>{props.children}</FeatureItem>; | ||||
| }; | ||||
| const Feature = (props) => <FeatureItem feature>{props.children}</FeatureItem>; | ||||
| 
 | ||||
| const NoFeature = (props) => { | ||||
|   return <FeatureItem feature={false}>{props.children}</FeatureItem>; | ||||
| }; | ||||
| const NoFeature = (props) => <FeatureItem feature={false}>{props.children}</FeatureItem>; | ||||
| 
 | ||||
| const FeatureItem = (props) => { | ||||
|   return ( | ||||
|     <ListItem disableGutters sx={{ m: 0, p: 0 }}> | ||||
|       <ListItemIcon sx={{ minWidth: "24px" }}> | ||||
|         {props.feature && <Check fontSize="small" sx={{ color: "#338574" }} />} | ||||
|         {!props.feature && <Close fontSize="small" sx={{ color: "gray" }} />} | ||||
|       </ListItemIcon> | ||||
|       <ListItemText sx={{ mt: "2px", mb: "2px" }} primary={<Typography variant="body1">{props.children}</Typography>} /> | ||||
|     </ListItem> | ||||
|   ); | ||||
| }; | ||||
| const FeatureItem = (props) => ( | ||||
|   <ListItem disableGutters sx={{ m: 0, p: 0 }}> | ||||
|     <ListItemIcon sx={{ minWidth: "24px" }}> | ||||
|       {props.feature && <Check fontSize="small" sx={{ color: "#338574" }} />} | ||||
|       {!props.feature && <Close fontSize="small" sx={{ color: "gray" }} />} | ||||
|     </ListItemIcon> | ||||
|     <ListItemText sx={{ mt: "2px", mb: "2px" }} primary={<Typography variant="body1">{props.children}</Typography>} /> | ||||
|   </ListItem> | ||||
| ); | ||||
| 
 | ||||
| const Action = { | ||||
|   REDIRECT_SIGNUP: 1, | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ export const useConnectionListeners = (account, subscriptions, users) => { | |||
|       }; | ||||
|     }, | ||||
|     // We have to disable dep checking for "navigate". This is fine, it never changes.
 | ||||
|     // eslint-disable-next-line
 | ||||
| 
 | ||||
|     [] | ||||
|   ); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import Typography from "@mui/material/Typography"; | ||||
| import theme from "./theme"; | ||||
| import Container from "@mui/material/Container"; | ||||
| import { Backdrop, styled } from "@mui/material"; | ||||
| import theme from "./theme"; | ||||
| 
 | ||||
| export const Paragraph = styled(Typography)({ | ||||
|   paddingTop: 8, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue