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 ( +
+
+ +
+ +
+
+
+
+ + <> +
+
+ +
+
+
+ + Overseerr + +
+ +
+
+
+ {/* */} +
+ +
+
+
+
+ +
+
+
+
+
+ Overseerr +
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+ + + +
+ +
+
+
+
+ +
+
+ +
+ + + +
+
+
+
+ +
+
+
+ {children} +
+
+
+
+
+ ); +}; + +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"