bananas-commerce-admin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +25 -0
  2. package/dist/cjs/Admin.js +47 -0
  3. package/dist/cjs/App.js +49 -0
  4. package/dist/cjs/api.js +225 -0
  5. package/dist/cjs/components/Branding.js +41 -0
  6. package/dist/cjs/components/Hamburger.js +40 -0
  7. package/dist/cjs/components/Link.js +21 -0
  8. package/dist/cjs/components/Logo.js +25 -0
  9. package/dist/cjs/components/NavBar.js +101 -0
  10. package/dist/cjs/components/NavBarItem.js +42 -0
  11. package/dist/cjs/components/NavBarRoutes.js +47 -0
  12. package/dist/cjs/components/ProgressBar.js +14 -0
  13. package/dist/cjs/components/User.js +71 -0
  14. package/dist/cjs/containers/Content.js +16 -0
  15. package/dist/cjs/containers/ErrorScreen.js +35 -0
  16. package/dist/cjs/containers/LoadingScreen.js +84 -0
  17. package/dist/cjs/containers/PageErrorBoundary.js +43 -0
  18. package/dist/cjs/containers/PageLoader.js +123 -0
  19. package/dist/cjs/contexts/ApiContext.js +105 -0
  20. package/dist/cjs/contexts/I18nContext.js +109 -0
  21. package/dist/cjs/contexts/RouterContext.js +99 -0
  22. package/dist/cjs/contexts/UserContext.js +144 -0
  23. package/dist/cjs/extensions/bananas/components/PasswordChangeForm.js +85 -0
  24. package/dist/cjs/extensions/bananas/index.js +54 -0
  25. package/dist/cjs/extensions/bananas/pages/me/list.js +20 -0
  26. package/dist/cjs/extensions/pos/components/PurchaseRow.js +32 -0
  27. package/dist/cjs/extensions/pos/components/ReceiptCard.js +86 -0
  28. package/dist/cjs/extensions/pos/components/ReceiptLine.js +29 -0
  29. package/dist/cjs/extensions/pos/index.js +22 -0
  30. package/dist/cjs/extensions/pos/pages/purchase/detail.js +13 -0
  31. package/dist/cjs/extensions/pos/pages/purchase/list.js +34 -0
  32. package/dist/cjs/extensions/pos/types/purchase.js +2 -0
  33. package/dist/cjs/extensions/pos/types/receipt.js +2 -0
  34. package/dist/cjs/forms/LoginForm.js +63 -0
  35. package/dist/cjs/hooks/useAsyncError.js +15 -0
  36. package/dist/cjs/hooks/useLocalStorage.js +47 -0
  37. package/dist/cjs/index.js +40 -0
  38. package/dist/cjs/pages/DashboardPage.js +10 -0
  39. package/dist/cjs/pages/LoginPage.js +31 -0
  40. package/dist/cjs/router/Router.js +35 -0
  41. package/dist/cjs/router/routes.js +57 -0
  42. package/dist/cjs/types/index.js +2 -0
  43. package/dist/cjs/util/get_cookie.js +10 -0
  44. package/dist/cjs/util/index.js +62 -0
  45. package/dist/cjs/util/select_styles.js +38 -0
  46. package/dist/esm/Admin.js +42 -0
  47. package/dist/esm/App.js +44 -0
  48. package/dist/esm/api.js +219 -0
  49. package/dist/esm/components/Branding.js +36 -0
  50. package/dist/esm/components/Hamburger.js +35 -0
  51. package/dist/esm/components/Link.js +16 -0
  52. package/dist/esm/components/Logo.js +20 -0
  53. package/dist/esm/components/NavBar.js +73 -0
  54. package/dist/esm/components/NavBarItem.js +37 -0
  55. package/dist/esm/components/NavBarRoutes.js +42 -0
  56. package/dist/esm/components/ProgressBar.js +9 -0
  57. package/dist/esm/components/User.js +66 -0
  58. package/dist/esm/containers/Content.js +11 -0
  59. package/dist/esm/containers/ErrorScreen.js +30 -0
  60. package/dist/esm/containers/LoadingScreen.js +79 -0
  61. package/dist/esm/containers/PageErrorBoundary.js +38 -0
  62. package/dist/esm/containers/PageLoader.js +117 -0
  63. package/dist/esm/contexts/ApiContext.js +77 -0
  64. package/dist/esm/contexts/I18nContext.js +77 -0
  65. package/dist/esm/contexts/RouterContext.js +71 -0
  66. package/dist/esm/contexts/UserContext.js +113 -0
  67. package/dist/esm/extensions/bananas/components/PasswordChangeForm.js +80 -0
  68. package/dist/esm/extensions/bananas/index.js +48 -0
  69. package/dist/esm/extensions/bananas/pages/me/list.js +15 -0
  70. package/dist/esm/extensions/pos/components/PurchaseRow.js +25 -0
  71. package/dist/esm/extensions/pos/components/ReceiptCard.js +56 -0
  72. package/dist/esm/extensions/pos/components/ReceiptLine.js +22 -0
  73. package/dist/esm/extensions/pos/index.js +16 -0
  74. package/dist/esm/extensions/pos/pages/purchase/detail.js +8 -0
  75. package/dist/esm/extensions/pos/pages/purchase/list.js +29 -0
  76. package/dist/esm/extensions/pos/types/purchase.js +1 -0
  77. package/dist/esm/extensions/pos/types/receipt.js +1 -0
  78. package/dist/esm/forms/LoginForm.js +58 -0
  79. package/dist/esm/hooks/useAsyncError.js +9 -0
  80. package/dist/esm/hooks/useLocalStorage.js +41 -0
  81. package/dist/esm/index.js +14 -0
  82. package/dist/esm/pages/DashboardPage.js +5 -0
  83. package/dist/esm/pages/LoginPage.js +26 -0
  84. package/dist/esm/router/Router.js +28 -0
  85. package/dist/esm/router/routes.js +49 -0
  86. package/dist/esm/types/index.js +1 -0
  87. package/dist/esm/util/get_cookie.js +6 -0
  88. package/dist/esm/util/index.js +54 -0
  89. package/dist/esm/util/select_styles.js +34 -0
  90. package/dist/types/Admin.d.ts +13 -0
  91. package/dist/types/App.d.ts +13 -0
  92. package/dist/types/api.d.ts +20 -0
  93. package/dist/types/components/Branding.d.ts +14 -0
  94. package/dist/types/components/Hamburger.d.ts +8 -0
  95. package/dist/types/components/Link.d.ts +9 -0
  96. package/dist/types/components/Logo.d.ts +7 -0
  97. package/dist/types/components/NavBar.d.ts +11 -0
  98. package/dist/types/components/NavBarItem.d.ts +12 -0
  99. package/dist/types/components/NavBarRoutes.d.ts +7 -0
  100. package/dist/types/components/ProgressBar.d.ts +7 -0
  101. package/dist/types/components/User.d.ts +824 -0
  102. package/dist/types/containers/Content.d.ts +3 -0
  103. package/dist/types/containers/ErrorScreen.d.ts +7 -0
  104. package/dist/types/containers/LoadingScreen.d.ts +50 -0
  105. package/dist/types/containers/PageErrorBoundary.d.ts +16 -0
  106. package/dist/types/containers/PageLoader.d.ts +13 -0
  107. package/dist/types/contexts/ApiContext.d.ts +12 -0
  108. package/dist/types/contexts/I18nContext.d.ts +10 -0
  109. package/dist/types/contexts/RouterContext.d.ts +24 -0
  110. package/dist/types/contexts/UserContext.d.ts +20 -0
  111. package/dist/types/extensions/bananas/components/PasswordChangeForm.d.ts +3 -0
  112. package/dist/types/extensions/bananas/index.d.ts +2 -0
  113. package/dist/types/extensions/bananas/pages/me/list.d.ts +6 -0
  114. package/dist/types/extensions/pos/components/PurchaseRow.d.ts +7 -0
  115. package/dist/types/extensions/pos/components/ReceiptCard.d.ts +7 -0
  116. package/dist/types/extensions/pos/components/ReceiptLine.d.ts +7 -0
  117. package/dist/types/extensions/pos/index.d.ts +2 -0
  118. package/dist/types/extensions/pos/pages/purchase/detail.d.ts +7 -0
  119. package/dist/types/extensions/pos/pages/purchase/list.d.ts +9 -0
  120. package/dist/types/extensions/pos/types/purchase.d.ts +18 -0
  121. package/dist/types/extensions/pos/types/receipt.d.ts +34 -0
  122. package/dist/types/forms/LoginForm.d.ts +3 -0
  123. package/dist/types/hooks/useAsyncError.d.ts +1 -0
  124. package/dist/types/hooks/useLocalStorage.d.ts +2 -0
  125. package/dist/types/index.d.ts +14 -0
  126. package/dist/types/pages/DashboardPage.d.ts +5 -0
  127. package/dist/types/pages/LoginPage.d.ts +9 -0
  128. package/dist/types/router/Router.d.ts +13 -0
  129. package/dist/types/router/routes.d.ts +20 -0
  130. package/dist/types/types/index.d.ts +6 -0
  131. package/dist/types/util/get_cookie.d.ts +1 -0
  132. package/dist/types/util/index.d.ts +8 -0
  133. package/dist/types/util/select_styles.d.ts +3 -0
  134. package/example/Dockerfile +27 -0
  135. package/example/docker-compose.yml +7 -0
  136. package/example/index.html +13 -0
  137. package/example/index.tsx +21 -0
  138. package/example/package-lock.json +13167 -0
  139. package/example/package.json +52 -0
  140. package/example/pages/.gitkeep +0 -0
  141. package/example/webpack.config.js +67 -0
  142. package/package.json +52 -0
  143. package/src/Admin.tsx +94 -0
  144. package/src/App.tsx +43 -0
  145. package/src/api.ts +202 -0
  146. package/src/components/Branding.tsx +79 -0
  147. package/src/components/Hamburger.tsx +41 -0
  148. package/src/components/Link.tsx +23 -0
  149. package/src/components/Logo.tsx +57 -0
  150. package/src/components/NavBar.tsx +115 -0
  151. package/src/components/NavBarItem.tsx +73 -0
  152. package/src/components/NavBarRoutes.tsx +67 -0
  153. package/src/components/ProgressBar.tsx +30 -0
  154. package/src/components/User.tsx +97 -0
  155. package/src/containers/Content.tsx +18 -0
  156. package/src/containers/ErrorScreen.tsx +64 -0
  157. package/src/containers/LoadingScreen.tsx +135 -0
  158. package/src/containers/PageErrorBoundary.tsx +32 -0
  159. package/src/containers/PageLoader.tsx +64 -0
  160. package/src/contexts/ApiContext.tsx +55 -0
  161. package/src/contexts/I18nContext.tsx +55 -0
  162. package/src/contexts/RouterContext.tsx +127 -0
  163. package/src/contexts/UserContext.tsx +99 -0
  164. package/src/extensions/bananas/components/PasswordChangeForm.tsx +138 -0
  165. package/src/extensions/bananas/index.ts +14 -0
  166. package/src/extensions/bananas/pages/me/list.tsx +31 -0
  167. package/src/extensions/pos/components/PurchaseRow.tsx +42 -0
  168. package/src/extensions/pos/components/ReceiptCard.tsx +101 -0
  169. package/src/extensions/pos/components/ReceiptLine.tsx +51 -0
  170. package/src/extensions/pos/index.tsx +20 -0
  171. package/src/extensions/pos/pages/purchase/detail.tsx +22 -0
  172. package/src/extensions/pos/pages/purchase/list.tsx +56 -0
  173. package/src/extensions/pos/types/purchase.ts +20 -0
  174. package/src/extensions/pos/types/receipt.ts +36 -0
  175. package/src/forms/LoginForm.tsx +99 -0
  176. package/src/hooks/useAsyncError.ts +13 -0
  177. package/src/hooks/useLocalStorage.ts +42 -0
  178. package/src/index.ts +18 -0
  179. package/src/pages/DashboardPage.tsx +9 -0
  180. package/src/pages/LoginPage.tsx +50 -0
  181. package/src/router/Router.tsx +63 -0
  182. package/src/router/routes.ts +67 -0
  183. package/src/types/index.ts +4 -0
  184. package/src/types/swagger-client.d.ts +1 -0
  185. package/src/util/get_cookie.ts +6 -0
  186. package/src/util/index.ts +63 -0
  187. package/src/util/select_styles.ts +29 -0
  188. package/tsconfig.json +38 -0
