1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2025-05-29 09:55:35 +02:00

WIP: React

This commit is contained in:
Philipp Heckel 2022-02-18 09:49:51 -05:00
parent ce9e9f3e0d
commit 4c4e689af4
20 changed files with 31671 additions and 0 deletions

82
web/src/App.js Normal file
View file

@ -0,0 +1,82 @@
import * as React from 'react';
import Container from '@mui/material/Container';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import ProTip from './ProTip';
function Copyright() {
return (
<Typography variant="body2" color="text.secondary" align="center">
{'Copyright © '}
<Link color="inherit" href="https://mui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
const shortUrl = (url) => url.replaceAll(/https?:\/\//g, "");
const shortTopicUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic))
function SubscriptionList(props) {
return (
<div className="subscriptionList">
{props.subscriptions.map(subscription => <SubscriptionItem key={topicUrl(subscription.base_url, subscription.topic)} {...subscription}/>)}
</div>
);
}
function SubscriptionItem(props) {
return (
<div>
<div>{shortTopicUrl(props.base_url, props.topic)}</div>
</div>
);
}
function NotificationList(props) {
return (
<div className="notificationList">
{props.notifications.map(notification => <NotificationItem key={notification.id} {...notification}/>)}
<div className="date">{props.timestamp}</div>
<div className="message">{props.message}</div>
</div>
);
}
function NotificationItem(props) {
return (
<div>
<div className="date">{props.time}</div>
<div className="message">{props.message}</div>
</div>
);
}
export default function App() {
const subscriptions = [
{base_url: "https://ntfy.sh", topic: "mytopic"},
{base_url: "https://ntfy.sh", topic: "phils_alerts"},
];
const notifications = [
{id: "qGrfmhp3vK", times: 1645193395, message: "Message 1"},
{id: "m4YYjfxwyT", times: 1645193428, message: "Message 2"}
];
return (
<Container maxWidth="sm">
<Box sx={{my: 4}}>
<Typography variant="h4" component="h1" gutterBottom>
ntfy
</Typography>
<SubscriptionList subscriptions={subscriptions}/>
<NotificationList notifications={notifications}/>
<ProTip/>
<Copyright/>
</Box>
</Container>
);
}

71
web/src/Chart.js vendored Normal file
View file

@ -0,0 +1,71 @@
import * as React from 'react';
import { useTheme } from '@mui/material/styles';
import { LineChart, Line, XAxis, YAxis, Label, ResponsiveContainer } from 'recharts';
import Title from './Title';
// Generate Sales Data
function createData(time, amount) {
return { time, amount };
}
const data = [
createData('00:00', 0),
createData('03:00', 300),
createData('06:00', 600),
createData('09:00', 800),
createData('12:00', 1500),
createData('15:00', 2000),
createData('18:00', 2400),
createData('21:00', 2400),
createData('24:00', undefined),
];
export default function Chart() {
const theme = useTheme();
return (
<React.Fragment>
<Title>Today</Title>
<ResponsiveContainer>
<LineChart
data={data}
margin={{
top: 16,
right: 16,
bottom: 0,
left: 24,
}}
>
<XAxis
dataKey="time"
stroke={theme.palette.text.secondary}
style={theme.typography.body2}
/>
<YAxis
stroke={theme.palette.text.secondary}
style={theme.typography.body2}
>
<Label
angle={270}
position="left"
style={{
textAnchor: 'middle',
fill: theme.palette.text.primary,
...theme.typography.body1,
}}
>
Sales ($)
</Label>
</YAxis>
<Line
isAnimationActive={false}
type="monotone"
dataKey="amount"
stroke={theme.palette.primary.main}
dot={false}
/>
</LineChart>
</ResponsiveContainer>
</React.Fragment>
);
}

208
web/src/Dashboard.js Normal file
View file

@ -0,0 +1,208 @@
import * as React from 'react';
import { styled, createTheme, ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import MuiDrawer from '@mui/material/Drawer';
import Box from '@mui/material/Box';
import MuiAppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import List from '@mui/material/List';
import Typography from '@mui/material/Typography';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import Badge from '@mui/material/Badge';
import Container from '@mui/material/Container';
import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper';
import Link from '@mui/material/Link';
import MenuIcon from '@mui/icons-material/Menu';
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
import NotificationsIcon from '@mui/icons-material/Notifications';
import { mainListItems, secondaryListItems } from './listItems';
import Chart from './Chart';
import Deposits from './Deposits';
import Orders from './Orders';
function Copyright(props) {
return (
<Typography variant="body2" color="text.secondary" align="center" {...props}>
{'Copyright © '}
<Link color="inherit" href="https://mui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
const drawerWidth = 240;
const AppBar = styled(MuiAppBar, {
shouldForwardProp: (prop) => prop !== 'open',
})(({ theme, open }) => ({
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
...(open && {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
}),
}));
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
({ theme, open }) => ({
'& .MuiDrawer-paper': {
position: 'relative',
whiteSpace: 'nowrap',
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
boxSizing: 'border-box',
...(!open && {
overflowX: 'hidden',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: theme.spacing(7),
[theme.breakpoints.up('sm')]: {
width: theme.spacing(9),
},
}),
},
}),
);
const mdTheme = createTheme();
function DashboardContent() {
const [open, setOpen] = React.useState(true);
const toggleDrawer = () => {
setOpen(!open);
};
return (
<ThemeProvider theme={mdTheme}>
<Box sx={{ display: 'flex' }}>
<CssBaseline />
<AppBar position="absolute" open={open}>
<Toolbar
sx={{
pr: '24px', // keep right padding when drawer closed
}}
>
<IconButton
edge="start"
color="inherit"
aria-label="open drawer"
onClick={toggleDrawer}
sx={{
marginRight: '36px',
...(open && { display: 'none' }),
}}
>
<MenuIcon />
</IconButton>
<Typography
component="h1"
variant="h6"
color="inherit"
noWrap
sx={{ flexGrow: 1 }}
>
Dashboard
</Typography>
<IconButton color="inherit">
<Badge badgeContent={4} color="secondary">
<NotificationsIcon />
</Badge>
</IconButton>
</Toolbar>
</AppBar>
<Drawer variant="permanent" open={open}>
<Toolbar
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
px: [1],
}}
>
<IconButton onClick={toggleDrawer}>
<ChevronLeftIcon />
</IconButton>
</Toolbar>
<Divider />
<List component="nav">
{mainListItems}
<Divider sx={{ my: 1 }} />
{secondaryListItems}
</List>
</Drawer>
<Box
component="main"
sx={{
backgroundColor: (theme) =>
theme.palette.mode === 'light'
? theme.palette.grey[100]
: theme.palette.grey[900],
flexGrow: 1,
height: '100vh',
overflow: 'auto',
}}
>
<Toolbar />
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
<Grid container spacing={3}>
{/* Chart */}
<Grid item xs={12} md={8} lg={9}>
<Paper
sx={{
p: 2,
display: 'flex',
flexDirection: 'column',
height: 240,
}}
>
<Chart />
</Paper>
</Grid>
{/* Recent Deposits */}
<Grid item xs={12} md={4} lg={3}>
<Paper
sx={{
p: 2,
display: 'flex',
flexDirection: 'column',
height: 240,
}}
>
<Deposits />
</Paper>
</Grid>
{/* Recent Orders */}
<Grid item xs={12}>
<Paper sx={{ p: 2, display: 'flex', flexDirection: 'column' }}>
<Orders />
</Paper>
</Grid>
</Grid>
<Copyright sx={{ pt: 4 }} />
</Container>
</Box>
</Box>
</ThemeProvider>
);
}
export default function Dashboard() {
return <DashboardContent />;
}

27
web/src/Deposits.js Normal file
View file

@ -0,0 +1,27 @@
import * as React from 'react';
import Link from '@mui/material/Link';
import Typography from '@mui/material/Typography';
import Title from './Title';
function preventDefault(event) {
event.preventDefault();
}
export default function Deposits() {
return (
<React.Fragment>
<Title>Recent Deposits</Title>
<Typography component="p" variant="h4">
$3,024.00
</Typography>
<Typography color="text.secondary" sx={{ flex: 1 }}>
on 15 March, 2019
</Typography>
<div>
<Link color="primary" href="#" onClick={preventDefault}>
View balance
</Link>
</div>
</React.Fragment>
);
}

86
web/src/Orders.js Normal file
View file

@ -0,0 +1,86 @@
import * as React from 'react';
import Link from '@mui/material/Link';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Title from './Title';
// Generate Order Data
function createData(id, date, name, shipTo, paymentMethod, amount) {
return { id, date, name, shipTo, paymentMethod, amount };
}
const rows = [
createData(
0,
'16 Mar, 2019',
'Elvis Presley',
'Tupelo, MS',
'VISA ⠀•••• 3719',
312.44,
),
createData(
1,
'16 Mar, 2019',
'Paul McCartney',
'London, UK',
'VISA ⠀•••• 2574',
866.99,
),
createData(2, '16 Mar, 2019', 'Tom Scholz', 'Boston, MA', 'MC ⠀•••• 1253', 100.81),
createData(
3,
'16 Mar, 2019',
'Michael Jackson',
'Gary, IN',
'AMEX ⠀•••• 2000',
654.39,
),
createData(
4,
'15 Mar, 2019',
'Bruce Springsteen',
'Long Branch, NJ',
'VISA ⠀•••• 5919',
212.79,
),
];
function preventDefault(event) {
event.preventDefault();
}
export default function Orders() {
return (
<React.Fragment>
<Title>Recent Orders</Title>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Date</TableCell>
<TableCell>Name</TableCell>
<TableCell>Ship To</TableCell>
<TableCell>Payment Method</TableCell>
<TableCell align="right">Sale Amount</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row.id}>
<TableCell>{row.date}</TableCell>
<TableCell>{row.name}</TableCell>
<TableCell>{row.shipTo}</TableCell>
<TableCell>{row.paymentMethod}</TableCell>
<TableCell align="right">{`$${row.amount}`}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Link color="primary" href="#" onClick={preventDefault} sx={{ mt: 3 }}>
See more orders
</Link>
</React.Fragment>
);
}

