From 8bee841d661b532260ebf65709d8c06a0f9ed18a Mon Sep 17 00:00:00 2001 From: Yash Joshi Date: Sat, 15 Jan 2022 21:15:46 +0530 Subject: [PATCH] feat: setup dashboard - Setup basic code structure - Add routes - Add layout components for authentication and dashboard pages - Add session handling - Add login, signup and session --- dashboard/package-lock.json | 33 +++++++ dashboard/package.json | 4 +- dashboard/src/App.tsx | 54 ++++++++--- dashboard/src/Router.tsx | 0 dashboard/src/components/Sidebar.tsx | 54 +++++++++++ .../src/components/layouts/AuthLayout.tsx | 5 -- .../src/components/layouts/DefaultLayout.tsx | 5 -- dashboard/src/constants.ts | 1 + dashboard/src/containers/AuthContainer.tsx | 37 ++++++++ dashboard/src/contexts/AuthContext.tsx | 0 dashboard/src/graphql/mutation/index.ts | 15 ++++ dashboard/src/graphql/queries/index.ts | 7 ++ dashboard/src/layouts/AuthLayout.tsx | 29 ++++++ dashboard/src/layouts/DashboardLayout.tsx | 14 +++ dashboard/src/pages/Auth.tsx | 90 +++++++++++++++++++ dashboard/src/pages/Home.tsx | 6 ++ dashboard/src/pages/Users.tsx | 6 ++ dashboard/src/routes/index.tsx | 26 ++++++ dashboard/src/utils/index.ts | 3 + 19 files changed, 366 insertions(+), 23 deletions(-) delete mode 100644 dashboard/src/Router.tsx create mode 100644 dashboard/src/components/Sidebar.tsx delete mode 100644 dashboard/src/components/layouts/AuthLayout.tsx delete mode 100644 dashboard/src/components/layouts/DefaultLayout.tsx create mode 100644 dashboard/src/constants.ts create mode 100644 dashboard/src/containers/AuthContainer.tsx delete mode 100644 dashboard/src/contexts/AuthContext.tsx create mode 100644 dashboard/src/graphql/mutation/index.ts create mode 100644 dashboard/src/graphql/queries/index.ts create mode 100644 dashboard/src/layouts/AuthLayout.tsx create mode 100644 dashboard/src/layouts/DashboardLayout.tsx create mode 100644 dashboard/src/pages/Auth.tsx create mode 100644 dashboard/src/pages/Home.tsx create mode 100644 dashboard/src/pages/Users.tsx create mode 100644 dashboard/src/routes/index.tsx create mode 100644 dashboard/src/utils/index.ts diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 5005c5e..10b7230 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -781,6 +781,11 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, + "@graphql-typed-document-node/core": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz", + "integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==" + }, "@popperjs/core": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz", @@ -891,6 +896,15 @@ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" }, + "@urql/core": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-2.3.6.tgz", + "integrity": "sha512-PUxhtBh7/8167HJK6WqBv6Z0piuiaZHQGYbhwpNL9aIQmLROPEdaUYkY4wh45wPQXcTpnd11l0q3Pw+TI11pdw==", + "requires": { + "@graphql-typed-document-node/core": "^3.1.0", + "wonka": "^4.0.14" + } + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1232,6 +1246,11 @@ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" }, + "graphql": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.2.0.tgz", + "integrity": "sha512-MuQd7XXrdOcmfwuLwC2jNvx0n3rxIuNYOxUtiee5XOmfrWo613ar2U8pE7aHAKh8VwfpifubpD9IP+EdEAEOsA==" + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1611,6 +1630,15 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==" }, + "urql": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz", + "integrity": "sha512-ovK9mx7YxD/CKUwVZGbEDBzZjbCcNsr1990nIhDCKe3Ij/0gNcIa+0EIyXKceOPuYDYKavIoaNQV2kOZjQxFcw==", + "requires": { + "@urql/core": "^2.3.6", + "wonka": "^4.0.14" + } + }, "use-callback-ref": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz", @@ -1640,6 +1668,11 @@ "loose-envify": "^1.0.0" } }, + "wonka": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz", + "integrity": "sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==" + }, "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", diff --git a/dashboard/package.json b/dashboard/package.json index 301ee58..ba96a12 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -19,10 +19,12 @@ "@types/react-router-dom": "^5.3.2", "esbuild": "^0.14.9", "framer-motion": "^5.5.5", + "graphql": "^16.2.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-icons": "^4.3.1", "react-router-dom": "^6.2.1", - "typescript": "^4.5.4" + "typescript": "^4.5.4", + "urql": "^2.0.6" } } diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index a1700c6..4effe54 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -1,14 +1,44 @@ -import * as React from 'react'; -import { Text, ChakraProvider } from '@chakra-ui/react'; -import { MdStar } from 'react-icons/md'; -import { BrowserRouter } from 'react-router-dom'; +import * as React from "react"; +import { ChakraProvider, extendTheme } from "@chakra-ui/react"; +import { BrowserRouter } from "react-router-dom"; +import { createClient, Provider } from "urql"; +import {AppRoutes} from './routes' +import { AuthContainer } from "./containers/AuthContainer"; -export default function Example() { - return ( - - -

Dashboard

-
-
- ); +const queryClient = createClient({ + url: "/graphql", + fetchOptions: () => { + return { + credentials: "include", + }; + }, +}); + +const theme = extendTheme({ + styles: { + global: { + "html, body, #root": { + height: "100%", + }, + }, + }, + colors: { + blue: { + 500: "rgb(59,130,246)", + }, + }, +}); + +export default function App() { + return ( + + + + + + + + + + ); } diff --git a/dashboard/src/Router.tsx b/dashboard/src/Router.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/dashboard/src/components/Sidebar.tsx b/dashboard/src/components/Sidebar.tsx new file mode 100644 index 0000000..d4fbb13 --- /dev/null +++ b/dashboard/src/components/Sidebar.tsx @@ -0,0 +1,54 @@ +import { Box, Image, Link, Text } from "@chakra-ui/react"; +import { NavLink, useLocation } from "react-router-dom"; +import React from "react"; +import { LOGO_URL } from "../constants"; + +const routes = [ + { + route: "/users", + name: "Users", + }, + { + route: "/settings", + name: "Settings", + }, +]; + +export const Sidebar = () => { + const { pathname } = useLocation(); + return ( + + + + + + Authorizer + + + + {routes.map(({ route, name }: any) => ( + + {name} + + ))} + + ); +}; diff --git a/dashboard/src/components/layouts/AuthLayout.tsx b/dashboard/src/components/layouts/AuthLayout.tsx deleted file mode 100644 index 0ba7800..0000000 --- a/dashboard/src/components/layouts/AuthLayout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export default function AuthLayout() { - return

Auth Layout

; -} diff --git a/dashboard/src/components/layouts/DefaultLayout.tsx b/dashboard/src/components/layouts/DefaultLayout.tsx deleted file mode 100644 index 505b6c5..0000000 --- a/dashboard/src/components/layouts/DefaultLayout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export default function DefaultLayout() { - return

Default Layout

; -} diff --git a/dashboard/src/constants.ts b/dashboard/src/constants.ts new file mode 100644 index 0000000..2a2eacc --- /dev/null +++ b/dashboard/src/constants.ts @@ -0,0 +1 @@ +export const LOGO_URL = "https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png" \ No newline at end of file diff --git a/dashboard/src/containers/AuthContainer.tsx b/dashboard/src/containers/AuthContainer.tsx new file mode 100644 index 0000000..e8aad71 --- /dev/null +++ b/dashboard/src/containers/AuthContainer.tsx @@ -0,0 +1,37 @@ +import { Center, Spinner } from "@chakra-ui/react"; +import React from "react"; +import { Navigate, Route, Routes } from "react-router-dom"; +import { useLocation } from "react-router-dom"; +import { useQuery } from "urql"; +import { AdminSessionQuery } from "../graphql/queries"; +import { hasAdminSecret } from "../utils"; + +export const AuthContainer = ({ children }: { children: any }) => { + const { pathname } = useLocation(); + const isOnboardingComplete = hasAdminSecret(); + const [result] = useQuery({ + query: AdminSessionQuery, + pause: !isOnboardingComplete, + }); + + if (result.fetching) { + return ( +
+ +
+ ); + } + + if ( + result?.error?.message.includes("unauthorized") && + pathname !== "/login" + ) { + return ; + } + + if (!isOnboardingComplete && pathname !== "/setup") { + return ; + } + + return children; +}; diff --git a/dashboard/src/contexts/AuthContext.tsx b/dashboard/src/contexts/AuthContext.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/dashboard/src/graphql/mutation/index.ts b/dashboard/src/graphql/mutation/index.ts new file mode 100644 index 0000000..01fcacc --- /dev/null +++ b/dashboard/src/graphql/mutation/index.ts @@ -0,0 +1,15 @@ +export const AdminSignup = ` + mutation adminSignup($secret: String!) { + _admin_signup (params: {admin_secret: $secret}) { + message + } + } +`; + +export const AdminLogin = ` +mutation adminLogin($secret: String!){ + _admin_login(params: { admin_secret: $secret }) { + message + } +} +` \ No newline at end of file diff --git a/dashboard/src/graphql/queries/index.ts b/dashboard/src/graphql/queries/index.ts new file mode 100644 index 0000000..837dbbc --- /dev/null +++ b/dashboard/src/graphql/queries/index.ts @@ -0,0 +1,7 @@ +export const AdminSessionQuery = ` + query { + _admin_session{ + message + } + } +`; diff --git a/dashboard/src/layouts/AuthLayout.tsx b/dashboard/src/layouts/AuthLayout.tsx new file mode 100644 index 0000000..10c73bd --- /dev/null +++ b/dashboard/src/layouts/AuthLayout.tsx @@ -0,0 +1,29 @@ +import { Box, Center, Flex, Image, Text } from "@chakra-ui/react"; +import React from "react"; +import { LOGO_URL } from "../constants"; + +export function AuthLayout({ children }: { children: React.ReactNode }) { + return ( + +
+ + + + Authorizer + +
+
+ {children} +
+
+ ); +} diff --git a/dashboard/src/layouts/DashboardLayout.tsx b/dashboard/src/layouts/DashboardLayout.tsx new file mode 100644 index 0000000..d3b663f --- /dev/null +++ b/dashboard/src/layouts/DashboardLayout.tsx @@ -0,0 +1,14 @@ +import { Box, Flex } from "@chakra-ui/react"; +import React from "react"; +import { Sidebar } from "../components/Sidebar"; + +export function DashboardLayout({ children }: { children: React.ReactNode }) { + return ( + + + + + {children} + + ); +} diff --git a/dashboard/src/pages/Auth.tsx b/dashboard/src/pages/Auth.tsx new file mode 100644 index 0000000..0db905f --- /dev/null +++ b/dashboard/src/pages/Auth.tsx @@ -0,0 +1,90 @@ +import { + Button, + FormControl, + FormLabel, + Input, + useToast, + VStack, +} from "@chakra-ui/react"; +import React, { useEffect } from "react"; +import { useMutation } from "urql"; +import { AuthLayout } from "../layouts/AuthLayout"; +import { AdminLogin, AdminSignup } from "../graphql/mutation"; +import { useLocation, useNavigate } from "react-router-dom"; + +export const Auth = () => { + const [loginResult, login] = useMutation(AdminLogin); + const [signUpResult, signup] = useMutation(AdminSignup); + + const toast = useToast(); + const navigate = useNavigate() + const { pathname } = useLocation(); + const isLogin = pathname === "/login"; + + const handleAdminSecret = (e: any) => { + e.preventDefault(); + const formValues = [...e.target.elements].reduce((agg: any, elem: any) => { + if (elem.id) { + return { + ...agg, + [elem.id]: elem.value, + }; + } + + return agg; + }, {}); + + (isLogin ? login : signup)({ + secret: formValues["admin-secret"], + }).then((res) => { + if (!res.error?.name) { + navigate("/"); + } + }); + }; + + const errors = isLogin ? loginResult.error : signUpResult.error; + + useEffect(() => { + if (errors?.graphQLErrors) { + (errors?.graphQLErrors || []).map((error: any) => { + toast({ + title: error.message, + isClosable: true, + status: "error", + position:"bottom-right" + }); + }) + } + }, [errors]) + + return ( + +
+ + + + {isLogin ? "Enter" : "Setup"} Admin Secret + + + + + +
+
+ ); +}; diff --git a/dashboard/src/pages/Home.tsx b/dashboard/src/pages/Home.tsx new file mode 100644 index 0000000..2fd4335 --- /dev/null +++ b/dashboard/src/pages/Home.tsx @@ -0,0 +1,6 @@ +import { Box } from "@chakra-ui/react"; +import React from "react"; + +export function Home() { + return Welcome to Authorizer dashboard!; +} diff --git a/dashboard/src/pages/Users.tsx b/dashboard/src/pages/Users.tsx new file mode 100644 index 0000000..3f97712 --- /dev/null +++ b/dashboard/src/pages/Users.tsx @@ -0,0 +1,6 @@ +import { Box } from "@chakra-ui/react"; +import React from "react"; + +export function Users() { + return users; +} diff --git a/dashboard/src/routes/index.tsx b/dashboard/src/routes/index.tsx new file mode 100644 index 0000000..3c1e2f9 --- /dev/null +++ b/dashboard/src/routes/index.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { Outlet, Route, Routes } from "react-router-dom"; +import { DashboardLayout } from "../layouts/DashboardLayout"; +import { Auth } from "../pages/Auth"; + +import { Home } from "../pages/Home"; +import { Users } from "../pages/Users"; + +export const AppRoutes = () => { + return ( + + } /> + } /> + + + + } + > + } /> + } /> + + + ); +}; diff --git a/dashboard/src/utils/index.ts b/dashboard/src/utils/index.ts new file mode 100644 index 0000000..cab1ebf --- /dev/null +++ b/dashboard/src/utils/index.ts @@ -0,0 +1,3 @@ +export const hasAdminSecret = () => { + return (window)["__authorizer__"].isOnboardingCompleted === true +} \ No newline at end of file