mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
add EmptyState, Loading components
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
80
www/src/components/EmptyState.tsx
Normal file
80
www/src/components/EmptyState.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
34
www/src/components/ErrorBoundary.tsx
Normal file
34
www/src/components/ErrorBoundary.tsx
Normal 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;
|
||||
60
www/src/components/Loading.tsx
Normal file
60
www/src/components/Loading.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user