diff --git a/package.json b/package.json
index 2e79a0d68..a72fb95a3 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"next": "9.5.2",
"react": "16.13.1",
"react-dom": "16.13.1",
+ "react-transition-group": "^4.4.1",
"reflect-metadata": "^0.1.13",
"sqlite3": "^5.0.0",
"typeorm": "^0.2.25"
@@ -27,6 +28,7 @@
"@types/express": "^4.17.7",
"@types/node": "^14.0.27",
"@types/react": "^16.9.46",
+ "@types/react-transition-group": "^4.4.0",
"@typescript-eslint/eslint-plugin": "^3.9.0",
"@typescript-eslint/parser": "^3.9.0",
"commitizen": "^4.1.2",
diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx
new file mode 100644
index 000000000..3f614adb5
--- /dev/null
+++ b/src/components/Layout/index.tsx
@@ -0,0 +1,271 @@
+import React, { useState } from 'react';
+import Transition from '../Transition';
+
+const Layout: React.FC = ({ children }) => {
+ const [isSidebarOpen, setSidebarOpen] = useState(false);
+ const [isDropdownOpen, setDropdownOpen] = useState(false);
+
+ return (
+
+
+
+
+
+
+
+
+ <>
+
+
+
+
+
+
+
+ {/* */}
+
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Layout;
diff --git a/src/components/Transition/index.tsx b/src/components/Transition/index.tsx
new file mode 100644
index 000000000..0b940a5e0
--- /dev/null
+++ b/src/components/Transition/index.tsx
@@ -0,0 +1,124 @@
+import React from 'react';
+import { CSSTransition as ReactCSSTransition } from 'react-transition-group';
+import { useRef, useEffect, useContext } from 'react';
+
+interface CSSTransitionProps {
+ show?: boolean;
+ enter?: string;
+ enterFrom?: string;
+ enterTo?: string;
+ leave?: string;
+ leaveFrom?: string;
+ leaveTo?: string;
+ appear?: boolean;
+}
+
+const TransitionContext = React.createContext<{
+ parent: { show?: boolean; isInitialRender?: boolean; appear?: boolean };
+}>({
+ parent: {},
+});
+
+function useIsInitialRender() {
+ const isInitialRender = useRef(true);
+ useEffect(() => {
+ isInitialRender.current = false;
+ }, []);
+ return isInitialRender.current;
+}
+
+const CSSTransition: React.FC = ({
+ show,
+ enter = '',
+ enterFrom = '',
+ enterTo = '',
+ leave = '',
+ leaveFrom = '',
+ leaveTo = '',
+ appear,
+ children,
+}) => {
+ const enterClasses = enter.split(' ').filter((s) => s.length);
+ const enterFromClasses = enterFrom.split(' ').filter((s) => s.length);
+ const enterToClasses = enterTo.split(' ').filter((s) => s.length);
+ const leaveClasses = leave.split(' ').filter((s) => s.length);
+ const leaveFromClasses = leaveFrom.split(' ').filter((s) => s.length);
+ const leaveToClasses = leaveTo.split(' ').filter((s) => s.length);
+
+ const addClasses = (node: HTMLElement, classes: string[]) => {
+ classes.length && node.classList.add(...classes);
+ };
+
+ const removeClasses = (node: HTMLElement, classes: string[]) => {
+ classes.length && node.classList.remove(...classes);
+ };
+
+ return (
+ {
+ node.addEventListener('transitionend', done, false);
+ }}
+ onEnter={(node: HTMLElement) => {
+ addClasses(node, [...enterClasses, ...enterFromClasses]);
+ }}
+ onEntering={(node: HTMLElement) => {
+ removeClasses(node, enterFromClasses);
+ addClasses(node, enterToClasses);
+ }}
+ onEntered={(node: HTMLElement) => {
+ removeClasses(node, [...enterToClasses, ...enterClasses]);
+ }}
+ onExit={(node) => {
+ addClasses(node, [...leaveClasses, ...leaveFromClasses]);
+ }}
+ onExiting={(node) => {
+ removeClasses(node, leaveFromClasses);
+ addClasses(node, leaveToClasses);
+ }}
+ onExited={(node) => {
+ removeClasses(node, [...leaveToClasses, ...leaveClasses]);
+ }}
+ >
+ {children}
+
+ );
+};
+
+const Transition: React.FC = ({
+ show,
+ appear,
+ ...rest
+}) => {
+ const { parent } = useContext(TransitionContext);
+ const isInitialRender = useIsInitialRender();
+ const isChild = show === undefined;
+
+ if (isChild) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+ );
+};
+
+export default Transition;
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index f4aa3d9af..63eaedead 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -1,11 +1,16 @@
import React from 'react';
import '../styles/globals.css';
import App from 'next/app';
+import Layout from '../components/Layout';
class CoreApp extends App {
public render(): JSX.Element {
const { Component, pageProps } = this.props;
- return ;
+ return (
+
+
+
+ );
}
}
diff --git a/src/pages/_error.tsx b/src/pages/_error.tsx
new file mode 100644
index 000000000..5a9a183c1
--- /dev/null
+++ b/src/pages/_error.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { NextPage } from 'next';
+import Link from 'next/link';
+import { Undefinable } from '../utils/typeHelpers';
+
+interface ErrorProps {
+ statusCode?: number;
+}
+
+const getErrorMessage = (statusCode?: number) => {
+ switch (statusCode) {
+ case 404:
+ return 'Page not found.';
+ default:
+ return 'Something went wrong.';
+ }
+};
+
+const Error: NextPage = ({ statusCode }) => {
+ return (
+
+
{statusCode ? statusCode : 'Oops'}
+
+ {getErrorMessage(statusCode)}{' '}
+
+ Go home
+
+
+
+ );
+};
+
+Error.getInitialProps = async ({ res, err }): Promise => {
+ // Apologies for how gross ternary is but this is just temporary. Honestly,
+ // blame the nextjs docs
+ let statusCode: Undefinable;
+ if (!!res) {
+ statusCode = res.statusCode;
+ } else {
+ statusCode = err ? err.statusCode : undefined;
+ }
+
+ return { statusCode };
+};
+
+export default Error;
diff --git a/yarn.lock b/yarn.lock
index 0a5c08b64..312428e69 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -978,7 +978,7 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.10.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.6":
+"@babel/runtime@^7.10.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.6":
version "7.11.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
@@ -1331,7 +1331,14 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
-"@types/react@^16.9.46":
+"@types/react-transition-group@^4.4.0":
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
+ integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react@*", "@types/react@^16.9.46":
version "16.9.46"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.46.tgz#f0326cd7adceda74148baa9bff6e918632f5069e"
integrity sha512-dbHzO3aAq1lB3jRQuNpuZ/mnu+CdD3H0WVaaBQA8LTT3S33xhVBUj232T8M3tAhSWJs/D/UqORYUlJNl/8VQZg==
@@ -3311,6 +3318,14 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+dom-helpers@^5.0.1:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b"
+ integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==
+ dependencies:
+ "@babel/runtime" "^7.8.7"
+ csstype "^3.0.2"
+
dom-serializer@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.0.1.tgz#79695eb49af3cd8abc8d93a73da382deb1ca0795"
@@ -7140,6 +7155,16 @@ react-refresh@0.8.3:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
+react-transition-group@^4.4.1:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
+ integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ dom-helpers "^5.0.1"
+ loose-envify "^1.4.0"
+ prop-types "^15.6.2"
+
react@16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"