fix(dashboard): navigation issues

This commit is contained in:
Lakhan Samani 2022-01-17 13:03:28 +05:30
parent f1b4141367
commit a596d91ce0
13 changed files with 320 additions and 270 deletions

View File

@ -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>

View File

@ -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>
);
};

View File

@ -1,5 +0,0 @@
import React from 'react';
export default function AuthLayout() {
return <h1>Auth Layout</h1>;
}

View File

@ -1,5 +0,0 @@
import React from 'react';
export default function DefaultLayout() {
return <h1>Default Layout</h1>;
}

View File

@ -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;
};

View File

@ -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);

View File

@ -12,4 +12,12 @@ mutation adminLogin($secret: String!){
message
}
}
`
`;
export const AdminLogout = `
mutation adminLogout {
_admin_logout {
message
}
}
`;

View File

@ -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"

View File

@ -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>
);
}

View File

@ -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>

View File

@ -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>
);
};

View File

@ -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);

View File

@ -39,6 +39,7 @@ func InitRouter() *gin.Engine {
{
app.Static("/build", "dashboard/build")
app.GET("/", handlers.DashboardHandler())
app.GET("/:page", handlers.DashboardHandler())
}
return router
}