add EmptyState, Loading components

This commit is contained in:
Sidney Alcantara
2020-01-30 12:13:06 +11:00
parent 2695cf7489
commit 86b09fc067
10 changed files with 234 additions and 31 deletions

View File

@@ -19,6 +19,7 @@
"@types/react": "^16.9.2",
"@types/react-color": "^3.0.1",
"@types/react-data-grid": "^4.0.3",
"@types/react-div-100vh": "^0.3.0",
"@types/react-dom": "16.9.0",
"@types/react-router-dom": "^4.3.5",
"algoliasearch": "^3.34.0",
@@ -38,6 +39,7 @@
"react-color": "^2.17.3",
"react-data-grid": "^6.1.0",
"react-data-grid-addons": "^6.1.0",
"react-div-100vh": "^0.3.8",
"react-dom": "^16.9.0",
"react-dropzone": "^10.1.8",
"react-quill": "^1.3.3",

View File

@@ -1,5 +1,5 @@
import React, { lazy, Suspense } from "react";
import { Route } from "react-router-dom";
import { Route, Switch } from "react-router-dom";
import {
MuiThemeProvider as ThemeProvider,
@@ -10,9 +10,13 @@ import Theme from "./Theme";
import CustomBrowserRouter from "./util/CustomBrowserRouter";
import PrivateRoute from "./util/PrivateRoute";
import Snack from "./components/Snack";
import { SnackProvider } from "./util/SnackProvider";
import ErrorBoundary from "./components/ErrorBoundary";
import EmptyState from "./components/EmptyState";
import Loading from "./components/Loading";
import { SnackProvider } from "./util/SnackProvider";
import { AuthProvider } from "./AuthProvider";
const AuthView = lazy(() => import("./views/AuthView"));
const TableView = lazy(() => import("./views/TableView"));
const TablesView = lazy(() => import("./views/TablesView"));
@@ -22,21 +26,28 @@ const App: React.FC = () => {
return (
<ThemeProvider theme={Theme}>
<CssBaseline />
<AuthProvider>
<SnackProvider>
<CustomBrowserRouter>
<div>
<Suspense fallback={<div>Loading View</div>}>
<Route exact path="/auth" render={() => <AuthView />} />
<PrivateRoute exact path="/" render={() => <TablesView />} />
<PrivateRoute path="/table/" render={() => <TableView />} />
<PrivateRoute path="/editor" render={() => <EditorView />} />
<Snack />
<ErrorBoundary>
<AuthProvider>
<SnackProvider>
<CustomBrowserRouter>
<Suspense fallback={<Loading fullScreen />}>
<Switch>
<Route exact path="/auth" render={() => <AuthView />} />
<PrivateRoute exact path="/" render={() => <TablesView />} />
<PrivateRoute path="/table/" render={() => <TableView />} />
<PrivateRoute path="/editor" render={() => <EditorView />} />
<Route
render={() => (
<EmptyState message="Page Not Found" fullScreen />
)}
/>
</Switch>
</Suspense>
</div>
</CustomBrowserRouter>
</SnackProvider>
</AuthProvider>
<Snack />
</CustomBrowserRouter>
</SnackProvider>
</AuthProvider>
</ErrorBoundary>
</ThemeProvider>
);
};

View File

@@ -0,0 +1,80 @@
import React from "react";
import Div100vh from "react-div-100vh";
import {
makeStyles,
createStyles,
Grid,
CircularProgress,
Typography,
} from "@material-ui/core";
import ErrorIcon from "@material-ui/icons/Error";
const useStyles = makeStyles(theme =>
createStyles({
root: {
height: "100%",
width: "100%",
textAlign: "center",
},
content: { maxWidth: "25em" },
icon: {
color: theme.palette.text.disabled,
fontSize: "3.5rem",
},
message: {
textTransform: "uppercase",
marginTop: theme.spacing(1),
letterSpacing: 1,
},
})
);
interface IEmptyStateProps {
message?: React.ReactNode;
description?: React.ReactNode;
Icon?: typeof ErrorIcon;
fullScreen?: boolean;
}
export default function EmptyState({
message = "Nothing here",
description,
Icon = ErrorIcon,
fullScreen = false,
}: IEmptyStateProps) {
const classes = useStyles({});
return (
<Grid
container
className={classes.root}
direction="column"
justify="center"
alignItems="center"
component={fullScreen ? Div100vh : "div"}
style={{ height: fullScreen ? "100rvh" : "100%" }}
>
<Grid item className={classes.content}>
<Icon className={classes.icon} />
<Typography
variant="h6"
className={classes.message}
color="textSecondary"
gutterBottom
>
{message}
</Typography>
{description && (
<Typography color="textSecondary" variant="body2">
{description}
</Typography>
)}
</Grid>
</Grid>
);
}

View File

@@ -0,0 +1,34 @@
import React from "react";
import EmptyState from "./EmptyState";
class ErrorBoundary extends React.Component {
state = { hasError: false, errorMessage: "" };
static getDerivedStateFromError(error: Error) {
// Update state so the next render will show the fallback UI.
return { hasError: true, errorMessage: error.message };
}
componentDidCatch(error: Error, errorInfo: object) {
console.log(error, errorInfo);
// You can also log the error to an error reporting service
//logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
<EmptyState
message="Something Went Wrong"
description={this.state.errorMessage}
fullScreen
/>
);
}
return this.props.children;
}
}
export default ErrorBoundary;

View File

