feat(backoffice): authentication middleware, error pages, and 404 page
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="light">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Backoffice</title>
|
||||
|
||||
@@ -23,7 +23,7 @@ export const AppLayout: FC = (): ReactElement => {
|
||||
{/* Mobile menu button (shown on small screens) */}
|
||||
<button
|
||||
type="button"
|
||||
className="lg:hidden p-2 rounded-md hover:bg-gray-100 text-gray-700"
|
||||
className="lg:hidden p-2 rounded-md hover:bg-gray-100 text-gray-700 cursor-pointer"
|
||||
onClick={() => setMobileSidebarOpen(true)}
|
||||
aria-label="Open sidebar"
|
||||
>
|
||||
|
||||
@@ -2,22 +2,39 @@ import { useForm } from 'react-hook-form';
|
||||
import {
|
||||
authLoginSchema,
|
||||
TLoginRequest,
|
||||
useBackofficeLogin,
|
||||
} from '@imphnen-frontend-service/service';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useSession } from '@imphnen-frontend-service/utils';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export const useLogin = () => {
|
||||
const navigate = useNavigate();
|
||||
const loginMutation = useBackofficeLogin();
|
||||
|
||||
const form = useForm<TLoginRequest>({
|
||||
resolver: zodResolver(authLoginSchema),
|
||||
mode: 'all',
|
||||
defaultValues: {
|
||||
email: '',
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { signIn } = useSession();
|
||||
|
||||
const onSubmit = form.handleSubmit((data) => signIn(data));
|
||||
const onSubmit = form.handleSubmit(async (data) => {
|
||||
try {
|
||||
await loginMutation.mutateAsync(data);
|
||||
toast.success('Login berhasil!');
|
||||
navigate('/hackathon-dashboard');
|
||||
} catch (error) {
|
||||
console.error('[Backoffice Login] Error:', error);
|
||||
toast.error((error as Error).message || 'Login gagal');
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
onSubmit,
|
||||
isLoading: loginMutation.isPending,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useLogin } from './_hooks/use-login';
|
||||
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
const { form, onSubmit } = useLogin();
|
||||
const { form, onSubmit, isLoading } = useLogin();
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-screen">
|
||||
@@ -22,6 +22,7 @@ export const Components: FC = (): ReactElement => {
|
||||
name="email"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<ControlledInputField
|
||||
control={form.control}
|
||||
@@ -31,10 +32,11 @@ export const Components: FC = (): ReactElement => {
|
||||
name="password"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<Button
|
||||
disabled={
|
||||
form.formState.isSubmitting ||
|
||||
isLoading ||
|
||||
form.formState.isValidating ||
|
||||
!form.formState.isValid
|
||||
}
|
||||
@@ -42,7 +44,7 @@ export const Components: FC = (): ReactElement => {
|
||||
size="md"
|
||||
className="w-full"
|
||||
>
|
||||
Login
|
||||
{isLoading ? 'Loading...' : 'Login'}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default function NotFoundPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-950">
|
||||
<div className="text-center">
|
||||
<h1 className="text-9xl font-bold text-gray-200 mb-4">404</h1>
|
||||
<h2 className="text-3xl font-semibold text-gray-900 dark:text-gray-300 mb-4">
|
||||
Page Not Found
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-8">
|
||||
The page you are looking for doesn't exist or has been moved.
|
||||
</p>
|
||||
<Link
|
||||
to="/hackathon-dashboard"
|
||||
className="inline-block px-6 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
Go Back Home
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useRouteError, isRouteErrorResponse } from 'react-router-dom';
|
||||
|
||||
export default function ErrorPage() {
|
||||
const error = useRouteError();
|
||||
let errorMessage: string;
|
||||
|
||||
if (isRouteErrorResponse(error)) {
|
||||
errorMessage = error.statusText;
|
||||
} else if (error instanceof Error) {
|
||||
errorMessage = error.message;
|
||||
} else if (typeof error === 'string') {
|
||||
errorMessage = error;
|
||||
} else {
|
||||
console.error(error);
|
||||
errorMessage = 'Unknown error';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="text-center">
|
||||
<h1 className="text-6xl font-bold text-red-600 mb-4">Oops!</h1>
|
||||
<p className="text-xl text-gray-700 mb-2">
|
||||
Sorry, an unexpected error has occurred.
|
||||
</p>
|
||||
<p className="text-gray-500 italic">{errorMessage}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -84,11 +84,11 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
||||
session?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
|
||||
|
||||
if (mappingPublicRoutes.includes(pathname)) {
|
||||
if (token) return redirect('/dashboard');
|
||||
if (token) return redirect('/hackathon-dashboard');
|
||||
return null;
|
||||
}
|
||||
|
||||
// if (!session) return redirect('/auth/login');
|
||||
if (!session) return redirect('/auth/login');
|
||||
|
||||
const matchedRoute = mappingRoutePermissions.find(
|
||||
(route) => route.path === pathname
|
||||
@@ -100,7 +100,7 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
||||
matchedRoute.permissions.some((perm) => userPermissions.includes(perm));
|
||||
|
||||
if (!hasPermission) {
|
||||
return '/dashboard';
|
||||
return '/hackathon-dashboard';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export default defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../node_modules/.vite/apps/backoffice',
|
||||
server: {
|
||||
port: 3000,
|
||||
port: 3003,
|
||||
host: 'localhost',
|
||||
},
|
||||
preview: {
|
||||
|
||||
Reference in New Issue
Block a user