.
This commit is contained in:
parent
6cb7b831e8
commit
d0ccc20a1c
@ -1,11 +1,9 @@
|
||||
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||
import { ProtectedRoute } from "./components";
|
||||
import { AuthContextState, useAuth } from "./lib/hooks";
|
||||
import { DashboardPage, LogoutPage } from "./pages";
|
||||
import { LoginPage } from "./pages/LoginPage";
|
||||
|
||||
export const Routes = () => {
|
||||
const { isLoggedIn } = useAuth() as AuthContextState;
|
||||
|
||||
// Define public routes accessible to all users
|
||||
const routesForPublic = [
|
||||
{
|
||||
@ -20,6 +18,22 @@ export const Routes = () => {
|
||||
|
||||
// Define routes accessible only to authenticated users
|
||||
const routesForAuthenticatedOnly = [
|
||||
{
|
||||
path: "/profile",
|
||||
element: (
|
||||
<ProtectedRoute>
|
||||
<h1>Profile</h1>
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/logout",
|
||||
element: (
|
||||
<ProtectedRoute>
|
||||
<LogoutPage />
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/admin",
|
||||
element: <ProtectedRoute />, // Wrap the component in ProtectedRoute
|
||||
@ -28,14 +42,6 @@ export const Routes = () => {
|
||||
path: "",
|
||||
element: <div>Dashboard</div>,
|
||||
},
|
||||
{
|
||||
path: "profile",
|
||||
element: <div>User Profile</div>,
|
||||
},
|
||||
{
|
||||
path: "logout",
|
||||
element: <div>Logout</div>,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@ -44,7 +50,7 @@ export const Routes = () => {
|
||||
const routesForNotAuthenticatedOnly = [
|
||||
{
|
||||
path: "/",
|
||||
element: <div>Home Page</div>,
|
||||
Component: DashboardPage,
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
@ -55,7 +61,7 @@ export const Routes = () => {
|
||||
// Combine and conditionally include routes based on authentication status
|
||||
const router = createBrowserRouter([
|
||||
...routesForPublic,
|
||||
...(!isLoggedIn ? routesForNotAuthenticatedOnly : []),
|
||||
...routesForNotAuthenticatedOnly,
|
||||
...routesForAuthenticatedOnly,
|
||||
]);
|
||||
|
||||
|
||||
@ -1,28 +1,30 @@
|
||||
import { useIsLoggedIn } from "@/lib/hooks";
|
||||
import React from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { LoadingOverlay } from "../LoadingOverlay";
|
||||
|
||||
type ProctectRouteProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const ProtectedRoute = ({ children }: ProctectRouteProps) => {
|
||||
const { isSuccess, data } = useIsLoggedIn();
|
||||
const { isPending, isSuccess, data: { authenticated, redirectTo } = {} } = useIsLoggedIn();
|
||||
|
||||
if (isSuccess && data) {
|
||||
const { authenticated, redirectTo } = data;
|
||||
if (authenticated) {
|
||||
return (
|
||||
<Navigate
|
||||
to={redirectTo ?? "/login"}
|
||||
replace
|
||||
state={{
|
||||
error: "No authentication, please complete the login process.",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (isPending) {
|
||||
return <LoadingOverlay />;
|
||||
}
|
||||
|
||||
return children;
|
||||
if (isSuccess && !authenticated) {
|
||||
return (
|
||||
<Navigate
|
||||
to={redirectTo ?? "/login"}
|
||||
replace
|
||||
state={{
|
||||
error: "No authentication, please complete the login process.",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children ?? null}</>;
|
||||
};
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
export const UeckoLogo = ({ className }: { className: string }) => (
|
||||
<svg viewBox='0 0 336 133' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>
|
||||
export const UeckoLogo = ({ className, ...props }: { className: string }) => (
|
||||
<svg
|
||||
viewBox='0 0 336 100'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
className={className}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d='M49.7002 83.0001H66.9002V22.5001H49.7002V56.2001C49.7002 64.3001 45.5002 68.5001 39.0002 68.5001C32.5002 68.5001 28.6002 64.3001 28.6002 56.2001V22.5001H0.700195V33.2001H11.4002V61.6001C11.4002 75.5001 19.0002 84.1001 31.9002 84.1001C40.6002 84.1001 45.7002 79.5001 49.6002 74.4001V83.0001H49.7002ZM120.6 48.0001H94.8002C96.2002 40.2001 100.8 35.1001 107.9 35.1001C115.1 35.2001 119.6 40.3001 120.6 48.0001ZM137.1 58.7001C137.2 57.1001 137.3 56.1001 137.3 54.4001V54.2001C137.3 37.0001 128 21.4001 107.8 21.4001C90.2002 21.4001 77.9002 35.6001 77.9002 52.9001V53.1001C77.9002 71.6001 91.3002 84.4001 109.5 84.4001C120.4 84.4001 128.6 80.1001 134.2 73.1001L124.4 64.4001C119.7 68.8001 115.5 70.6001 109.7 70.6001C102 70.6001 96.6002 66.5001 94.9002 58.7001H137.1ZM162.2 52.9001V52.7001C162.2 43.8001 168.3 36.2001 176.9 36.2001C183 36.2001 186.8 38.8001 190.7 42.9001L201.2 31.6001C195.6 25.3001 188.4 21.4001 177 21.4001C158.5 21.4001 145.3 35.6001 145.3 52.9001V53.1001C145.3 70.4001 158.6 84.4001 176.8 84.4001C188.9 84.4001 195.6 79.8001 201.5 73.3001L191.5 63.1001C187.3 67.1001 183.4 69.5001 177.6 69.5001C168.2 69.6001 162.2 62.1001 162.2 52.9001ZM269.1 83.0001L245.3 46.3001L268.3 22.5001H247.8L227.7 44.5001V0.600098H210.5V83.0001H227.7V64.6001L233.7 58.3001L249.5 83.0001H269.1ZM318.5 53.1001C318.5 62.0001 312.6 69.6001 302.8 69.6001C293.3 69.6001 286.9 61.8001 286.9 52.9001V52.7001C286.9 43.8001 292.8 36.2001 302.6 36.2001C312.1 36.2001 318.5 44.0001 318.5 52.9001V53.1001ZM335.4 52.9001V52.7001C335.4 35.3001 321.5 21.4001 302.8 21.4001C284 21.4001 270 35.5001 270 52.9001V53.1001C270 70.5001 283.9 84.4001 302.6 84.4001C321.4 84.4001 335.4 70.3001 335.4 52.9001Z'
|
||||
fill='black'
|
||||
|
||||
35
client/src/lib/hooks/useAuth/useLogout.tsx
Normal file
35
client/src/lib/hooks/useAuth/useLogout.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { AuthActionResponse, useAuth } from "@/lib/hooks";
|
||||
import { UseMutationOptions, useMutation } from "@tanstack/react-query";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { useQueryKey } from "../useQueryKey";
|
||||
|
||||
export const useLogout = (params?: UseMutationOptions<AuthActionResponse, Error>) => {
|
||||
const { onSuccess, onError, ...restParams } = params || {};
|
||||
const keys = useQueryKey();
|
||||
const { logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: keys().auth().action("logout").get(),
|
||||
mutationFn: logout,
|
||||
onSuccess: async (data, variables, context) => {
|
||||
const { success, redirectTo } = data;
|
||||
if (success && redirectTo) {
|
||||
navigate(redirectTo);
|
||||
}
|
||||
if (onSuccess) {
|
||||
onSuccess(data, variables, context);
|
||||
}
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
const { message } = error;
|
||||
toast.error(message);
|
||||
|
||||
if (onError) {
|
||||
onError(error, variables, context);
|
||||
}
|
||||
},
|
||||
...restParams,
|
||||
});
|
||||
};
|
||||
232
client/src/pages/DashboardPage/DashboardPage.tsx
Normal file
232
client/src/pages/DashboardPage/DashboardPage.tsx
Normal file
@ -0,0 +1,232 @@
|
||||
import { CircleUser, LogOutIcon, Menu, Package2Icon, Search, UserIcon } from "lucide-react";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Checkbox,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
Input,
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetTrigger,
|
||||
} from "@/ui";
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/ui";
|
||||
|
||||
import { UeckoLogo } from "@/components/UeckoLogo/UeckoLogo";
|
||||
import { SyntheticEvent, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export const DashboardPage = () => {
|
||||
const [logoutAlertVisible, setLogoutAlertVisible] = useState<boolean>(false);
|
||||
|
||||
const openLogoutAlert = (event: Event) => {
|
||||
event.preventDefault();
|
||||
setLogoutAlertVisible(true);
|
||||
return;
|
||||
};
|
||||
|
||||
const closeLogoutAlert = (event: SyntheticEvent) => {
|
||||
event.preventDefault();
|
||||
setLogoutAlertVisible(false);
|
||||
return;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='flex flex-col w-full min-h-screen'>
|
||||
<header className='sticky top-0 flex items-center h-16 gap-4 px-4 border-b bg-background md:px-6'>
|
||||
<nav className='flex-col hidden gap-6 text-lg font-medium md:flex md:flex-row md:items-center md:gap-5 md:text-sm lg:gap-6'>
|
||||
<Link to='/' className='flex items-center font-semibold'>
|
||||
<UeckoLogo className='w-24' />
|
||||
<span className='sr-only'>Uecko</span>
|
||||
</Link>
|
||||
<Link to='#' className='transition-colors text-muted-foreground hover:text-foreground'>
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link to='#' className='transition-colors text-muted-foreground hover:text-foreground'>
|
||||
Orders
|
||||
</Link>
|
||||
<Link to='#' className='transition-colors text-muted-foreground hover:text-foreground'>
|
||||
Products
|
||||
</Link>
|
||||
<Link to='#' className='transition-colors text-muted-foreground hover:text-foreground'>
|
||||
Customers
|
||||
</Link>
|
||||
<Link to='#' className='transition-colors text-foreground hover:text-foreground'>
|
||||
Settings
|
||||
</Link>
|
||||
</nav>
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant='outline' size='icon' className='shrink-0 md:hidden'>
|
||||
<Menu className='w-5 h-5' />
|
||||
<span className='sr-only'>Toggle navigation menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side='left'>
|
||||
<nav className='grid gap-6 text-lg font-medium'>
|
||||
<Link to='#' className='flex items-center gap-2 text-lg font-semibold'>
|
||||
<Package2Icon className='w-6 h-6' />
|
||||
<span className='sr-only'>Acme Inc</span>
|
||||
</Link>
|
||||
<Link to='#' className='text-muted-foreground hover:text-foreground'>
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link to='#' className='text-muted-foreground hover:text-foreground'>
|
||||
Orders
|
||||
</Link>
|
||||
<Link to='#' className='text-muted-foreground hover:text-foreground'>
|
||||
Products
|
||||
</Link>
|
||||
<Link to='#' className='text-muted-foreground hover:text-foreground'>
|
||||
Customers
|
||||
</Link>
|
||||
<Link to='#' className='hover:text-foreground'>
|
||||
Settings
|
||||
</Link>
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<div className='flex items-center w-full gap-4 md:ml-auto md:gap-2 lg:gap-4'>
|
||||
<form className='flex-1 ml-auto sm:flex-initial'>
|
||||
<div className='relative'>
|
||||
<Search className='absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground' />
|
||||
<Input
|
||||
type='search'
|
||||
placeholder='Search products...'
|
||||
className='pl-8 sm:w-[300px] md:w-[200px] lg:w-[300px]'
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant='secondary' size='icon' className='rounded-full'>
|
||||
<CircleUser className='w-5 h-5' />
|
||||
<span className='sr-only'>Toggle user menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='end' className='w-56'>
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<UserIcon className='w-4 h-4 mr-2' />
|
||||
<span>Profile</span>
|
||||
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem>Support</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onSelect={openLogoutAlert}>
|
||||
<LogOutIcon className='w-4 h-4 mr-2' />
|
||||
<span>Log out</span>
|
||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</header>
|
||||
<main className='flex min-h-[calc(100vh_-_theme(spacing.16))] flex-1 flex-col gap-4 bg-muted/40 p-4 md:gap-8 md:p-10'>
|
||||
<div className='grid w-full max-w-6xl gap-2 mx-auto'>
|
||||
<h1 className='text-3xl font-semibold'>Settings</h1>
|
||||
</div>
|
||||
<div className='mx-auto grid w-full max-w-6xl items-start gap-6 md:grid-cols-[180px_1fr] lg:grid-cols-[250px_1fr]'>
|
||||
<nav className='grid gap-4 text-sm text-muted-foreground' x-chunk='dashboard-04-chunk-0'>
|
||||
<Link to='#' className='font-semibold text-primary'>
|
||||
General
|
||||
</Link>
|
||||
<Link to='#'>Security</Link>
|
||||
<Link to='#'>Integrations</Link>
|
||||
<Link to='#'>Support</Link>
|
||||
<Link to='#'>Organizations</Link>
|
||||
<Link to='#'>Advanced</Link>
|
||||
</nav>
|
||||
<div className='grid gap-6'>
|
||||
<Card x-chunk='dashboard-04-chunk-1'>
|
||||
<CardHeader>
|
||||
<CardTitle>Store Name</CardTitle>
|
||||
<CardDescription>Used to identify your store in the marketplace.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form>
|
||||
<Input placeholder='Store Name' />
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className='px-6 py-4 border-t'>
|
||||
<Button>Save</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card x-chunk='dashboard-04-chunk-2'>
|
||||
<CardHeader>
|
||||
<CardTitle>Plugins Directory</CardTitle>
|
||||
<CardDescription>
|
||||
The directory within your project, in which your plugins are located.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form className='flex flex-col gap-4'>
|
||||
<Input placeholder='Project Name' defaultValue='/content/plugins' />
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Checkbox id='include' defaultChecked />
|
||||
<label
|
||||
htmlFor='include'
|
||||
className='text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
|
||||
>
|
||||
Allow administrators to change the directory.
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className='px-6 py-4 border-t'>
|
||||
<Button>Save</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<AlertDialog open={logoutAlertVisible}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete your account and
|
||||
remove your data from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
<Link to='#' onClick={closeLogoutAlert}>
|
||||
Cancel
|
||||
</Link>
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction>
|
||||
<Link to='/logout'>Continue</Link>
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
1
client/src/pages/DashboardPage/index.ts
Normal file
1
client/src/pages/DashboardPage/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./DashboardPage";
|
||||
@ -1,12 +1,11 @@
|
||||
// - - - - - ErrorPage.tsx - - - - -
|
||||
import { Button } from "@/ui";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
// import Button
|
||||
|
||||
const isDev = process.env.REACT_APP_NODE_ENV === "development";
|
||||
/*const isDev = process.env.REACT_APP_NODE_ENV === "development";
|
||||
const hostname = `${
|
||||
isDev ? process.env.REACT_APP_DEV_API_URL : process.env.REACT_APP_PROD_API_URL
|
||||
}`;
|
||||
}`;*/
|
||||
|
||||
type ErrorPageProps = {
|
||||
error?: string;
|
||||
@ -39,8 +38,8 @@ export const ErrorPage = (props: ErrorPageProps) => {
|
||||
<Button
|
||||
id='logout'
|
||||
onClick={() => {
|
||||
const endpoint = `${hostname}/logout`;
|
||||
window.open(endpoint, "_blank");
|
||||
//const endpoint = `${hostname}/logout`;
|
||||
//window.open(endpoint, "_blank");
|
||||
}}
|
||||
>
|
||||
Reset Authentication
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
Form,
|
||||
} from "@/ui";
|
||||
import { joiResolver } from "@hookform/resolvers/joi";
|
||||
import { ILogin_DTO } from "@shared/contexts";
|
||||
import Joi from "joi";
|
||||
import { AlertCircleIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
@ -21,10 +22,7 @@ import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import { Link } from "react-router-dom";
|
||||
import SpanishJoiMessages from "../spanish-joi-messages.json";
|
||||
|
||||
type LoginDataForm = {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
type LoginDataForm = ILogin_DTO;
|
||||
|
||||
export const LoginPage = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
13
client/src/pages/LogoutPage.tsx
Normal file
13
client/src/pages/LogoutPage.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { LoadingOverlay } from "@/components";
|
||||
import { useLogout } from "@/lib/hooks/useAuth/useLogout";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export const LogoutPage = () => {
|
||||
const { mutate: logout } = useLogout({});
|
||||
|
||||
useEffect(() => {
|
||||
return logout();
|
||||
}, [logout]);
|
||||
|
||||
return <LoadingOverlay />;
|
||||
};
|
||||
4
client/src/pages/index.ts
Normal file
4
client/src/pages/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./DashboardPage";
|
||||
export * from "./ErrorPage";
|
||||
export * from "./LoginPage";
|
||||
export * from "./LogoutPage";
|
||||
Loading…
Reference in New Issue
Block a user