Use another server
This commit is contained in:
		
							parent
							
								
									17e5af654b
								
							
						
					
					
						commit
						f23c7a2dbf
					
				
					 9 changed files with 131 additions and 28 deletions
				
			
		|  | @ -15,7 +15,6 @@ | ||||||
|     "@mui/styles": "^5.4.2", |     "@mui/styles": "^5.4.2", | ||||||
|     "react": "latest", |     "react": "latest", | ||||||
|     "react-dom": "latest", |     "react-dom": "latest", | ||||||
|     "react-router-dom": "^6.2.1", |  | ||||||
|     "react-scripts": "^3.0.1" |     "react-scripts": "^3.0.1" | ||||||
|   }, |   }, | ||||||
|   "browserslist": { |   "browserslist": { | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| <head> | <head> | ||||||
|   <meta charset="UTF-8"> |   <meta charset="UTF-8"> | ||||||
| 
 | 
 | ||||||
|   <title>ntfy.sh | Send push notifications to your phone via PUT/POST</title> |   <title>ntfy web</title> | ||||||
| 
 | 
 | ||||||
|   <!-- Mobile view --> |   <!-- Mobile view --> | ||||||
|   <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> |   <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> | ||||||
|  |  | ||||||
|  | @ -11,8 +11,12 @@ export const topicUrlAuth = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/aut | ||||||
| export const topicShortUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic)); | export const topicShortUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic)); | ||||||
| export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, ""); | export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, ""); | ||||||
| 
 | 
 | ||||||