@@ -0,0 +1,57 @@
1
+ import React from "react";
2
+
3
+ import { LogoType } from "../types";
4
+
5
+ interface LogoProps {
6
+ src?: LogoType;
7
+ }
8
+
9
+ const Logo: React.FC<LogoProps> = ({ src }) => {
10
+ if (src === true) {
11
+ return (
12
+ <svg
13
+ version="1.1"
14
+ id="bananas"
15
+ x="0px"
16
+ y="0px"
17
+ width="32px"
18
+ height="32px"
19
+ viewBox="0 0 927.611 927.611"
20
+ xmlSpace="preserve"
21
+ xmlns="http://www.w3.org/2000/svg"
22
+ xmlnsXlink="http://www.w3.org/1999/xlink"
23
+ >
24
+ <g fill="#FFFFFF">
25
+ <path d="M158.56,618.97l2.4-0.7c97-26.199,181.6-59.8,251.2-99.8c94.5-54.3,159.4-119.5,193-193.799l16-35.4l-28.699,26.2
26
+ c-57,52.1-134.801,91-231.4,115.8c-81.9,21-176,31.7-279.8,31.7c-5.4,0-17.4-0.1-24.6-0.2l-16.9-0.8c-13.3-0.6-25.4,7.6-29.8,20.2
27
+ l-6.6,19.1c-3.9,11.301-0.7,23.9,8.2,32c7.4,6.7,14.9,13.2,14.9,13.2s56,48.5,129.6,71.7L158.56,618.97z" />
28
+ <path d="M811.86,163.17c-29.1-13.4-56.899-15.4-70.899-15.4c-1.801,0-3.5,0-4.9,0.1c-3.2-11.2-19.4-78.1-30.7-124.9
29
+ c-5.2-21.5-31.1-30.2-48.2-16.1l-53.6,44.2c-14,11.5-16.9,31.7-6.7,46.7c22.4,32.9,59.5,91.7,77.7,144.8
30
+ c17.2,50.3,18.7,102.9,10.5,154.6c-13.4,83.7-57.6,168.2-131.4,251.2c-40.199,45.2-84.699,86.399-132.199,123.7
31
+ c-23.801,18.699-48.4,36.399-73.7,53c-2.601,1.699-32.7,19.399-53.601,30.199c-11.699,6-18.1,19-15.8,31.9l2.5,14
32
+ c2.4,13.4,13.5,23.5,27,24.6c34.3,2.9,105.101,4.801,199.7-13.899c51.8-10.2,101.8-34.5,148.2-62.101
33
+ c78.1-46.6,182.8-131.399,238.1-271c24.7-62.3,35.2-126.699,31.2-191.599c-3.9-63.8-20.7-112.4-34.1-142
34
+ C873.66,206.771,847.06,179.271,811.86,163.17z" />
35
+ <path d="M109.46,744.97c13.1,8.101,34.4,19.8,60.3,28.4c44.5,14.8,91.8,21.2,138.5,22.7l2.6,0.1l2.101-1.4
36
+ c34.5-23.1,67.3-47.3,97.6-71.899c37.7-30.7,71.5-62.2,100.5-93.601c26.3-28.5,50.6-58.899,71.4-91.6
37
+ c18.199-28.6,33.699-59.1,44.8-91.2c5.2-15,9.5-30.399,12.399-46.1c3-15.9,4.4-32.1,6.5-48.2c0.4-3.2,0.801-6.3,1.2-9.5
38
+ l-19.899,33.9c-41.7,71.199-110.5,133.699-204.601,186c-61.2,34.1-126.8,60.1-193.6,81.199c-36.4,11.5-71.6,21.601-108.6,27.301
39
+ c-14.6,2.199-25.3,14.8-25.3,29.6c0,6.4,0,13.1,0,18.9C95.36,729.87,100.66,739.47,109.46,744.97z" />
40
+ </g>
41
+ </svg>
42
+ );
43
+ }
44
+
45
+ if (typeof src === "string") {
46
+ return <img src={src} />;
47
+ }
48
+
49
+ if (src) {
50
+ const Src = src;
51
+ return <Src />;
52
+ }
53
+
54
+ return null;
55
+ };
56
+
57
+ export default Logo;
@@ -0,0 +1,115 @@
1
+ import * as React from "react";
2
+ import { CSSObject, Theme, useTheme } from "@mui/material/styles";
3
+ import Box from "@mui/material/Box";
4
+ import Drawer from "@mui/material/Drawer";
5
+ import Divider from "@mui/material/Divider";
6
+ import User from "./User";
7
+ import Hamburger from "./Hamburger";
8
+ import NavBarRoutes from "./NavBarRoutes";
9
+ import Branding from "./Branding";
10
+ import { LogoType } from "../types";
11
+ import { useNavigate } from "react-router-dom";
12
+
13
+ const drawerWidth = 240;
14
+
15
+ const openedMixin = (theme: Theme): CSSObject => ({
16
+ width: drawerWidth,
17
+ transition: theme.transitions.create("width", {
18
+ easing: theme.transitions.easing.sharp,
19
+ duration: theme.transitions.duration.enteringScreen,
20
+ }),
21
+ overflowX: "hidden",
22
+ });
23
+
24
+ const closedMixin = (theme: Theme): CSSObject => ({
25
+ transition: theme.transitions.create("width", {
26
+ easing: theme.transitions.easing.sharp,
27
+ duration: theme.transitions.duration.leavingScreen,
28
+ }),
29
+ overflowX: "hidden",
30
+ width: `calc(${theme.spacing(7)} + 1px)`,
31
+ [theme.breakpoints.up("sm")]: {
32
+ width: `calc(${theme.spacing(8)} + 1px)`,
33
+ },
34
+ });
35
+
36
+ interface NavBarProps {
37
+ nav: Record<string, React.ReactNode>;
38
+ logo?: LogoType;
39
+ title?: string;
40
+ subtitle?: string;
41
+ version?: string;
42
+ }
43
+
44
+ const NavBar: React.FC<NavBarProps> = (
45
+ { nav, logo, title, subtitle, version },
46
+ ) => {
47
+ const theme = useTheme();
48
+ const navigate = useNavigate();
49
+
50
+ const [open, setOpen] = React.useState(true);
51
+
52
+ const toggleDrawerOpen = () => {
53
+ setOpen(!open);
54
+ };
55
+
56
+ const onClick = () => {
57
+ navigate("/", { replace: true });
58
+ };
59
+
60
+ return (
61
+ <Drawer
62
+ sx={{
63
+ width: drawerWidth,
64
+ flexShrink: 0,
65
+ whiteSpace: "nowrap",
66
+ boxSizing: "border-box",
67
+ ...(open && {
68
+ ...openedMixin(theme),
69
+ "& .MuiDrawer-paper": openedMixin(theme),
70
+ }),
71
+ ...(!open && {
72
+ ...closedMixin(theme),
73
+ "& .MuiDrawer-paper": closedMixin(theme),
74
+ }),
75
+ }}
76
+ anchor="left"
77
+ variant="permanent"
78
+ open={open}
79
+ >
80
+ <Box sx={{ display: "flex", padding: theme.spacing(2, 0, 2) }}>
81
+ <Hamburger open={open} onClick={toggleDrawerOpen} />
82
+ <Branding
83
+ sx={{ opacity: open ? 1 : 0 }}
84
+ logo={logo}
85
+ title={title}
86
+ subtitle={subtitle}
87
+ version={version}
88
+ onClick={onClick}
89
+ fullWidth={open}
90
+ />
91
+ </Box>
92
+
93
+ <Box
94
+ sx={{
95
+ flexGrow: 1,
96
+ flexShrink: 0,
97
+ }}
98
+ >
99
+ <NavBarRoutes nav={nav} open={open} />
100
+ </Box>
101
+
102
+ <Box
103
+ sx={{
104
+ flexGrow: 0,
105
+ flexShrink: 0,
106
+ }}
107
+ >
108
+ <Divider />
109
+ <User open={open} />
110
+ </Box>
111
+ </Drawer>
112
+ );
113
+ };
114
+
115
+ export default NavBar;
@@ -0,0 +1,73 @@
1
+ import { useTheme } from "@mui/material/styles";
2
+ import ListItem from "@mui/material/ListItem";
3
+ import ListItemButton from "@mui/material/ListItemButton";
4
+ import ListItemIcon from "@mui/material/ListItemIcon";
5
+ import ListItemText from "@mui/material/ListItemText";
6
+ import React from "react";
7
+ import { useLocation } from "react-router-dom";
8
+ import { RouteInfo, useRouter } from "../contexts/RouterContext";
9
+ import { LogoType } from "../types";
10
+ import Link from "./Link";
11
+ import Logo from "./Logo";
12
+
13
+ interface NavBarItemProps {
14
+ route: RouteInfo;
15
+ title: string;
16
+ open: boolean;
17
+ subtitle?: string | React.ReactNode;
18
+ icon?: LogoType;
19
+ }
20
+
21
+ const NavBarItem: React.FC<React.PropsWithChildren<NavBarItemProps>> = (
22
+ { route, title, subtitle, icon, children, open },
23
+ ) => {
24
+ const Icon = icon;
25
+ const [selected, setSelected] = React.useState(false);
26
+ const location = useLocation();
27
+ const { navigate } = useRouter();
28
+ const theme = useTheme();
29
+
30
+ const onClick = () => {
31
+ navigate(route);
32
+ };
33
+
34
+ React.useEffect(() => {
35
+ setSelected(location.pathname === route.path);
36
+ }, [location]);
37
+
38
+ return (
39
+ <ListItem
40
+ onClick={onClick}
41
+ key={route.id}
42
+ disablePadding
43
+ sx={{ display: "block" }}
44
+ >
45
+ <ListItemButton
46
+ selected={selected}
47
+ sx={{
48
+ minHeight: 48,
49
+ justifyContent: open ? "initial" : "center",
50
+ px: 2.5,
51
+ }}
52
+ >
53
+ <ListItemIcon
54
+ sx={{
55
+ minWidth: 0,
56
+ justifyContent: "center",
57
+ padding: theme.spacing(1, 1),
58
+ }}
59
+ >
60
+ {Icon !== undefined ? <Logo src={Icon} /> : title.substring(0, 1)}
61
+ </ListItemIcon>
62
+ <ListItemText
63
+ sx={{ opacity: open ? 1 : 0 }}
64
+ primary={title}
65
+ secondary={subtitle}
66
+ />
67
+ {children}
68
+ </ListItemButton>
69
+ </ListItem>
70
+ );
71
+ };
72
+
73
+ export default NavBarItem;
@@ -0,0 +1,67 @@
1
+ import List from "@mui/material/List";
2
+ import React from "react";
3
+ import { RouteInfo, useRouter } from "../contexts/RouterContext";
4
+ import NavBarItem from "./NavBarItem";
5
+
6
+ interface NavBarRoutesProps {
7
+ nav: Record<string, React.ReactNode>;
8
+ open: boolean;
9
+ }
10
+
11
+ function groupRoutesByApp(routes: RouteInfo[]): Record<string, RouteInfo[]> {
12
+ const groupedRoutes = {};
13
+
14
+ for (const route of routes) {
15
+ groupedRoutes[route.app] ??= [];
16
+ groupedRoutes[route.app].push(route);
17
+ }
18
+
19
+ return groupedRoutes;
20
+ }
21
+
22
+ const NavBarRoutes: React.FC<NavBarRoutesProps> = ({ nav, open }) => {
23
+ const { routes } = useRouter();
24
+
25
+ const navItems = React.useMemo(() => {
26
+ const apps: React.ReactNode[] = [];
27
+ const groupedRoutes = groupRoutesByApp(routes);
28
+
29
+ for (const app of Object.keys(groupedRoutes)) {
30
+ const appRoutes = groupedRoutes[app];
31
+ const appItems: React.ReactNode[] = [];
32
+
33
+ for (const route of appRoutes) {
34
+ if (route.navigation) {
35
+ appItems.push(
36
+ <NavBarItem
37
+ key={route.id}
38
+ route={route}
39
+ title={route.title}
40
+ open={open}
41
+ />,
42
+ );
43
+ }
44
+ }
45
+
46
+ apps.push(
47
+ <List key={app}>
48
+ {appItems}
49
+ </List>,
50
+ );
51
+ }
52
+
53
+ return apps;
54
+ }, [
55
+ routes,
56
+ nav,
57
+ open,
58
+ ]);
59
+
60
+ return (
61
+ <>
62
+ {navItems}
63
+ </>
64
+ );
65
+ };
66
+
67
+ export default NavBarRoutes;
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+
3
+ import { LinearProgress } from "@mui/material";
4
+
5
+ interface ProgressBarProps {
6
+ loading?: boolean;
7
+ color?:
8
+ | "primary"
9
+ | "secondary"
10
+ | "error"
11
+ | "info"
12
+ | "success"
13
+ | "warning"
14
+ | "inherit"
15
+ | undefined;
16
+ }
17
+
18
+ const ProgressBar: React.FC<ProgressBarProps> = ({
19
+ loading,
20
+ color,
21
+ }) => {
22
+ loading ??= true;
23
+ color ??= "secondary";
24
+
25
+ return (
26
+ loading && <LinearProgress color={color} />
27
+ );
28
+ };
29
+
30
+ export default ProgressBar;
@@ -0,0 +1,97 @@
1
+ import React from "react";
2
+
3
+ import AccountCircleIcon from "@mui/icons-material/AccountCircle";
4
+ import ButtonBase from "@mui/material/ButtonBase";
5
+ import List from "@mui/material/List";
6
+ import { Theme, useTheme } from "@mui/material/styles";
7
+ import Typography from "@mui/material/Typography";
8
+
9
+ import { useUser } from "../contexts/UserContext";
10
+ import { LogoType } from "../types";
11
+ import NavBarItem from "./NavBarItem";
12
+ import { ss } from "../util/select_styles";
13
+ import { useRouter } from "../contexts/RouterContext";
14
+ import { useLocation } from "react-router-dom";
15
+
16
+ const styles = (theme: Theme) => ({
17
+ root: {
18
+ display: "flex",
19
+ padding: 0,
20
+ borderTopWidth: 1,
21
+ borderTopStyle: "solid",
22
+ borderTopColor: theme.palette.divider,
23
+ ...theme.mixins.toolbar,
24
+ },
25
+ link: {
26
+ fontSize: "inherit",
27
+ "& > *": {
28
+ fontSize: "0.9em",
29
+ },
30
+ },
31
+ drawerLink: {
32
+ "&:hover": {
33
+ color: theme.palette.primary.main,
34
+ opacity: 1,
35
+ },
36
+ },
37
+ appbarLink: {
38
+ "&:hover": {
39
+ color: theme.palette.secondary.light,
40
+ opacity: 1,
41
+ },
42
+ },
43
+ });
44
+
45
+ interface UserProps {
46
+ classes?: ReturnType<typeof styles>;
47
+ open: boolean;
48
+ icon?: LogoType;
49
+ }
50
+
51
+ const User: React.FC<UserProps> = ({ classes, open, icon }) => {
52
+ const { user, logout } = useUser();
53
+ const { routes, getRoute } = useRouter();
54
+
55
+ classes ??= styles(useTheme());
56
+
57
+ const UserIcon = icon ?? AccountCircleIcon;
58
+ const [route, logoutText, selected] = React.useMemo(() => {
59
+ const route = getRoute("bananas.me:list");
60
+ return [
61
+ route,
62
+ getRoute("bananas.logout:create")!.title,
63
+ route?.path === window.location.pathname,
64
+ ];
65
+ }, [routes]);
66
+
67
+ return (
68
+ user !== null && route !== undefined
69
+ ? (
70
+ <List>
71
+ <NavBarItem
72
+ route={route}
73
+ open={open}
74
+ icon={UserIcon}
75
+ title={user.full_name}
76
+ subtitle={
77
+ <ButtonBase
78
+ sx={ss(
79
+ classes.link,
80
+ classes.drawerLink,
81
+ )}
82
+ onClick={(e) => {
83
+ e.preventDefault();
84
+ logout();
85
+ }}
86
+ >
87
+ <Typography color="inherit">{logoutText}</Typography>
88
+ </ButtonBase>
89
+ }
90
+ />
91
+ </List>
92
+ )
93
+ : null
94
+ );
95
+ };
96
+
97
+ export default User;
@@ -0,0 +1,18 @@
1
+ import { Box } from "@mui/material";
2
+ import React from "react";
3
+
4
+ const Content: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
5
+ return (
6
+ <Box
7
+ sx={{
8
+ display: "flex",
9
+ position: "relative",
10
+ flexGrow: 1,
11
+ }}
12
+ >
13
+ {children}
14
+ </Box>
15
+ );
16
+ };
17
+
18
+ export default Content;
@@ -0,0 +1,64 @@
1
+ import React from "react";
2
+
3
+ import Box from "@mui/material/Box";
4
+ import Card from "@mui/material/Card";
5
+ import CardContent from "@mui/material/CardContent";
6
+ import { useTheme } from "@mui/material/styles";
7
+ import { Button, CardActions, Typography } from "@mui/material";
8
+ import { PageLoadFailedError } from "./PageLoader";
9
+ import { useI18n } from "../contexts/I18nContext";
10
+ import { useUser } from "../contexts/UserContext";
11
+
12
+ const errorMessages = {
13
+ 403:
14
+ "You are authenticated as %(username)s, but are not authorized to access this page. Would you like to login to a different account?",
15
+ 404: "We're sorry, but the requested page could not be found.",
16
+ };
17
+
18
+ interface ErrorScreenInnerProps {
19
+ error: PageLoadFailedError;
20
+ }
21
+
22
+ const ErrorScreen: React.FC<ErrorScreenInnerProps> = ({
23
+ error,
24
+ }) => {
25
+ const { t } = useI18n();
26
+ const { user, logout } = useUser();
27
+ const theme = useTheme();
28
+
29
+ return (
30
+ <Card>
31
+ <CardContent>
32
+ <Typography variant="h4">
33
+ {error.message}
34
+ </Typography>
35
+
36
+ <Typography>
37
+ {t(
38
+ error.response.status >= 500
39
+ ? "There's been an error. It's been reported to the site administrators via email and should be fixed shortly. Thanks for your patience."
40
+ : errorMessages[error.response.status] ?? "",
41
+ (user as unknown as Record<string, string>) ?? {},
42
+ )}
43
+ </Typography>
44
+ </CardContent>
45
+ <CardActions>
46
+ {error.response.status === 403 && (
47
+ <Box sx={{ marginTop: theme.spacing(3), textAlign: "right" }}>
48
+ <Button
49
+ variant="contained"
50
+ color="secondary"
51
+ onClick={() =>
52
+ logout()}
53
+ sx={{ boxShadow: "none" }}
54
+ >
55
+ {t("Log in again")}
56
+ </Button>
57
+ </Box>
58
+ )}
59
+ </CardActions>
60
+ </Card>
61
+ );
62
+ };
63
+
64
+ export default ErrorScreen;
@@ -0,0 +1,135 @@
1
+ import React from "react";
2
+
3
+ import { Theme, useTheme } from "@mui/material/styles";
4
+ import Box from "@mui/material/Box";
5
+ import CircularProgress from "@mui/material/CircularProgress";
6
+ import Fade from "@mui/material/Fade";
7
+ import { alpha } from "@mui/system";
8
+
9
+ import Logo from "../components/Logo";
10
+ import { LogoType } from "../types";
11
+ import { ss } from "../util/select_styles";
12
+
13
+ const styles = (theme: Theme) =>
14
+ ({
15
+ root: {
16
+ position: "relative",
17
+ top: 0,
18
+ left: 0,
19
+ width: "100%",
20
+ height: "100%",
21
+ display: "flex",
22
+ flexDirection: "column",
23
+ alignItems: "center",
24
+ justifyContent: "center",
25
+ },
26
+ backdrop: {
27
+ position: "absolute",
28
+ zIndex: 2000,
29
+ backgroundColor: alpha(theme.palette.background.default, 0.666),
30
+ },
31
+ backdropPrimary: {
32
+ backgroundColor: alpha(theme.palette.primary.main, 0.666),
33
+ },
34
+ backdropSecondary: {
35
+ backgroundColor: alpha(theme.palette.secondary.main, 0.666),
36
+ },
37
+ backdropPaper: {
38
+ backgroundColor: alpha(theme.palette.background.paper, 0.666),
39
+ },
40
+ spinner: {
41
+ color: theme.palette.primary.main,
42
+ },
43
+ spinnerContrast: {
44
+ color: theme.palette.primary.contrastText,
45
+ },
46
+ logo: {
47
+ position: "absolute",
48
+ margin: 0,
49
+ marginTop: theme.spacing(-36),
50
+ },
51
+ }) as const;
52
+
53
+ interface LoadingScreenInnerProps {
54
+ loading: boolean;
55
+ logo?: LogoType;
56
+ backdrop: boolean;
57
+ color?: string;
58
+ classes: ReturnType<typeof styles>;
59
+ }
60
+
61
+ const LoadingScreenInner: React.FC<LoadingScreenInnerProps> = ({
62
+ loading,
63
+ logo,
64
+ classes,
65
+ backdrop,
66
+ color,
67
+ }) => {
68
+ return (
69
+ <Box
70
+ sx={ss(
71
+ classes.root,
72
+ [classes.backdrop, backdrop],
73
+ [classes.backdropPrimary, backdrop && color === "primary"],
74
+ [classes.backdropSecondary, backdrop && color === "secondary"],
75
+ [classes.backdropPaper, backdrop && color === "paper"],
76
+ )}
77
+ >
78
+ <Box
79
+ sx={classes.logo}
80
+ >
81
+ {logo && <Logo src={logo} />}
82
+ </Box>
83
+ {loading && (
84
+ <CircularProgress
85
+ sx={ss(classes.spinner, [classes.spinnerContrast, !color])}
86
+ />
87
+ )}
88
+ </Box>
89
+ );
90
+ };
91
+
92
+ interface LoadingScreenProps {
93
+ loading?: boolean;
94
+ logo?: LogoType;
95
+ backdrop?: boolean;
96
+ color?: string;
97
+ classes?: ReturnType<typeof styles>;
98
+ }
99
+
100
+ const LoadingScreen: React.FC<LoadingScreenProps> = (props) => {
101
+ let { loading, logo, backdrop, color, classes } = { ...props };
102
+ classes ??= styles(useTheme());
103
+ loading ??= true;
104
+ backdrop ??= false;
105
+
106
+ return backdrop
107
+ ? (
108
+ <Fade
109
+ in={loading}
110
+ timeout={{
111
+ enter: 750,
112
+ exit: 250,
113
+ }}
114
+ >
115
+ <LoadingScreenInner
116
+ loading={loading}
117
+ logo={logo}
118
+ backdrop={backdrop}
119
+ color={color}
120
+ classes={classes}
121
+ />
122
+ </Fade>
123
+ )
124
+ : (
125
+ <LoadingScreenInner
126
+ loading={loading}
127
+ logo={logo}
128
+ backdrop={backdrop}
129
+ color={color}
130
+ classes={classes}
131
+ />
132
+ );
133
+ };
134
+
135
+ export default LoadingScreen;
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+
3
+ export interface PageErrorBoundaryProps<T = Error> {
4
+ errorPage: React.ComponentType<{ error: T }>;
5
+ children: React.ReactNode;
6
+ }
7
+
8
+ export default class PageErrorBoundary<T = Error>
9
+ extends React.Component<PageErrorBoundaryProps<T>> {
10
+ state: { error: T | null } = { error: null };
11
+ errorPage: PageErrorBoundaryProps<T>["errorPage"];
12
+
13
+ constructor(props: PageErrorBoundaryProps<T>) {
14
+ super(props);
15
+ this.errorPage = props.errorPage;
16
+ }
17
+
18
+ componentDidCatch(error: Error) {
19
+ this.setState({ error });
20
+ }
21
+
22
+ render() {
23
+ const { error } = this.state;
24
+
25
+ if (error) {
26
+ const ErrorPage = this.errorPage;
27
+ return <ErrorPage error={error} />;
28
+ }
29
+
30
+ return this.props.children;
31
+ }
32
+ }