feat: migrate all 5 Vite apps from react-router to TanStack Router
Migrated backoffice, hackathon, dimentorin, gacha, qrcampaign, and infra from custom react-router file-based routing to TanStack Router file-based routing following the tanstack-frontend-best-practice convention. Key changes per app: - New routes/ directory with __root.tsx, _public.tsx, _authenticated.tsx - Auth guards via beforeLoad (replaces old middleware.ts) - createFileRoute pattern for all page components - TanStackRouterVite plugin in vite.config for auto route generation - _components/_hooks folders colocated with routes (ignored by router) - routeTree.gen.ts auto-generated on dev/build Convention: - _public/* routes redirect to dashboard if authenticated - _authenticated/* routes redirect to /auth/login if not authenticated - $param for dynamic segments (was [param] in old convention) - _layout suffix for pathless layout routes Removed: - Old src/app/ directories from all apps - Old src/middleware.ts files - Custom convertPagesToRoute utility (no longer needed) - react-router dependency usage (kept in package.json for shared libs) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
86d9e759a6
commit
4240e8eb51
@@ -1,16 +0,0 @@
|
||||
import { Navbar } from '@imphnen-frontend-service/ui/organisms';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { ModalLoginProvider } from '@imphnen-frontend-service/utils';
|
||||
|
||||
export const AppLayout: FC = (): ReactElement => {
|
||||
return (
|
||||
<ModalLoginProvider>
|
||||
<main className="bg-primary-50 min-h-screen">
|
||||
<Navbar />
|
||||
<Outlet />
|
||||
</main>
|
||||
</ModalLoginProvider>
|
||||
);
|
||||
};
|
||||
export default AppLayout;
|
||||
+17
-31
@@ -1,42 +1,28 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { middleware } from './middleware';
|
||||
import { StrictMode } from 'react';
|
||||
import { createBrowserRouter, RouteObject, RouterProvider } from 'react-router';
|
||||
import {
|
||||
add404PageToRoutesChildren,
|
||||
addErrorElementToRoutes,
|
||||
convertPagesToRoute,
|
||||
QueryProvider,
|
||||
} from '@imphnen-frontend-service/utils';
|
||||
import { Toaster } from 'sonner';
|
||||
import './index.css';
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { RouterProvider, createRouter } from '@tanstack/react-router'
|
||||
import { QueryProvider } from '@imphnen-frontend-service/utils'
|
||||
import { Toaster } from 'sonner'
|
||||
import { routeTree } from './routeTree.gen'
|
||||
import './index.css'
|
||||
|
||||
const files = import.meta.glob('./app/**/*(page|layout).tsx');
|
||||
const errorFiles = import.meta.glob('./app/**/*error.tsx');
|
||||
const notFoundFiles = import.meta.glob('./app/**/*404.tsx');
|
||||
const loadingFiles = import.meta.glob('./app/**/*loading.tsx');
|
||||
const router = createRouter({ routeTree })
|
||||
|
||||
const routes = convertPagesToRoute(files, loadingFiles) as RouteObject;
|
||||
addErrorElementToRoutes(errorFiles, routes);
|
||||
add404PageToRoutesChildren(notFoundFiles, routes);
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: typeof router
|
||||
}
|
||||
}
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
...routes,
|
||||
loader: middleware,
|
||||
shouldRevalidate: () => true,
|
||||
},
|
||||
]);
|
||||
const rootElement = document.getElementById('root')
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
if (!rootElement) throw new Error('Failed to find the root element');
|
||||
if (!rootElement) throw new Error('Failed to find the root element')
|
||||
|
||||
createRoot(rootElement).render(
|
||||
<StrictMode>
|
||||
<QueryProvider>
|
||||
<Toaster position="top-right"/>
|
||||
<RouterProvider router={router} />
|
||||
<Toaster position="top-right" />
|
||||
</QueryProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { LoaderFunctionArgs } from 'react-router';
|
||||
|
||||
export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
// This file is auto-generated by TanStack Router
|
||||
export const routeTree = {} as any
|
||||
@@ -0,0 +1,18 @@
|
||||
import { createRootRoute, Outlet } from '@tanstack/react-router'
|
||||
import { Navbar } from '@imphnen-frontend-service/ui/organisms'
|
||||
import { ModalLoginProvider } from '@imphnen-frontend-service/utils'
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: RootLayout,
|
||||
})
|
||||
|
||||
function RootLayout() {
|
||||
return (
|
||||
<ModalLoginProvider>
|
||||
<main className="bg-primary-50 min-h-screen">
|
||||
<Navbar />
|
||||
<Outlet />
|
||||
</main>
|
||||
</ModalLoginProvider>
|
||||
)
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from '@imphnen-frontend-service/service';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { toast } from 'sonner';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useVerifyEmail } from './use-verify-email';
|
||||
|
||||
export const useLogin = () => {
|
||||
@@ -16,7 +15,6 @@ export const useLogin = () => {
|
||||
mode: 'all',
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { mutate, isPending } = usePostLogin();
|
||||
const { setLoading, setSession, clearSession } = useAuthStore();
|
||||
|
||||
@@ -48,7 +46,7 @@ export const useLogin = () => {
|
||||
setSession(loginData);
|
||||
}
|
||||
|
||||
navigate(0);
|
||||
window.location.reload();
|
||||
},
|
||||
onError: (error) => {
|
||||
const errorMessage = error?.response?.data?.message;
|
||||
+1
-3
@@ -5,12 +5,10 @@ import {
|
||||
} from '@imphnen-frontend-service/service';
|
||||
import { toast } from 'sonner';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export const useVerifyEmail = () => {
|
||||
const [showVerifyModal, setShowVerifyModal] = useState(false);
|
||||
const [emailToVerify, setEmailToVerify] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const postVerifyEmail = usePostVerifyEmail();
|
||||
|
||||
@@ -33,7 +31,7 @@ export const useVerifyEmail = () => {
|
||||
onSuccess: () => {
|
||||
toast.success('Verifikasi email sukses');
|
||||
|
||||
navigate(0);
|
||||
window.location.reload();
|
||||
},
|
||||
onError: (error) =>
|
||||
toast.error(error.message || 'Verifikasi email gagal'),
|
||||
@@ -1,16 +1,21 @@
|
||||
import { ArrowDownOutlined } from '@ant-design/icons';
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { FC, Fragment, ReactElement, useState } from 'react';
|
||||
import ModalFormForgotPassword from './_components/form/modal-form-forgot-password';
|
||||
import ModalFormLogin from './_components/form/modal-form-login';
|
||||
import ModalFormRegister from './_components/form/modal-form-register';
|
||||
import { GachaItem } from './_components/item/gacha-item';
|
||||
import { useModalLogin } from '@imphnen-frontend-service/utils';
|
||||
import { useGachaItemList, useUserCredits, useExecuteGachaRoll } from '@imphnen-frontend-service/service';
|
||||
import { toast } from 'sonner';
|
||||
import type { TGachaRollItemDto } from '@imphnen-frontend-service/service';
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { ArrowDownOutlined } from '@ant-design/icons'
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms'
|
||||
import { FC, Fragment, ReactElement, useState } from 'react'
|
||||
import ModalFormForgotPassword from './_components/form/modal-form-forgot-password'
|
||||
import ModalFormLogin from './_components/form/modal-form-login'
|
||||
import ModalFormRegister from './_components/form/modal-form-register'
|
||||
import { GachaItem } from './_components/item/gacha-item'
|
||||
import { useModalLogin } from '@imphnen-frontend-service/utils'
|
||||
import { useGachaItemList, useUserCredits, useExecuteGachaRoll } from '@imphnen-frontend-service/service'
|
||||
import { toast } from 'sonner'
|
||||
import type { TGachaRollItemDto } from '@imphnen-frontend-service/service'
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
export const Route = createFileRoute('/')({
|
||||
component: GachaHomePage,
|
||||
})
|
||||
|
||||
function GachaHomePage(): ReactElement {
|
||||
const { showModalLogin, setShowModalLogin } = useModalLogin();
|
||||
const [showModalForgotPassword, setShowModalForgotPassword] = useState(false);
|
||||
const [showModalRegister, setShowModalRegister] = useState(false);
|
||||
@@ -251,6 +256,4 @@ export const Components: FC = (): ReactElement => {
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default Components;
|
||||
}
|
||||
Reference in New Issue
Block a user