@@ -0,0 +1,60 @@
import React from "react";
import Div100vh from "react-div-100vh";
import {
makeStyles,
createStyles,
Grid,
CircularProgress,
Typography,
} from "@material-ui/core";
const useStyles = makeStyles(theme =>
createStyles({
root: {
height: "100%",
width: "100%",
textAlign: "center",
},
content: { maxWidth: "25em" },
message: {
textTransform: "uppercase",
marginTop: theme.spacing(1),
letterSpacing: 1,
},
})
);
interface ILoading {
message?: string;
fullScreen?: boolean;
}
export default function Loading({
message = "Loading",
fullScreen = false,
}: ILoading) {
const classes = useStyles({});
return (
<Grid
container
className={classes.root}
direction="column"
justify="center"
alignItems="center"
component={fullScreen ? Div100vh : "div"}
style={{ height: fullScreen ? "100rvh" : "100%" }}
>
<Grid item className={classes.content}>
<CircularProgress />
<Typography
variant="h6"
className={classes.message}
color="textSecondary"
>
{message}
</Typography>
</Grid>
</Grid>
);
}

View File

@@ -1,9 +1,11 @@
import React from "react";
import Button from "@material-ui/core/Button";
import Loading from "../Loading";
import EmptyState from "../EmptyState";
const EmptyTable = (props: any) => {
const { isLoading, tableHeight, addRow } = props;
if (isLoading) return <h3>Fetching rows</h3>;
if (isLoading) return <Loading message="Fetching rows" />;
else
return (
<div
@@ -14,7 +16,7 @@ const EmptyTable = (props: any) => {
padding: "100px",
}}
>
<h3>no data to show</h3>
<EmptyState message="No data to show" />
<Button
onClick={() => {
addRow();

View File

@@ -5,6 +5,8 @@ import { DraggableHeader } from "react-data-grid-addons";
import isEqual from "lodash/isEqual";
import isEmpty from "lodash/isEmpty";
import xorWith from "lodash/xorWith";
import Loading from "../Loading";
const ReactDataGrid = lazy(() => import("react-data-grid"));
const { DraggableContainer } = DraggableHeader;
const Grid = (props: any) => {
@@ -23,10 +25,10 @@ const Grid = (props: any) => {
setSelectedCell,
} = props;
return (
<Suspense fallback={<div>Loading table...</div>}>
<Suspense fallback={<Loading message="Loading table" />}>
<DraggableContainer onHeaderDrop={onHeaderDrop}>
<ReactDataGrid
headerRowHeight={45}
headerRowHeight={44}
rowRenderer={RowRenderer}
rowHeight={rowHeight}
columns={columns}

View File

@@ -33,6 +33,7 @@ import { EditorProvider } from "../../util/EditorProvider";
import LongTextEditor from "../LongTextEditor";
import RichTextEditor from "../RichTextEditor";
import _isEmpty from "lodash/isEmpty";
import Loading from "../../components/Loading";
const Hotkeys = lazy(() => import("./HotKeys"));
const TableHeader = lazy(() => import("./TableHeader"));
const SearchBox = lazy(() => import("../SearchBox"));
@@ -333,9 +334,9 @@ function Table(props: Props) {
setSelectedCell={setSelectedCell}
/>
) : (
<p>fetching columns</p>
<Loading message="Fetching columns" />
)}
<Suspense fallback={<div>Loading helpers...</div>}>
<Suspense fallback={<Loading message="Loading helpers" />}>
<ColumnEditor
handleClose={handleCloseHeader}
anchorEl={anchorEl}

View File

@@ -1,6 +1,8 @@
import React, { useContext } from "react";
import { Route, RouteProps, Redirect } from "react-router-dom";
import AuthContext from "../contexts/authContext";
import Loading from "../components/Loading";
interface IPrivateRouteProps extends RouteProps {
render: NonNullable<RouteProps["render"]>;
@@ -8,18 +10,15 @@ interface IPrivateRouteProps extends RouteProps {
const PrivateRoute: React.FC<IPrivateRouteProps> = ({ render, ...rest }) => {
const { currentUser } = useContext(AuthContext);
if (!!currentUser) return <Route {...rest} render={render} />;
if (currentUser === null) return <Redirect to="/auth" />;
return (
<Route
{...rest}
render={routeProps =>
!!currentUser ? (
render(routeProps)
) : currentUser === null ? (
<Redirect to={"/auth"} />
) : (
<p>authenticating</p>
)
}
render={() => <Loading message="Authenticating" fullScreen />}
/>
);
};

View File

@@ -1688,6 +1688,13 @@
dependencies:
"@types/react" "*"
"@types/react-div-100vh@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@types/react-div-100vh/-/react-div-100vh-0.3.0.tgz#3146b567304c6d9587f340fdb4b6e612596f07ad"
integrity sha512-ooouxtvbj+mP/OmdAh91+huVECechS2Svb55zTWMaRPXIG4XXL4+TESR7navmm6kvlnZRhDNb7/2qUOTrQm17g==
dependencies:
"@types/react" "*"
"@types/react-dom@16.9.0":
version "16.9.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.0.tgz#ba6ddb00bf5de700b0eb91daa452081ffccbfdea"
@@ -10254,6 +10261,11 @@ react-dev-utils@^9.0.4:
strip-ansi "5.2.0"
text-table "0.2.0"
react-div-100vh@^0.3.8:
version "0.3.8"
resolved "https://registry.yarnpkg.com/react-div-100vh/-/react-div-100vh-0.3.8.tgz#54e4c32d0286a65e92367fc0a07cc3f2f00739d8"
integrity sha512-1kDFW+HXYpfac1tfJ4BcQmgTSeTtLVs2FO2ZNHcwLIga+oVluexUEISCBJvr9xq98DK8tcygY3259EvIy5O+3g==
react-dnd-html5-backend@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-2.6.0.tgz#590cd1cca78441bb274edd571fef4c0b16ddcf8e"