diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx
index 4effe54..24fe062 100644
--- a/dashboard/src/App.tsx
+++ b/dashboard/src/App.tsx
@@ -1,44 +1,44 @@
-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";
+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 { AuthContextProvider } from './contexts/AuthContext';
const queryClient = createClient({
- url: "/graphql",
- fetchOptions: () => {
- return {
- credentials: "include",
- };
- },
+ url: '/graphql',
+ fetchOptions: () => {
+ return {
+ credentials: 'include',
+ };
+ },
});
const theme = extendTheme({
- styles: {
- global: {
- "html, body, #root": {
- height: "100%",
- },
- },
- },
- colors: {
- blue: {
- 500: "rgb(59,130,246)",
- },
- },
+ styles: {
+ global: {
+ 'html, body, #root': {
+ height: '100%',
+ },
+ },
+ },
+ colors: {
+ blue: {
+ 500: 'rgb(59,130,246)',
+ },
+ },
});
export default function App() {
- return (
-
-
-
-
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+
+
+
+ );
}
diff --git a/dashboard/src/components/Sidebar.tsx b/dashboard/src/components/Sidebar.tsx
index d4fbb13..ab82762 100644
--- a/dashboard/src/components/Sidebar.tsx
+++ b/dashboard/src/components/Sidebar.tsx
@@ -1,54 +1,78 @@
-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";
+import { Box, Image, Link, Text, Button } from '@chakra-ui/react';
+import { NavLink, useLocation, useNavigate } from 'react-router-dom';
+import React from 'react';
+import { LOGO_URL } from '../constants';
+import { useMutation } from 'urql';
+import { AdminLogout } from '../graphql/mutation';
+import { useAuthContext } from '../contexts/AuthContext';
const routes = [
- {
- route: "/users",
- name: "Users",
- },
- {
- route: "/settings",
- name: "Settings",
- },
+ {
+ route: '/users',
+ name: 'Users',
+ },
+ {
+ route: '/',
+ name: 'Environment Variable',
+ },
];
export const Sidebar = () => {
- const { pathname } = useLocation();
- return (
-
-
-
-
-
- Authorizer
-
-
-
- {routes.map(({ route, name }: any) => (
-
- {name}
-
- ))}
-
- );
+ const { pathname } = useLocation();
+ const [_, logout] = useMutation(AdminLogout);
+ const { setIsLoggedIn } = useAuthContext();
+ const navigate = useNavigate();
+
+ const handleLogout = async () => {
+ await logout();
+ setIsLoggedIn(false);
+ navigate('/', { replace: true });
+ };
+
+ 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/containers/AuthContainer.tsx b/dashboard/src/containers/AuthContainer.tsx
deleted file mode 100644
index e8aad71..0000000
--- a/dashboard/src/containers/AuthContainer.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-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
index e69de29..6a1c57e 100644
--- a/dashboard/src/contexts/AuthContext.tsx
+++ b/dashboard/src/contexts/AuthContext.tsx
@@ -0,0 +1,49 @@
+import React, { createContext, useState, useContext, useEffect } from 'react';
+import { Center, Spinner } from '@chakra-ui/react';
+import { useQuery } from 'urql';
+import { useLocation, useNavigate } from 'react-router-dom';
+
+import { AdminSessionQuery } from '../graphql/queries';
+import { hasAdminSecret } from '../utils';
+
+const AuthContext = createContext({
+ isLoggedIn: false,
+ setIsLoggedIn: (data: boolean) => {},
+});
+
+export const AuthContextProvider = ({ children }: { children: any }) => {
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+
+ const { pathname } = useLocation();
+ const navigate = useNavigate();
+
+ const isOnboardingComplete = hasAdminSecret();
+ const [{ fetching, data, error }] = useQuery({
+ query: AdminSessionQuery,
+ });
+
+ useEffect(() => {
+ if (!fetching && !error) {
+ setIsLoggedIn(true);
+ if (pathname === '/login' || pathname === 'signup') {
+ navigate('/', { replace: true });
+ }
+ }
+ }, [fetching, error]);
+
+ if (fetching) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useAuthContext = () => useContext(AuthContext);
diff --git a/dashboard/src/graphql/mutation/index.ts b/dashboard/src/graphql/mutation/index.ts
index 01fcacc..1f070f2 100644
--- a/dashboard/src/graphql/mutation/index.ts
+++ b/dashboard/src/graphql/mutation/index.ts
@@ -12,4 +12,12 @@ mutation adminLogin($secret: String!){
message
}
}
-`
\ No newline at end of file
+`;
+
+export const AdminLogout = `
+ mutation adminLogout {
+ _admin_logout {
+ message
+ }
+ }
+`;
diff --git a/dashboard/src/layouts/AuthLayout.tsx b/dashboard/src/layouts/AuthLayout.tsx
index 10c73bd..4ce69e5 100644
--- a/dashboard/src/layouts/AuthLayout.tsx
+++ b/dashboard/src/layouts/AuthLayout.tsx
@@ -1,29 +1,26 @@
-import { Box, Center, Flex, Image, Text } from "@chakra-ui/react";
-import React from "react";
-import { LOGO_URL } from "../constants";
+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 (
-
-
-
+ return (
+
+
+
-
- Authorizer
-
-
-
- {children}
-
-
- );
+
+ Authorizer
+
+
+
+ {children}
+
+
+ );
}
diff --git a/dashboard/src/layouts/DashboardLayout.tsx b/dashboard/src/layouts/DashboardLayout.tsx
index d3b663f..03ce24f 100644
--- a/dashboard/src/layouts/DashboardLayout.tsx
+++ b/dashboard/src/layouts/DashboardLayout.tsx
@@ -1,14 +1,16 @@
-import { Box, Flex } from "@chakra-ui/react";
-import React from "react";
-import { Sidebar } from "../components/Sidebar";
+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}
-
- );
+ return (
+
+
+
+
+
+ {children}
+
+
+ );
}
diff --git a/dashboard/src/pages/Auth.tsx b/dashboard/src/pages/Auth.tsx
index 0db905f..f8f7574 100644
--- a/dashboard/src/pages/Auth.tsx
+++ b/dashboard/src/pages/Auth.tsx
@@ -1,90 +1,95 @@
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";
+ 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 { useNavigate } from 'react-router-dom';
+import { useAuthContext } from '../contexts/AuthContext';
+import { capitalizeFirstLetter, hasAdminSecret } from '../utils';
export const Auth = () => {
- const [loginResult, login] = useMutation(AdminLogin);
- const [signUpResult, signup] = useMutation(AdminSignup);
+ const [loginResult, login] = useMutation(AdminLogin);
+ const [signUpResult, signup] = useMutation(AdminSignup);
+ const { setIsLoggedIn } = useAuthContext();
- const toast = useToast();
- const navigate = useNavigate()
- const { pathname } = useLocation();
- const isLogin = pathname === "/login";
+ const toast = useToast();
+ const navigate = useNavigate();
+ const isLogin = hasAdminSecret();
- 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,
- };
- }
+ const handleSubmit = (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;
- }, {});
+ return agg;
+ }, {});
- (isLogin ? login : signup)({
- secret: formValues["admin-secret"],
- }).then((res) => {
- if (!res.error?.name) {
- navigate("/");
- }
- });
- };
+ (isLogin ? login : signup)({
+ secret: formValues['admin-secret'],
+ }).then((res) => {
+ setIsLoggedIn(true);
+ if (res.data) {
+ navigate('/', { replace: true });
+ }
+ });
+ };
- const errors = isLogin ? loginResult.error : signUpResult.error;
+ 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])
+ useEffect(() => {
+ if (errors?.graphQLErrors) {
+ (errors?.graphQLErrors || []).map((error: any) => {
+ toast({
+ title: capitalizeFirstLetter(error.message),
+ isClosable: true,
+ status: 'error',
+ position: 'bottom-right',
+ });
+ });
+ }
+ }, [errors]);
- return (
-
-
-
- );
+ return (
+
+
+
+ );
};
diff --git a/dashboard/src/routes/index.tsx b/dashboard/src/routes/index.tsx
index 3c1e2f9..a967160 100644
--- a/dashboard/src/routes/index.tsx
+++ b/dashboard/src/routes/index.tsx
@@ -1,26 +1,34 @@
-import React from "react";
-import { Outlet, Route, Routes } from "react-router-dom";
-import { DashboardLayout } from "../layouts/DashboardLayout";
-import { Auth } from "../pages/Auth";
+import React from 'react';
+import { Outlet, Route, Routes } from 'react-router-dom';
-import { Home } from "../pages/Home";
-import { Users } from "../pages/Users";
+import { useAuthContext } from '../contexts/AuthContext';
+import { DashboardLayout } from '../layouts/DashboardLayout';
+import { Auth } from '../pages/Auth';
+import { Home } from '../pages/Home';
+import { Users } from '../pages/Users';
export const AppRoutes = () => {
- return (
-
- } />
- } />
-
-
-
- }
- >
- } />
- } />
-
-
- );
+ const { isLoggedIn } = useAuthContext();
+
+ if (isLoggedIn) {
+ return (
+
+
+
+
+ }
+ >
+ } />
+ } />
+
+
+ );
+ }
+ return (
+
+ } />
+
+ );
};
diff --git a/dashboard/src/utils/index.ts b/dashboard/src/utils/index.ts
index cab1ebf..465d658 100644
--- a/dashboard/src/utils/index.ts
+++ b/dashboard/src/utils/index.ts
@@ -1,3 +1,6 @@
export const hasAdminSecret = () => {
- return (window)["__authorizer__"].isOnboardingCompleted === true
-}
\ No newline at end of file
+ return (window)['__authorizer__'].isOnboardingCompleted === true;
+};
+
+export const capitalizeFirstLetter = (data: string): string =>
+ data.charAt(0).toUpperCase() + data.slice(1);
diff --git a/server/routes/routes.go b/server/routes/routes.go
index fdab544..0a8f938 100644
--- a/server/routes/routes.go
+++ b/server/routes/routes.go
@@ -39,6 +39,7 @@ func InitRouter() *gin.Engine {
{
app.Static("/build", "dashboard/build")
app.GET("/", handlers.DashboardHandler())
+ app.GET("/:page", handlers.DashboardHandler())
}
return router
}