22
web/src/ProTip.js Normal file
View file

@ -0,0 +1,22 @@
import * as React from 'react';
import Link from '@mui/material/Link';
import SvgIcon from '@mui/material/SvgIcon';
import Typography from '@mui/material/Typography';
function LightBulbIcon(props) {
return (
<SvgIcon {...props}>
<path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z" />
</SvgIcon>
);
}
export default function ProTip() {
return (
<Typography sx={{ mt: 6, mb: 3 }} color="text.secondary">
<LightBulbIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Pro tip: See more <Link href="https://mui.com/getting-started/templates/">templates</Link> on
the MUI documentation.
</Typography>
);
}

17
web/src/Title.js Normal file
View file

@ -0,0 +1,17 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Typography from '@mui/material/Typography';
function Title(props) {
return (
<Typography component="h2" variant="h6" color="primary" gutterBottom>
{props.children}
</Typography>
);
}
Title.propTypes = {
children: PropTypes.node,
};
export default Title;

16
web/src/index.js Normal file
View file

@ -0,0 +1,16 @@
import * as React from 'react';
import ReactDOM from 'react-dom';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import App from './App';
import theme from './theme';
import Dashboard from "./Dashboard";
ReactDOM.render(
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<App />
</ThemeProvider>,
document.querySelector('#root'),
);

