fix(dashboard): navigation issues
This commit is contained in:
parent
f1b4141367
commit
a596d91ce0
|
@ -1,15 +1,15 @@
|
|||
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",
|
||||
url: '/graphql',
|
||||
fetchOptions: () => {
|
||||
return {
|
||||
credentials: "include",
|
||||
credentials: 'include',
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -17,14 +17,14 @@ const queryClient = createClient({
|
|||
const theme = extendTheme({
|
||||
styles: {
|
||||
global: {
|
||||
"html, body, #root": {
|
||||
height: "100%",
|
||||
'html, body, #root': {
|
||||
height: '100%',
|
||||
},
|
||||
},
|
||||
},
|
||||
colors: {
|
||||
blue: {
|
||||
500: "rgb(59,130,246)",
|
||||
500: 'rgb(59,130,246)',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -34,9 +34,9 @@ export default function App() {
|
|||
<ChakraProvider theme={theme}>
|
||||
<Provider value={queryClient}>
|
||||
<BrowserRouter basename="/dashboard">
|
||||
<AuthContainer>
|
||||
<AuthContextProvider>
|
||||
<AppRoutes />
|
||||
</AuthContainer>
|
||||
</AuthContextProvider>
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
</ChakraProvider>
|
||||
|
|
|
@ -1,21 +1,34 @@
|
|||
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: '/users',
|
||||
name: 'Users',
|
||||
},
|
||||
{
|
||||
route: "/settings",
|
||||
name: "Settings",
|
||||
route: '/',
|
||||
name: 'Environment Variable',
|
||||
},
|
||||
];
|
||||
|
||||
export const Sidebar = () => {
|
||||
const { pathname } = useLocation();
|
||||
const [_, logout] = useMutation(AdminLogout);
|
||||
const { setIsLoggedIn } = useAuthContext();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setIsLoggedIn(false);
|
||||
navigate('/', { replace: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<Box as="nav" h="100%">
|
||||
<NavLink to="/">
|
||||
|
@ -34,12 +47,12 @@ export const Sidebar = () => {
|
|||
</NavLink>
|
||||
{routes.map(({ route, name }: any) => (
|
||||
<Link
|
||||
color={pathname === route ? "blue.500" : "white"}
|
||||
color={pathname === route ? 'blue.500' : 'white'}
|
||||
transition="all ease-in 0.2s"
|
||||
_hover={{ color: pathname === route ? "blue.200" : "whiteAlpha.700" }}
|
||||
_hover={{ color: pathname === route ? 'blue.200' : 'whiteAlpha.700' }}
|
||||
px="4"
|
||||
py="2"
|
||||
bg={pathname === route ? "white" : ""}
|
||||
bg={pathname === route ? 'white' : ''}
|
||||
fontWeight="bold"
|
||||
display="block"
|
||||
as={NavLink}
|
||||
|
@ -49,6 +62,17 @@ export const Sidebar = () => {
|
|||
{name}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
<Box
|
||||
as="div"
|
||||
w="100%"
|
||||
position="absolute"
|
||||
bottom="5"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Button onClick={handleLogout}>Logout</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function AuthLayout() {
|
||||
return <h1>Auth Layout</h1>;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function DefaultLayout() {
|
||||
return <h1>Default Layout</h1>;
|
||||
}
|
|
@ -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 (
|
||||
<Center>
|
||||
<Spinner />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
result?.error?.message.includes("unauthorized") &&
|
||||
pathname !== "/login"
|
||||
) {
|
||||
return <Navigate to="login" />;
|
||||
}
|
||||
|
||||
if (!isOnboardingComplete && pathname !== "/setup") {
|
||||
return <Navigate to="setup" />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
|
@ -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 (
|
||||
<Center>
|
||||
<Spinner />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ isLoggedIn, setIsLoggedIn }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAuthContext = () => useContext(AuthContext);
|
|
@ -12,4 +12,12 @@ mutation adminLogin($secret: String!){
|
|||
message
|
||||
}
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
export const AdminLogout = `
|
||||
mutation adminLogout {
|
||||
_admin_logout {
|
||||
message
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
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 (
|
||||
<Flex flexWrap="wrap" h="100%">
|
||||
<Center h="100%" flex="3" bg="blue.500" flexDirection="column">
|
||||
<Image
|
||||
src={LOGO_URL}
|
||||
alt=""
|
||||
/>
|
||||
<Center h="100%" flex="2" bg="blue.500" flexDirection="column">
|
||||
<Image src={LOGO_URL} alt="" />
|
||||
|
||||
<Text
|
||||
color="white"
|
||||
|
|
|
@ -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 (
|
||||
<Flex flexWrap="wrap" h="100%">
|
||||
<Box maxW="72" bg="blue.500" flex="1">
|
||||
<Box w="72" bg="blue.500" flex="1" position="fixed" h="100vh">
|
||||
<Sidebar />
|
||||
</Box>
|
||||
<Box as="main" flex="2" p="10">{children}</Box>
|
||||
<Box as="main" flex="2" p="10" marginLeft="72">
|
||||
{children}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,23 +5,26 @@ import {
|
|||
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";
|
||||
} 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 { setIsLoggedIn } = useAuthContext();
|
||||
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate()
|
||||
const { pathname } = useLocation();
|
||||
const isLogin = pathname === "/login";
|
||||
const navigate = useNavigate();
|
||||
const isLogin = hasAdminSecret();
|
||||
|
||||
const handleAdminSecret = (e: any) => {
|
||||
const handleSubmit = (e: any) => {
|
||||
e.preventDefault();
|
||||
const formValues = [...e.target.elements].reduce((agg: any, elem: any) => {
|
||||
if (elem.id) {
|
||||
|
@ -35,10 +38,11 @@ export const Auth = () => {
|
|||
}, {});
|
||||
|
||||
(isLogin ? login : signup)({
|
||||
secret: formValues["admin-secret"],
|
||||
secret: formValues['admin-secret'],
|
||||
}).then((res) => {
|
||||
if (!res.error?.name) {
|
||||
navigate("/");
|
||||
setIsLoggedIn(true);
|
||||
if (res.data) {
|
||||
navigate('/', { replace: true });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -49,28 +53,29 @@ export const Auth = () => {
|
|||
if (errors?.graphQLErrors) {
|
||||
(errors?.graphQLErrors || []).map((error: any) => {
|
||||
toast({
|
||||
title: error.message,
|
||||
title: capitalizeFirstLetter(error.message),
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
position:"bottom-right"
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
}, [errors])
|
||||
}, [errors]);
|
||||
|
||||
return (
|
||||
<AuthLayout>
|
||||
<form onSubmit={handleAdminSecret}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<VStack spacing="2.5" justify="space-between">
|
||||
<FormControl isRequired>
|
||||
<FormLabel htmlFor="admin-secret">
|
||||
{isLogin ? "Enter" : "Setup"} Admin Secret
|
||||
{isLogin ? 'Enter' : 'Setup'} Admin Secret
|
||||
</FormLabel>
|
||||
<Input
|
||||
size="lg"
|
||||
id="admin-secret"
|
||||
placeholder="Admin secret"
|
||||
minLength={6}
|
||||
type="password"
|
||||
minLength={!isLogin ? 6 : 1}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
|
@ -81,7 +86,7 @@ export const Auth = () => {
|
|||
d="block"
|
||||
type="submit"
|
||||
>
|
||||
{isLogin ? "Login" : "Sign up"}
|
||||
{isLogin ? 'Login' : 'Sign up'}
|
||||
</Button>
|
||||
</VStack>
|
||||
</form>
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
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 = () => {
|
||||
const { isLoggedIn } = useAuthContext();
|
||||
|
||||
if (isLoggedIn) {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="login" element={<Auth />} />
|
||||
<Route path="setup" element={<Auth />} />
|
||||
<Route
|
||||
element={
|
||||
<DashboardLayout>
|
||||
|
@ -23,4 +25,10 @@ export const AppRoutes = () => {
|
|||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Auth />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
export const hasAdminSecret = () => {
|
||||
return (<any>window)["__authorizer__"].isOnboardingCompleted === true
|
||||
}
|
||||
return (<any>window)['__authorizer__'].isOnboardingCompleted === true;
|
||||
};
|
||||
|
||||
export const capitalizeFirstLetter = (data: string): string =>
|
||||
data.charAt(0).toUpperCase() + data.slice(1);
|
||||
|
|
|
@ -39,6 +39,7 @@ func InitRouter() *gin.Engine {
|
|||
{
|
||||
app.Static("/build", "dashboard/build")
|
||||
app.GET("/", handlers.DashboardHandler())
|
||||
app.GET("/:page", handlers.DashboardHandler())
|
||||
}
|
||||
return router
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user