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

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ server/docs/
tools/fbsend/fbsend
playground/
*.iml
node_modules/

27
web/.gitignore vendored Normal file
View file

@ -0,0 +1,27 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# IDEs and editors
/.idea
/.vscode
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

44
web/README.md Normal file
View file

@ -0,0 +1,44 @@
# Create React App example
## How to use
Download the example [or clone the repo](https://github.com/mui/material-ui):
<!-- #default-branch-switch -->
```sh
curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/create-react-app
cd create-react-app
```
Install it and run:
```sh
npm install
npm start
```
or:
<!-- #default-branch-switch -->
[![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/mui/material-ui/tree/master/examples/create-react-app)
<!-- #default-branch-switch -->
[![Edit on StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/mui/material-ui/tree/master/examples/create-react-app)
## The idea behind the example
<!-- #default-branch-switch -->
This example demonstrates how you can use [Create React App](https://github.com/facebookincubator/create-react-app).
It includes `@mui/material` and its peer dependencies, including `emotion`, the default style engine in MUI v5.
If you prefer, you can [use styled-components instead](https://mui.com/guides/interoperability/#styled-components).
## What's next?
<!-- #default-branch-switch -->
You now have a working example project.
You can head back to the documentation, continuing browsing it from the [templates](https://mui.com/getting-started/templates/) section.

30864
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

34
web/package.json Normal file
View file

@ -0,0 +1,34 @@
{
"name": "create-react-app",
"version": "5.0.0",
"private": true,
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"dependencies": {
"@emotion/react": "latest",
"@emotion/styled": "latest",
"@mui/icons-material": "^5.4.2",
"@mui/material": "latest",
"check-dependencies": "^1.1.0",
"react": "latest",
"react-dom": "latest",
"react-scripts": "^3.0.1",
"recharts": "^2.1.9"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

40
web/public/index.html Normal file
View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="initial-scale=1, width=device-width" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>My page</title>
<!-- Fonts to support Material Design -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

15
web/public/manifest.json Normal file
View file

@ -0,0 +1,15 @@
{
"short_name": "Your Orders",
"name": "Your Orders",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

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;

25
web/tsconfig.json Normal file
View file

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"include": [
"src"
]
}