72
web/src/listItems.js Normal file
View file

@ -0,0 +1,72 @@
import * as React from 'react';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import ListSubheader from '@mui/material/ListSubheader';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import PeopleIcon from '@mui/icons-material/People';
import BarChartIcon from '@mui/icons-material/BarChart';
import LayersIcon from '@mui/icons-material/Layers';
import AssignmentIcon from '@mui/icons-material/Assignment';
export const mainListItems = (
<React.Fragment>
<ListItemButton>
<ListItemIcon>
<DashboardIcon />
</ListItemIcon>
<ListItemText primary="Dashboard" />
</ListItemButton>
<ListItemButton>
<ListItemIcon>
<ShoppingCartIcon />
</ListItemIcon>
<ListItemText primary="Orders" />
</ListItemButton>
<ListItemButton>
<ListItemIcon>
<PeopleIcon />
</ListItemIcon>
<ListItemText primary="Customers" />
</ListItemButton>
<ListItemButton>
<ListItemIcon>
<BarChartIcon />
</ListItemIcon>
<ListItemText primary="Reports" />
</ListItemButton>
<ListItemButton>
<ListItemIcon>
<LayersIcon />
</ListItemIcon>
<ListItemText primary="Integrations" />
</ListItemButton>
</React.Fragment>
);
export const secondaryListItems = (
<React.Fragment>
<ListSubheader component="div" inset>
Saved reports
</ListSubheader>
<ListItemButton>
<ListItemIcon>
<AssignmentIcon />
</ListItemIcon>
<ListItemText primary="Current month" />
</ListItemButton>
<ListItemButton>
<ListItemIcon>
<AssignmentIcon />
</ListItemIcon>
<ListItemText primary="Last quarter" />
</ListItemButton>
<ListItemButton>
<ListItemIcon>
<AssignmentIcon />
</ListItemIcon>
<ListItemText primary="Year-end sale" />
</ListItemButton>
</React.Fragment>
);

1
web/src/react-app-env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="react-scripts" />

19
web/src/theme.js Normal file
View file

@ -0,0 +1,19 @@
import { red } from '@mui/material/colors';
import { createTheme } from '@mui/material/styles';
// A custom theme for this app
const theme = createTheme({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
error: {
main: red.A400,
},
},
});
export default theme;