|  | export const validUrl = (url) => { | ||||||
|  |     return url.match(/^https?:\/\//); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export const validTopic = (topic) => { | export const validTopic = (topic) => { | ||||||
|     return topic.match(/^([-_a-zA-Z0-9]{1,64})$/) // Regex must match Go & Android app!
 |     return topic.match(/^([-_a-zA-Z0-9]{1,64})$/); // Regex must match Go & Android app!
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Format emojis (see emoji.js)
 | // Format emojis (see emoji.js)
 | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import MenuIcon from "@mui/icons-material/Menu"; | ||||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||||
| import IconSubscribeSettings from "./IconSubscribeSettings"; | import IconSubscribeSettings from "./IconSubscribeSettings"; | ||||||
| import * as React from "react"; | import * as React from "react"; | ||||||
|  | import Box from "@mui/material/Box"; | ||||||
| 
 | 
 | ||||||
| const ActionBar = (props) => { | const ActionBar = (props) => { | ||||||
|     const title = (props.selectedSubscription !== null) |     const title = (props.selectedSubscription !== null) | ||||||
|  | @ -26,7 +27,11 @@ const ActionBar = (props) => { | ||||||
|                 > |                 > | ||||||
|                     <MenuIcon /> |                     <MenuIcon /> | ||||||
|                 </IconButton> |                 </IconButton> | ||||||
|                 <img src="static/img/ntfy.svg" height="28" style={{ marginRight: '10px' }}/> |                 <Box component="img" src="static/img/ntfy.svg" sx={{ | ||||||
|  |                     display: { xs: 'none', sm: 'block' }, | ||||||
|  |                     marginRight: '10px', | ||||||
|  |                     height: '28px' | ||||||
|  |                 }}/> | ||||||
|                 <Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}> |                 <Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}> | ||||||
|                     {title} |                     {title} | ||||||
|                 </Typography> |                 </Typography> | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ import ActionBar from "./ActionBar"; | ||||||
| import Users from "../app/Users"; | import Users from "../app/Users"; | ||||||
| import notificationManager from "../app/NotificationManager"; | import notificationManager from "../app/NotificationManager"; | ||||||
| import NoTopics from "./NoTopics"; | import NoTopics from "./NoTopics"; | ||||||
|  | import Preferences from "./Preferences"; | ||||||
| 
 | 
 | ||||||
| // TODO subscribe dialog:
 | // TODO subscribe dialog:
 | ||||||
| //  - check/use existing user
 | //  - check/use existing user
 | ||||||
|  | @ -26,10 +27,15 @@ const App = () => { | ||||||
|     console.log(`[App] Rendering main view`); |     console.log(`[App] Rendering main view`); | ||||||
| 
 | 
 | ||||||
|     const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); |     const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); | ||||||
|  |     const [prefsOpen, setPrefsOpen] = useState(false); | ||||||
|     const [subscriptions, setSubscriptions] = useState(new Subscriptions()); |     const [subscriptions, setSubscriptions] = useState(new Subscriptions()); | ||||||
|     const [users, setUsers] = useState(new Users()); |     const [users, setUsers] = useState(new Users()); | ||||||
|     const [selectedSubscription, setSelectedSubscription] = useState(null); |     const [selectedSubscription, setSelectedSubscription] = useState(null); | ||||||
|     const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted()); |     const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted()); | ||||||
|  |     const handleSubscriptionClick = (subscriptionId) => { | ||||||
|  |         setSelectedSubscription(subscriptions.get(subscriptionId)); | ||||||
|  |         setPrefsOpen(false); | ||||||
|  |     } | ||||||
|     const handleSubscribeSubmit = (subscription, user) => { |     const handleSubscribeSubmit = (subscription, user) => { | ||||||
|         console.log(`[App] New subscription: ${subscription.id}`); |         console.log(`[App] New subscription: ${subscription.id}`); | ||||||
|         if (user !== null) { |         if (user !== null) { | ||||||
|  | @ -67,6 +73,10 @@ const App = () => { | ||||||
|             setNotificationsGranted(granted); |             setNotificationsGranted(granted); | ||||||
|         }) |         }) | ||||||
|     }; |     }; | ||||||
|  |     const handlePrefsClick = () => { | ||||||
|  |         setPrefsOpen(true); | ||||||
|  |         setSelectedSubscription(null); | ||||||
|  |     }; | ||||||
|     const poll = (subscription, user) => { |     const poll = (subscription, user) => { | ||||||
|         const since = subscription.last; |         const since = subscription.last; | ||||||
|         api.poll(subscription.baseUrl, subscription.topic, since, user) |         api.poll(subscription.baseUrl, subscription.topic, since, user) | ||||||
|  | @ -138,9 +148,11 @@ const App = () => { | ||||||
|                         selectedSubscription={selectedSubscription} |                         selectedSubscription={selectedSubscription} | ||||||
|                         mobileDrawerOpen={mobileDrawerOpen} |                         mobileDrawerOpen={mobileDrawerOpen} | ||||||
|                         notificationsGranted={notificationsGranted} |                         notificationsGranted={notificationsGranted} | ||||||
|  |                         prefsOpen={prefsOpen} | ||||||
|                         onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)} |                         onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)} | ||||||
|                         onSubscriptionClick={(subscriptionId) => setSelectedSubscription(subscriptions.get(subscriptionId))} |                         onSubscriptionClick={handleSubscriptionClick} | ||||||
|                         onSubscribeSubmit={handleSubscribeSubmit} |                         onSubscribeSubmit={handleSubscribeSubmit} | ||||||
|  |                         onPrefsClick={handlePrefsClick} | ||||||
|                         onRequestPermissionClick={handleRequestPermission} |                         onRequestPermissionClick={handleRequestPermission} | ||||||
|                     /> |                     /> | ||||||
|                 </Box> |                 </Box> | ||||||
|  | @ -155,18 +167,34 @@ const App = () => { | ||||||
|                         height: '100vh', |                         height: '100vh', | ||||||
|                         overflow: 'auto', |                         overflow: 'auto', | ||||||
|                         backgroundColor: (theme) => theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900] |                         backgroundColor: (theme) => theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900] | ||||||
|                 }}> |                     }} | ||||||
|  |                 > | ||||||
|                     <Toolbar/> |                     <Toolbar/> | ||||||
|                     {selectedSubscription !== null && |                     <MainContent | ||||||
|                         <Notifications |                         subscription={selectedSubscription} | ||||||
|                             subscription={selectedSubscription} |                         prefsOpen={prefsOpen} | ||||||
|                             onDeleteNotification={handleDeleteNotification} |                         onDeleteNotification={handleDeleteNotification} | ||||||
|                         />} |                     /> | ||||||
|                     {selectedSubscription == null && <NoTopics />} |  | ||||||
|                 </Box> |                 </Box> | ||||||
|             </Box> |             </Box> | ||||||
|         </ThemeProvider> |         </ThemeProvider> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const MainContent = (props) => { | ||||||
|  |     if (props.prefsOpen) { | ||||||
|  |         return <Preferences/>; | ||||||
|  |     } | ||||||
|  |     if (props.subscription !== null) { | ||||||
|  |         return ( | ||||||
|  |             <Notifications | ||||||
|  |                 subscription={props.subscription} | ||||||
|  |                 onDeleteNotification={props.onDeleteNotification} | ||||||
|  |             /> | ||||||
|  |         ); | ||||||
|  |     } else { | ||||||
|  |         return <NoTopics/>; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| export default App; | export default App; | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ import SubscribeDialog from "./SubscribeDialog"; | ||||||
| import {Alert, AlertTitle, ListSubheader} from "@mui/material"; | import {Alert, AlertTitle, ListSubheader} from "@mui/material"; | ||||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||||
|  | import Preferences from "./Preferences"; | ||||||
| 
 | 
 | ||||||
| const navWidth = 240; | const navWidth = 240; | ||||||
| 
 | 
 | ||||||
|  | @ -97,11 +98,15 @@ const NavList = (props) => { | ||||||
|                         <SubscriptionList |                         <SubscriptionList | ||||||
|                             subscriptions={props.subscriptions} |                             subscriptions={props.subscriptions} | ||||||
|                             selectedSubscription={props.selectedSubscription} |                             selectedSubscription={props.selectedSubscription} | ||||||
|  |                             prefsOpen={props.prefsOpen} | ||||||
|                             onSubscriptionClick={props.onSubscriptionClick} |                             onSubscriptionClick={props.onSubscriptionClick} | ||||||
|                         /> |                         /> | ||||||
|                         <Divider sx={{my: 1}}/> |                         <Divider sx={{my: 1}}/> | ||||||
|                     </>} |                     </>} | ||||||
|                 <ListItemButton> |                 <ListItemButton | ||||||
|  |                     onClick={props.onPrefsClick} | ||||||
|  |                     selected={props.prefsOpen} | ||||||
|  |                 > | ||||||
|                     <ListItemIcon> |                     <ListItemIcon> | ||||||
|                         <SettingsIcon/> |                         <SettingsIcon/> | ||||||
|                     </ListItemIcon> |                     </ListItemIcon> | ||||||
|  | @ -115,7 +120,7 @@ const NavList = (props) => { | ||||||
|                 </ListItemButton> |                 </ListItemButton> | ||||||
|             </List> |             </List> | ||||||
|             <SubscribeDialog |             <SubscribeDialog | ||||||
|                 key={subscribeDialogKey} // Resets dialog when canceled/closed
 |                 key={`subscribeDialog${subscribeDialogKey}`} // Resets dialog when canceled/closed
 | ||||||
|                 open={subscribeDialogOpen} |                 open={subscribeDialogOpen} | ||||||
|                 subscriptions={props.subscriptions} |                 subscriptions={props.subscriptions} | ||||||
|                 onCancel={handleSubscribeReset} |                 onCancel={handleSubscribeReset} | ||||||
|  | @ -132,7 +137,7 @@ const SubscriptionList = (props) => { | ||||||
|                 <ListItemButton |                 <ListItemButton | ||||||
|                     key={id} |                     key={id} | ||||||
|                     onClick={() => props.onSubscriptionClick(id)} |                     onClick={() => props.onSubscriptionClick(id)} | ||||||
|                     selected={props.selectedSubscription && props.selectedSubscription.id === id} |                     selected={props.selectedSubscription && !props.prefsOpen && props.selectedSubscription.id === id} | ||||||
|                 > |                 > | ||||||
|                     <ListItemIcon><ChatBubbleOutlineIcon /></ListItemIcon> |                     <ListItemIcon><ChatBubbleOutlineIcon /></ListItemIcon> | ||||||
|                     <ListItemText primary={subscription.shortUrl()}/> |                     <ListItemText primary={subscription.shortUrl()}/> | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								web/src/components/Preferences.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								web/src/components/Preferences.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | import * as React from 'react'; | ||||||
|  | import {CardContent} from "@mui/material"; | ||||||
|  | import Typography from "@mui/material/Typography"; | ||||||
|  | import Card from "@mui/material/Card"; | ||||||
|  | 
 | ||||||
|  | const Preferences = (props) => { | ||||||
|  |     return ( | ||||||
|  |         <> | ||||||
|  |             <Typography variant="h5"> | ||||||
|  |                 Manage users | ||||||
|  |             </Typography> | ||||||
|  |             <Card sx={{ minWidth: 275 }}> | ||||||
|  |                 <CardContent> | ||||||
|  |                     You may manage users for your protected topics here. Please note that since this is a client | ||||||
|  |                     application only, username and password are stored in the browser's local storage. | ||||||
|  |                 </CardContent> | ||||||
|  |             </Card> | ||||||
|  |         </> | ||||||
|  |     ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default Preferences; | ||||||
|  | @ -8,10 +8,10 @@ import DialogContent from '@mui/material/DialogContent'; | ||||||
| import DialogContentText from '@mui/material/DialogContentText'; | import DialogContentText from '@mui/material/DialogContentText'; | ||||||
| import DialogTitle from '@mui/material/DialogTitle'; | import DialogTitle from '@mui/material/DialogTitle'; | ||||||
| import Subscription from "../app/Subscription"; | import Subscription from "../app/Subscription"; | ||||||
| import {useMediaQuery} from "@mui/material"; | import {Autocomplete, Checkbox, FormControlLabel, useMediaQuery} from "@mui/material"; | ||||||
| import theme from "./theme"; | import theme from "./theme"; | ||||||
| import api from "../app/Api"; | import api from "../app/Api"; | ||||||
| import {topicUrl, validTopic} from "../app/utils"; | import {topicUrl, validTopic, validUrl} from "../app/utils"; | ||||||
| import useStyles from "./styles"; | import useStyles from "./styles"; | ||||||
| import User from "../app/User"; | import User from "../app/User"; | ||||||
| 
 | 
 | ||||||
|  | @ -19,18 +19,20 @@ const defaultBaseUrl = "http://127.0.0.1" | ||||||
| //const defaultBaseUrl = "https://ntfy.sh"
 | //const defaultBaseUrl = "https://ntfy.sh"
 | ||||||
| 
 | 
 | ||||||
| const SubscribeDialog = (props) => { | const SubscribeDialog = (props) => { | ||||||
|     const [baseUrl, setBaseUrl] = useState(defaultBaseUrl); // FIXME
 |     const [baseUrl, setBaseUrl] = useState(""); | ||||||
|     const [topic, setTopic] = useState(""); |     const [topic, setTopic] = useState(""); | ||||||
|     const [showLoginPage, setShowLoginPage] = useState(false); |     const [showLoginPage, setShowLoginPage] = useState(false); | ||||||
|     const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); |     const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); | ||||||
|     const handleSuccess = (baseUrl, topic, user) => { |     const handleSuccess = (user) => { | ||||||
|         const subscription = new Subscription(baseUrl, topic); |         const actualBaseUrl = (baseUrl) ? baseUrl : defaultBaseUrl; // FIXME
 | ||||||
|  |         const subscription = new Subscription(actualBaseUrl, topic); | ||||||
|         props.onSuccess(subscription, user); |         props.onSuccess(subscription, user); | ||||||
|     } |     } | ||||||
|     return ( |     return ( | ||||||
|         <Dialog open={props.open} onClose={props.onClose} fullScreen={fullScreen}> |         <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}> | ||||||
|             {!showLoginPage && <SubscribePage |             {!showLoginPage && <SubscribePage | ||||||
|                 baseUrl={baseUrl} |                 baseUrl={baseUrl} | ||||||
|  |                 setBaseUrl={setBaseUrl} | ||||||
|                 topic={topic} |                 topic={topic} | ||||||
|                 setTopic={setTopic} |                 setTopic={setTopic} | ||||||
|                 subscriptions={props.subscriptions} |                 subscriptions={props.subscriptions} | ||||||
|  | @ -49,8 +51,12 @@ const SubscribeDialog = (props) => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const SubscribePage = (props) => { | const SubscribePage = (props) => { | ||||||
|     const baseUrl = props.baseUrl; |     const [anotherServerVisible, setAnotherServerVisible] = useState(false); | ||||||
|  |     const baseUrl = (anotherServerVisible) ? props.baseUrl : defaultBaseUrl; | ||||||
|     const topic = props.topic; |     const topic = props.topic; | ||||||
|  |     const existingTopicUrls = props.subscriptions.map((id, s) => s.url()); | ||||||
|  |     const existingBaseUrls = Array.from(new Set(["https://ntfy.sh", ...props.subscriptions.map((id, s) => s.baseUrl)])) | ||||||
|  |         .filter(s => s !== defaultBaseUrl); | ||||||
|     const handleSubscribe = async () => { |     const handleSubscribe = async () => { | ||||||
|         const success = await api.auth(baseUrl, topic, null); |         const success = await api.auth(baseUrl, topic, null); | ||||||
|         if (!success) { |         if (!success) { | ||||||
|  | @ -59,10 +65,21 @@ const SubscribePage = (props) => { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for anonymous user`); |         console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for anonymous user`); | ||||||
|         props.onSuccess(baseUrl, topic, null); |         props.onSuccess(null); | ||||||
|     }; |     }; | ||||||
|     const existingTopicUrls = props.subscriptions.map((id, s) => s.url()); |     const handleUseAnotherChanged = (e) => { | ||||||
|     const subscribeButtonEnabled = validTopic(props.topic) && !existingTopicUrls.includes(topicUrl(baseUrl, topic)); |         props.setBaseUrl(""); | ||||||
|  |         setAnotherServerVisible(e.target.checked); | ||||||
|  |     }; | ||||||
|  |     const subscribeButtonEnabled = (() => { | ||||||
|  |         if (anotherServerVisible) { | ||||||
|  |             const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic)); | ||||||
|  |             return validTopic(topic) && validUrl(baseUrl) && !isExistingTopicUrl; | ||||||
|  |         } else { | ||||||
|  |             const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(defaultBaseUrl, topic)); // FIXME
 | ||||||
|  |             return validTopic(topic) && !isExistingTopicUrl; | ||||||
|  |         } | ||||||
|  |     })(); | ||||||
|     return ( |     return ( | ||||||
|         <> |         <> | ||||||
|             <DialogTitle>Subscribe to topic</DialogTitle> |             <DialogTitle>Subscribe to topic</DialogTitle> | ||||||
|  | @ -75,13 +92,27 @@ const SubscribePage = (props) => { | ||||||
|                     autoFocus |                     autoFocus | ||||||
|                     margin="dense" |                     margin="dense" | ||||||
|                     id="topic" |                     id="topic" | ||||||
|                     label="Topic name, e.g. phil_alerts" |                     placeholder="Topic name, e.g. phil_alerts" | ||||||
|                     value={props.topic} |                     value={props.topic} | ||||||
|                     onChange={ev => props.setTopic(ev.target.value)} |                     onChange={ev => props.setTopic(ev.target.value)} | ||||||
|                     type="text" |                     type="text" | ||||||
|                     fullWidth |                     fullWidth | ||||||
|                     variant="standard" |                     variant="standard" | ||||||
|                 /> |                 /> | ||||||
|  |                 <FormControlLabel | ||||||
|  |                     sx={{pt: 1}} | ||||||
|  |                     control={<Checkbox onChange={handleUseAnotherChanged}/>} | ||||||
|  |                     label="Use another server" /> | ||||||
|  |                 {anotherServerVisible && <Autocomplete | ||||||
|  |                     freeSolo | ||||||
|  |                     options={existingBaseUrls} | ||||||
|  |                     sx={{ maxWidth: 400 }} | ||||||
|  |                     inputValue={props.baseUrl} | ||||||
|  |                     onInputChange={(ev, newVal) => props.setBaseUrl(newVal)} | ||||||
|  |                     renderInput={ (params) => | ||||||
|  |                         <TextField {...params} placeholder={defaultBaseUrl} variant="standard"/> | ||||||
|  |                     } | ||||||
|  |                 />} | ||||||
|             </DialogContent> |             </DialogContent> | ||||||
|             <DialogActions> |             <DialogActions> | ||||||
|                 <Button onClick={props.onCancel}>Cancel</Button> |                 <Button onClick={props.onCancel}>Cancel</Button> | ||||||
|  | @ -96,7 +127,7 @@ const LoginPage = (props) => { | ||||||
|     const [username, setUsername] = useState(""); |     const [username, setUsername] = useState(""); | ||||||
|     const [password, setPassword] = useState(""); |     const [password, setPassword] = useState(""); | ||||||
|     const [errorText, setErrorText] = useState(""); |     const [errorText, setErrorText] = useState(""); | ||||||
|     const baseUrl = props.baseUrl; |     const baseUrl = (props.baseUrl) ? props.baseUrl : defaultBaseUrl; | ||||||
|     const topic = props.topic; |     const topic = props.topic; | ||||||
|     const handleLogin = async () => { |     const handleLogin = async () => { | ||||||
|         const user = new User(baseUrl, username, password); |         const user = new User(baseUrl, username, password); | ||||||
|  | @ -107,7 +138,7 @@ const LoginPage = (props) => { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`); |         console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`); | ||||||
|         props.onSuccess(baseUrl, topic, user); |         props.onSuccess(user); | ||||||
|     }; |     }; | ||||||
|     return ( |     return ( | ||||||
|         <> |         <> | ||||||
|  |  | ||||||
|  | @ -16,6 +16,15 @@ const theme = createTheme({ | ||||||
|       main: '#444', |       main: '#444', | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|  |   components: { | ||||||
|  |     MuiListItemIcon: { | ||||||
|  |       styleOverrides: { | ||||||
|  |         root: { | ||||||
|  |           minWidth: '36px', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default theme; | export default theme; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue