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 (
+
+
+
+ );
+};
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