diff --git a/apps/backoffice/src/app/(public)/layout.tsx b/apps/backoffice/src/app/(public)/layout.tsx deleted file mode 100644 index 2d8a74d..0000000 --- a/apps/backoffice/src/app/(public)/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
- -
- ); -}; - -export default AppLayout; diff --git a/apps/backoffice/src/app/404.tsx b/apps/backoffice/src/app/404.tsx deleted file mode 100644 index 5eda28b..0000000 --- a/apps/backoffice/src/app/404.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Link } from 'react-router-dom'; - -export default function NotFoundPage() { - return ( -
-
-

404

-

- Page Not Found -

-

- The page you are looking for doesn't exist or has been moved. -

- - Go Back Home - -
-
- ); -} diff --git a/apps/backoffice/src/app/error.tsx b/apps/backoffice/src/app/error.tsx deleted file mode 100644 index 3859b00..0000000 --- a/apps/backoffice/src/app/error.tsx +++ /dev/null @@ -1,29 +0,0 @@ -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 ( -
-
-

Oops!

-

- Sorry, an unexpected error has occurred. -

-

{errorMessage}

-
-
- ); -} diff --git a/apps/backoffice/src/main.tsx b/apps/backoffice/src/main.tsx index 4f9c907..cf947ab 100644 --- a/apps/backoffice/src/main.tsx +++ b/apps/backoffice/src/main.tsx @@ -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( - + -); +) diff --git a/apps/backoffice/src/middleware.ts b/apps/backoffice/src/middleware.ts deleted file mode 100644 index bac524f..0000000 --- a/apps/backoffice/src/middleware.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { - PERMISSIONS, - SessionToken, - SessionUser, -} from '@imphnen-frontend-service/service'; -import { LoaderFunctionArgs, redirect } from 'react-router'; - -const mappingPublicRoutes = [ - '/auth/login', - '/auth/forgot', - '/auth/new-password', -]; - -const mappingRoutePermissions = [ - { - path: '/dashboard', - permissions: [], - }, - { - path: '/users', - permissions: [PERMISSIONS.USERS.READ_LIST], - }, - { - path: '/users/create', - permissions: [PERMISSIONS.USERS.CREATE], - }, - { - path: '/users/update', - permissions: [PERMISSIONS.USERS.UPDATE], - }, - { - path: '/users/detail', - permissions: [PERMISSIONS.USERS.READ_DETAIL], - }, - { - path: '/roles', - permissions: [PERMISSIONS.ROLES.READ_LIST], - }, - { - path: '/roles/create', - permissions: [PERMISSIONS.ROLES.CREATE], - }, - { - path: '/roles/update', - permissions: [PERMISSIONS.ROLES.UPDATE], - }, - { - path: '/roles/detail', - permissions: [PERMISSIONS.ROLES.READ_DETAIL], - }, - { - path: '/permissions', - permissions: [PERMISSIONS.PERMISSIONS.READ_LIST], - }, - { - path: '/permissions/create', - permissions: [PERMISSIONS.PERMISSIONS.CREATE], - }, - { - path: '/permissions/update', - permissions: [PERMISSIONS.PERMISSIONS.UPDATE], - }, - { - path: '/permissions/detail', - permissions: [PERMISSIONS.PERMISSIONS.READ_DETAIL], - }, -]; - -export const middleware = async ({ request }: LoaderFunctionArgs) => { - const url = new URL(request.url); - const pathname = url.pathname; - const session = SessionUser.get(); - const session_token = SessionToken.get(); - const token = session_token?.token?.access_token; - const userPermissions = - session?.role?.permissions?.map?.((perm) => perm?.name) ?? []; - - if (mappingPublicRoutes.includes(pathname)) { - if (token) return redirect('/hackathon-dashboard'); - return null; - } - - if (!session) return redirect('/auth/login'); - - const matchedRoute = mappingRoutePermissions.find( - (route) => route.path === pathname - ); - - if (matchedRoute) { - const hasPermission = - !matchedRoute.permissions || - matchedRoute.permissions.some((perm) => userPermissions.includes(perm)); - - if (!hasPermission) { - return '/hackathon-dashboard'; - } - } - - return null; -}; diff --git a/apps/backoffice/src/routeTree.gen.ts b/apps/backoffice/src/routeTree.gen.ts new file mode 100644 index 0000000..786525b --- /dev/null +++ b/apps/backoffice/src/routeTree.gen.ts @@ -0,0 +1,2 @@ +// This file is auto-generated by TanStack Router +export const routeTree = {} as any diff --git a/apps/backoffice/src/routes/__root.tsx b/apps/backoffice/src/routes/__root.tsx new file mode 100644 index 0000000..f463b79 --- /dev/null +++ b/apps/backoffice/src/routes/__root.tsx @@ -0,0 +1,5 @@ +import { createRootRoute, Outlet } from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: () => , +}) diff --git a/apps/backoffice/src/app/(protected)/layout.tsx b/apps/backoffice/src/routes/_authenticated.tsx similarity index 76% rename from apps/backoffice/src/app/(protected)/layout.tsx rename to apps/backoffice/src/routes/_authenticated.tsx index 810b1d9..0c23d34 100644 --- a/apps/backoffice/src/app/(protected)/layout.tsx +++ b/apps/backoffice/src/routes/_authenticated.tsx @@ -1,9 +1,20 @@ -import { FC, ReactElement, useState } from 'react'; -import { Outlet } from 'react-router-dom'; -import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms'; +import { createFileRoute, Outlet, redirect } from '@tanstack/react-router' +import { SessionToken } from '@imphnen-frontend-service/service' +import { useState } from 'react' +import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms' -export const AppLayout: FC = (): ReactElement => { - const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); +export const Route = createFileRoute('/_authenticated')({ + beforeLoad: () => { + const session = SessionToken.get() + if (!session?.token?.access_token) { + throw redirect({ to: '/auth/login' }) + } + }, + component: AuthenticatedLayout, +}) + +function AuthenticatedLayout() { + const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false) return (
@@ -13,14 +24,12 @@ export const AppLayout: FC = (): ReactElement => { onClose={() => setMobileSidebarOpen(false)} />
-
-
- ); -}; - -export default AppLayout; + ) +} diff --git a/apps/backoffice/src/app/(protected)/accounts/_components/modal-edit-account.tsx b/apps/backoffice/src/routes/_authenticated/_components/accounts/modal-edit-account.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/accounts/_components/modal-edit-account.tsx rename to apps/backoffice/src/routes/_authenticated/_components/accounts/modal-edit-account.tsx diff --git a/apps/backoffice/src/app/(protected)/cms-events/_components/modal-add-event.tsx b/apps/backoffice/src/routes/_authenticated/_components/cms-events/modal-add-event.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/cms-events/_components/modal-add-event.tsx rename to apps/backoffice/src/routes/_authenticated/_components/cms-events/modal-add-event.tsx diff --git a/apps/backoffice/src/app/(protected)/cms-events/_components/modal-delete-event.tsx b/apps/backoffice/src/routes/_authenticated/_components/cms-events/modal-delete-event.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/cms-events/_components/modal-delete-event.tsx rename to apps/backoffice/src/routes/_authenticated/_components/cms-events/modal-delete-event.tsx diff --git a/apps/backoffice/src/app/(protected)/cms-events/_components/modal-update-event.tsx b/apps/backoffice/src/routes/_authenticated/_components/cms-events/modal-update-event.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/cms-events/_components/modal-update-event.tsx rename to apps/backoffice/src/routes/_authenticated/_components/cms-events/modal-update-event.tsx diff --git a/apps/backoffice/src/app/(protected)/cms-testimonials/_components/modal-add-testimonial.tsx b/apps/backoffice/src/routes/_authenticated/_components/cms-testimonials/modal-add-testimonial.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/cms-testimonials/_components/modal-add-testimonial.tsx rename to apps/backoffice/src/routes/_authenticated/_components/cms-testimonials/modal-add-testimonial.tsx diff --git a/apps/backoffice/src/app/(protected)/cms-testimonials/_components/modal-delete-testimonial.tsx b/apps/backoffice/src/routes/_authenticated/_components/cms-testimonials/modal-delete-testimonial.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/cms-testimonials/_components/modal-delete-testimonial.tsx rename to apps/backoffice/src/routes/_authenticated/_components/cms-testimonials/modal-delete-testimonial.tsx diff --git a/apps/backoffice/src/app/(protected)/cms-testimonials/_components/modal-update-testimonial.tsx b/apps/backoffice/src/routes/_authenticated/_components/cms-testimonials/modal-update-testimonial.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/cms-testimonials/_components/modal-update-testimonial.tsx rename to apps/backoffice/src/routes/_authenticated/_components/cms-testimonials/modal-update-testimonial.tsx diff --git a/apps/backoffice/src/app/(protected)/dashboard-dimentorin/_components/chart/session-status.tsx b/apps/backoffice/src/routes/_authenticated/_components/dashboard-dimentorin/chart/session-status.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/dashboard-dimentorin/_components/chart/session-status.tsx rename to apps/backoffice/src/routes/_authenticated/_components/dashboard-dimentorin/chart/session-status.tsx diff --git a/apps/backoffice/src/app/(protected)/dashboard-dimentorin/_components/chart/user-growth.tsx b/apps/backoffice/src/routes/_authenticated/_components/dashboard-dimentorin/chart/user-growth.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/dashboard-dimentorin/_components/chart/user-growth.tsx rename to apps/backoffice/src/routes/_authenticated/_components/dashboard-dimentorin/chart/user-growth.tsx diff --git a/apps/backoffice/src/app/(protected)/dashboard/_components/modal-add-item.tsx b/apps/backoffice/src/routes/_authenticated/_components/dashboard/modal-add-item.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/dashboard/_components/modal-add-item.tsx rename to apps/backoffice/src/routes/_authenticated/_components/dashboard/modal-add-item.tsx diff --git a/apps/backoffice/src/app/(protected)/dashboard/_components/modal-delete-item.tsx b/apps/backoffice/src/routes/_authenticated/_components/dashboard/modal-delete-item.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/dashboard/_components/modal-delete-item.tsx rename to apps/backoffice/src/routes/_authenticated/_components/dashboard/modal-delete-item.tsx diff --git a/apps/backoffice/src/app/(protected)/dashboard/_components/modal-edit-item.tsx b/apps/backoffice/src/routes/_authenticated/_components/dashboard/modal-edit-item.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/dashboard/_components/modal-edit-item.tsx rename to apps/backoffice/src/routes/_authenticated/_components/dashboard/modal-edit-item.tsx diff --git a/apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-add-item.tsx b/apps/backoffice/src/routes/_authenticated/_components/gacha-roll/modal-add-item.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-add-item.tsx rename to apps/backoffice/src/routes/_authenticated/_components/gacha-roll/modal-add-item.tsx diff --git a/apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-delete-item.tsx b/apps/backoffice/src/routes/_authenticated/_components/gacha-roll/modal-delete-item.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-delete-item.tsx rename to apps/backoffice/src/routes/_authenticated/_components/gacha-roll/modal-delete-item.tsx diff --git a/apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-update-item.tsx b/apps/backoffice/src/routes/_authenticated/_components/gacha-roll/modal-update-item.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-update-item.tsx rename to apps/backoffice/src/routes/_authenticated/_components/gacha-roll/modal-update-item.tsx diff --git a/apps/backoffice/src/app/(protected)/hackathon-submissions/_components/submission-modal.tsx b/apps/backoffice/src/routes/_authenticated/_components/hackathon-submissions/submission-modal.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/hackathon-submissions/_components/submission-modal.tsx rename to apps/backoffice/src/routes/_authenticated/_components/hackathon-submissions/submission-modal.tsx diff --git a/apps/backoffice/src/app/(protected)/hackathon-teams/_components/modal-team-detail-new.tsx b/apps/backoffice/src/routes/_authenticated/_components/hackathon-teams/modal-team-detail-new.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/hackathon-teams/_components/modal-team-detail-new.tsx rename to apps/backoffice/src/routes/_authenticated/_components/hackathon-teams/modal-team-detail-new.tsx diff --git a/apps/backoffice/src/app/(protected)/hackathon-teams/_components/team-banner-placeholder.tsx b/apps/backoffice/src/routes/_authenticated/_components/hackathon-teams/team-banner-placeholder.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/hackathon-teams/_components/team-banner-placeholder.tsx rename to apps/backoffice/src/routes/_authenticated/_components/hackathon-teams/team-banner-placeholder.tsx diff --git a/apps/backoffice/src/app/(protected)/hackathon-users/_components/modal-user-detail.tsx b/apps/backoffice/src/routes/_authenticated/_components/hackathon-users/modal-user-detail.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/hackathon-users/_components/modal-user-detail.tsx rename to apps/backoffice/src/routes/_authenticated/_components/hackathon-users/modal-user-detail.tsx diff --git a/apps/backoffice/src/app/(protected)/permissions/_components/modal-add-permission.tsx b/apps/backoffice/src/routes/_authenticated/_components/permissions/modal-add-permission.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/permissions/_components/modal-add-permission.tsx rename to apps/backoffice/src/routes/_authenticated/_components/permissions/modal-add-permission.tsx diff --git a/apps/backoffice/src/app/(protected)/permissions/_components/modal-delete-permission.tsx b/apps/backoffice/src/routes/_authenticated/_components/permissions/modal-delete-permission.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/permissions/_components/modal-delete-permission.tsx rename to apps/backoffice/src/routes/_authenticated/_components/permissions/modal-delete-permission.tsx diff --git a/apps/backoffice/src/app/(protected)/permissions/_components/modal-update-permission.tsx b/apps/backoffice/src/routes/_authenticated/_components/permissions/modal-update-permission.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/permissions/_components/modal-update-permission.tsx rename to apps/backoffice/src/routes/_authenticated/_components/permissions/modal-update-permission.tsx diff --git a/apps/backoffice/src/app/(protected)/prizes/_components/modal-process-item.tsx b/apps/backoffice/src/routes/_authenticated/_components/prizes/modal-process-item.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/prizes/_components/modal-process-item.tsx rename to apps/backoffice/src/routes/_authenticated/_components/prizes/modal-process-item.tsx diff --git a/apps/backoffice/src/app/(protected)/roadmap-dimentorin/_components/modal/create-roadmap/index.tsx b/apps/backoffice/src/routes/_authenticated/_components/roadmap-dimentorin/modal/create-roadmap/index.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/roadmap-dimentorin/_components/modal/create-roadmap/index.tsx rename to apps/backoffice/src/routes/_authenticated/_components/roadmap-dimentorin/modal/create-roadmap/index.tsx diff --git a/apps/backoffice/src/app/(protected)/roles/_components/modal-add-role.tsx b/apps/backoffice/src/routes/_authenticated/_components/roles/modal-add-role.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/roles/_components/modal-add-role.tsx rename to apps/backoffice/src/routes/_authenticated/_components/roles/modal-add-role.tsx diff --git a/apps/backoffice/src/app/(protected)/roles/_components/modal-delete-role.tsx b/apps/backoffice/src/routes/_authenticated/_components/roles/modal-delete-role.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/roles/_components/modal-delete-role.tsx rename to apps/backoffice/src/routes/_authenticated/_components/roles/modal-delete-role.tsx diff --git a/apps/backoffice/src/app/(protected)/roles/_components/modal-update-role.tsx b/apps/backoffice/src/routes/_authenticated/_components/roles/modal-update-role.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/roles/_components/modal-update-role.tsx rename to apps/backoffice/src/routes/_authenticated/_components/roles/modal-update-role.tsx diff --git a/apps/backoffice/src/app/(protected)/session-dimentorin/_components/modal/detail/index.tsx b/apps/backoffice/src/routes/_authenticated/_components/session-dimentorin/modal/detail/index.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/session-dimentorin/_components/modal/detail/index.tsx rename to apps/backoffice/src/routes/_authenticated/_components/session-dimentorin/modal/detail/index.tsx diff --git a/apps/backoffice/src/app/(protected)/settings-dimentorin/_components/general.tsx b/apps/backoffice/src/routes/_authenticated/_components/settings-dimentorin/general.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/settings-dimentorin/_components/general.tsx rename to apps/backoffice/src/routes/_authenticated/_components/settings-dimentorin/general.tsx diff --git a/apps/backoffice/src/app/(protected)/settings-dimentorin/_components/notification.tsx b/apps/backoffice/src/routes/_authenticated/_components/settings-dimentorin/notification.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/settings-dimentorin/_components/notification.tsx rename to apps/backoffice/src/routes/_authenticated/_components/settings-dimentorin/notification.tsx diff --git a/apps/backoffice/src/app/(protected)/settings-dimentorin/_components/payment.tsx b/apps/backoffice/src/routes/_authenticated/_components/settings-dimentorin/payment.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/settings-dimentorin/_components/payment.tsx rename to apps/backoffice/src/routes/_authenticated/_components/settings-dimentorin/payment.tsx diff --git a/apps/backoffice/src/app/(protected)/settings-dimentorin/_components/security.tsx b/apps/backoffice/src/routes/_authenticated/_components/settings-dimentorin/security.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/settings-dimentorin/_components/security.tsx rename to apps/backoffice/src/routes/_authenticated/_components/settings-dimentorin/security.tsx diff --git a/apps/backoffice/src/app/(protected)/settings-dimentorin/_components/user-roles-permission.tsx b/apps/backoffice/src/routes/_authenticated/_components/settings-dimentorin/user-roles-permission.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/settings-dimentorin/_components/user-roles-permission.tsx rename to apps/backoffice/src/routes/_authenticated/_components/settings-dimentorin/user-roles-permission.tsx diff --git a/apps/backoffice/src/app/(protected)/transactions/_components/modal-validate.tsx b/apps/backoffice/src/routes/_authenticated/_components/transactions/modal-validate.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/transactions/_components/modal-validate.tsx rename to apps/backoffice/src/routes/_authenticated/_components/transactions/modal-validate.tsx diff --git a/apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/delete/index.tsx b/apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/delete/index.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/delete/index.tsx rename to apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/delete/index.tsx diff --git a/apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/detail/account-profile.tsx b/apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/detail/account-profile.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/detail/account-profile.tsx rename to apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/detail/account-profile.tsx diff --git a/apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/detail/activity-log.tsx b/apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/detail/activity-log.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/detail/activity-log.tsx rename to apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/detail/activity-log.tsx diff --git a/apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/detail/detail-profile.tsx b/apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/detail/detail-profile.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/detail/detail-profile.tsx rename to apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/detail/detail-profile.tsx diff --git a/apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/detail/index.tsx b/apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/detail/index.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/detail/index.tsx rename to apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/detail/index.tsx diff --git a/apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/detail/type.d.ts b/apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/detail/type.d.ts similarity index 100% rename from apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/detail/type.d.ts rename to apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/detail/type.d.ts diff --git a/apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/suspend-or-ban/index.tsx b/apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/suspend-or-ban/index.tsx similarity index 100% rename from apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/suspend-or-ban/index.tsx rename to apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/suspend-or-ban/index.tsx diff --git a/apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/type.d.ts b/apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/type.d.ts similarity index 100% rename from apps/backoffice/src/app/(protected)/users-dimentorin/_components/modal/type.d.ts rename to apps/backoffice/src/routes/_authenticated/_components/users-dimentorin/modal/type.d.ts diff --git a/apps/backoffice/src/app/(protected)/cms-events/_hook/use-item.ts b/apps/backoffice/src/routes/_authenticated/_hooks/cms-events/use-item.ts similarity index 100% rename from apps/backoffice/src/app/(protected)/cms-events/_hook/use-item.ts rename to apps/backoffice/src/routes/_authenticated/_hooks/cms-events/use-item.ts diff --git a/apps/backoffice/src/app/(protected)/cms-testimonials/_hook/use-item.ts b/apps/backoffice/src/routes/_authenticated/_hooks/cms-testimonials/use-item.ts similarity index 100% rename from apps/backoffice/src/app/(protected)/cms-testimonials/_hook/use-item.ts rename to apps/backoffice/src/routes/_authenticated/_hooks/cms-testimonials/use-item.ts diff --git a/apps/backoffice/src/app/(protected)/dashboard/_hook/use-item.ts b/apps/backoffice/src/routes/_authenticated/_hooks/dashboard/use-item.ts similarity index 100% rename from apps/backoffice/src/app/(protected)/dashboard/_hook/use-item.ts rename to apps/backoffice/src/routes/_authenticated/_hooks/dashboard/use-item.ts diff --git a/apps/backoffice/src/app/(protected)/gacha-roll/_hook/use-item.ts b/apps/backoffice/src/routes/_authenticated/_hooks/gacha-roll/use-item.ts similarity index 100% rename from apps/backoffice/src/app/(protected)/gacha-roll/_hook/use-item.ts rename to apps/backoffice/src/routes/_authenticated/_hooks/gacha-roll/use-item.ts diff --git a/apps/backoffice/src/app/(protected)/permissions/_hook/use-item.ts b/apps/backoffice/src/routes/_authenticated/_hooks/permissions/use-item.ts similarity index 100% rename from apps/backoffice/src/app/(protected)/permissions/_hook/use-item.ts rename to apps/backoffice/src/routes/_authenticated/_hooks/permissions/use-item.ts diff --git a/apps/backoffice/src/app/(protected)/roles/_hook/use-item.ts b/apps/backoffice/src/routes/_authenticated/_hooks/roles/use-item.ts similarity index 100% rename from apps/backoffice/src/app/(protected)/roles/_hook/use-item.ts rename to apps/backoffice/src/routes/_authenticated/_hooks/roles/use-item.ts diff --git a/apps/backoffice/src/app/(protected)/accounts/page.tsx b/apps/backoffice/src/routes/_authenticated/accounts.tsx similarity index 83% rename from apps/backoffice/src/app/(protected)/accounts/page.tsx rename to apps/backoffice/src/routes/_authenticated/accounts.tsx index 888d148..9ceba49 100644 --- a/apps/backoffice/src/app/(protected)/accounts/page.tsx +++ b/apps/backoffice/src/routes/_authenticated/accounts.tsx @@ -1,14 +1,13 @@ -import * as React from 'react'; - -import { FC, Fragment, ReactElement, useRef, useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router' +import * as React from 'react' +import { FC, Fragment, ReactElement, useRef, useState } from 'react' import { FilterOutlined, SearchOutlined, EditOutlined, -} from '@ant-design/icons'; -import { Button, Input } from '@imphnen-frontend-service/ui/atoms'; -import { DataTable, Filter } from '@imphnen-frontend-service/ui/organisms'; - +} from '@ant-design/icons' +import { Button, Input } from '@imphnen-frontend-service/ui/atoms' +import { DataTable, Filter } from '@imphnen-frontend-service/ui/organisms' import { ColumnDef, getCoreRowModel, @@ -16,20 +15,24 @@ import { PaginationState, useReactTable, RowSelectionState, -} from '@tanstack/react-table'; -import ModalEditAccount from './_components/modal-edit-account'; -import { useQueryState } from '@imphnen-frontend-service/utils'; +} from '@tanstack/react-table' +import ModalEditAccount from './_components/accounts/modal-edit-account' +import { useQueryState } from '@imphnen-frontend-service/utils' import { useUserList, useUpdateUserById, TUsersListItem, -} from '@imphnen-frontend-service/service'; +} from '@imphnen-frontend-service/service' -export const Components: FC = (): ReactElement => { - const [showModalEditAccount, setShowModalEditAccount] = useState(false); - const [selectedUser, setSelectedUser] = useState(null); - const [search, setSearch] = useState(''); - const pendingFormData = useRef(null); +export const Route = createFileRoute('/_authenticated/accounts')({ + component: AccountsPage, +}) + +function AccountsPage() { + const [showModalEditAccount, setShowModalEditAccount] = useState(false) + const [selectedUser, setSelectedUser] = useState(null) + const [search, setSearch] = useState('') + const pendingFormData = useRef(null) const { step: currentStep, @@ -40,31 +43,31 @@ export const Components: FC = (): ReactElement => { defaultValue: 1, maxValue: 2, minValue: 1, - }); + }) const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 9, - }); + }) - const [rowSelection, setRowSelection] = React.useState({}); - const [showFilter, setShowFilter] = useState(false); + const [rowSelection, setRowSelection] = React.useState({}) + const [showFilter, setShowFilter] = useState(false) const { data: usersData, isLoading } = useUserList({ search, page: pagination.pageIndex + 1, per_page: pagination.pageSize, - }); - const updateUser = useUpdateUserById(); + }) + const updateUser = useUpdateUserById() - const users: TUsersListItem[] = usersData?.data ?? []; - const totalItems = usersData?.meta?.total ?? users.length; + const users: TUsersListItem[] = usersData?.data ?? [] + const totalItems = usersData?.meta?.total ?? users.length const handleEditAccount = async () => { if (selectedUser && pendingFormData.current) { - await updateUser.mutateAsync({ id: selectedUser.id, data: pendingFormData.current }); + await updateUser.mutateAsync({ id: selectedUser.id, data: pendingFormData.current }) } - }; + } const columns: ColumnDef[] = [ { @@ -118,9 +121,9 @@ export const Components: FC = (): ReactElement => { variant="primary" size="sm" onClick={(e) => { - e.stopPropagation(); - setSelectedUser(row.original); - setShowModalEditAccount(true); + e.stopPropagation() + setSelectedUser(row.original) + setShowModalEditAccount(true) }} className="flex items-center gap-2" > @@ -128,7 +131,7 @@ export const Components: FC = (): ReactElement => { ), }, - ]; + ] const table = useReactTable({ data: users, @@ -144,7 +147,7 @@ export const Components: FC = (): ReactElement => { onPaginationChange: setPagination, pageCount: Math.ceil(totalItems / pagination.pageSize), manualPagination: true, - }); + }) return ( @@ -200,10 +203,8 @@ export const Components: FC = (): ReactElement => { prevStep={prevStep} resetStep={resetStep} initialValues={selectedUser ? { fullname: selectedUser.fullname, email: selectedUser.email } : undefined} - onDataCapture={(data) => { pendingFormData.current = data; }} + onDataCapture={(data) => { pendingFormData.current = data }} /> - ); -}; - -export default Components; + ) +} diff --git a/apps/backoffice/src/app/(protected)/cms-events/page.tsx b/apps/backoffice/src/routes/_authenticated/cms-events.tsx similarity index 82% rename from apps/backoffice/src/app/(protected)/cms-events/page.tsx rename to apps/backoffice/src/routes/_authenticated/cms-events.tsx index 0e778f3..6f30977 100644 --- a/apps/backoffice/src/app/(protected)/cms-events/page.tsx +++ b/apps/backoffice/src/routes/_authenticated/cms-events.tsx @@ -1,12 +1,13 @@ -import { FC, Fragment, ReactElement, useRef, useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, Fragment, ReactElement, useRef, useState } from 'react' import { SearchOutlined, EditOutlined, DeleteOutlined, PlusOutlined, -} from '@ant-design/icons'; -import { Button, Input } from '@imphnen-frontend-service/ui/atoms'; -import { DataTable } from '@imphnen-frontend-service/ui/organisms'; +} from '@ant-design/icons' +import { Button, Input } from '@imphnen-frontend-service/ui/atoms' +import { DataTable } from '@imphnen-frontend-service/ui/organisms' import { ColumnDef, getCoreRowModel, @@ -14,27 +15,31 @@ import { PaginationState, RowSelectionState, useReactTable, -} from '@tanstack/react-table'; -import ModalAddEvent from './_components/modal-add-event'; -import ModalUpdateEvent from './_components/modal-update-event'; -import ModalDeleteEvent from './_components/modal-delete-event'; -import { useQueryState } from '@imphnen-frontend-service/utils'; +} from '@tanstack/react-table' +import ModalAddEvent from './_components/cms-events/modal-add-event' +import ModalUpdateEvent from './_components/cms-events/modal-update-event' +import ModalDeleteEvent from './_components/cms-events/modal-delete-event' +import { useQueryState } from '@imphnen-frontend-service/utils' import { useEventList, useCreateEvent, useUpdateEvent, useDeleteEvent, TEventsListItem, -} from '@imphnen-frontend-service/service'; -import React from 'react'; +} from '@imphnen-frontend-service/service' +import React from 'react' -export const Components: FC = (): ReactElement => { - const [showModalAddItem, setShowModalAddItem] = useState(false); - const [showModalUpdateItem, setShowModalUpdateItem] = useState(false); - const [showModalDeleteItem, setShowModalDeleteItem] = useState(false); - const [selectedEvent, setSelectedEvent] = useState(null); - const [search, setSearch] = useState(''); - const pendingFormData = useRef(null); +export const Route = createFileRoute('/_authenticated/cms-events')({ + component: CmsEventsPage, +}) + +function CmsEventsPage() { + const [showModalAddItem, setShowModalAddItem] = useState(false) + const [showModalUpdateItem, setShowModalUpdateItem] = useState(false) + const [showModalDeleteItem, setShowModalDeleteItem] = useState(false) + const [selectedEvent, setSelectedEvent] = useState(null) + const [search, setSearch] = useState('') + const pendingFormData = useRef(null) const { step: currentStep, @@ -45,47 +50,47 @@ export const Components: FC = (): ReactElement => { defaultValue: 1, maxValue: 2, minValue: 1, - }); + }) const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 9, - }); + }) - const [rowSelection, setRowSelection] = React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}) const { data: eventsData, isLoading } = useEventList({ search, page: pagination.pageIndex + 1, per_page: pagination.pageSize, - }); - const createEvent = useCreateEvent(); - const updateEvent = useUpdateEvent(); - const deleteEvent = useDeleteEvent(); + }) + const createEvent = useCreateEvent() + const updateEvent = useUpdateEvent() + const deleteEvent = useDeleteEvent() - const events: TEventsListItem[] = eventsData?.data ?? []; - const totalItems = eventsData?.meta?.total ?? events.length; + const events: TEventsListItem[] = eventsData?.data ?? [] + const totalItems = eventsData?.meta?.total ?? events.length const handleAdd = async (): Promise => { if (pendingFormData.current) { - await createEvent.mutateAsync(pendingFormData.current); + await createEvent.mutateAsync(pendingFormData.current) } - return true; - }; + return true + } const handleUpdate = async (): Promise => { if (selectedEvent && pendingFormData.current) { - await updateEvent.mutateAsync({ id: selectedEvent.id, data: pendingFormData.current }); + await updateEvent.mutateAsync({ id: selectedEvent.id, data: pendingFormData.current }) } - return true; - }; + return true + } const handleDelete = async (): Promise => { if (selectedEvent) { - await deleteEvent.mutateAsync(selectedEvent.id); + await deleteEvent.mutateAsync(selectedEvent.id) } - return true; - }; + return true + } const columns: ColumnDef[] = [ { @@ -157,9 +162,9 @@ export const Components: FC = (): ReactElement => { variant="primary" size="sm" onClick={(e) => { - e.stopPropagation(); - setSelectedEvent(row.original); - setShowModalUpdateItem(true); + e.stopPropagation() + setSelectedEvent(row.original) + setShowModalUpdateItem(true) }} className="flex items-center gap-2" > @@ -169,9 +174,9 @@ export const Components: FC = (): ReactElement => { variant="danger" size="sm" onClick={(e) => { - e.stopPropagation(); - setSelectedEvent(row.original); - setShowModalDeleteItem(true); + e.stopPropagation() + setSelectedEvent(row.original) + setShowModalDeleteItem(true) }} className="flex items-center gap-2" > @@ -180,7 +185,7 @@ export const Components: FC = (): ReactElement => { ), }, - ]; + ] const table = useReactTable({ data: events, @@ -196,7 +201,7 @@ export const Components: FC = (): ReactElement => { onPaginationChange: setPagination, pageCount: Math.ceil(totalItems / pagination.pageSize), manualPagination: true, - }); + }) return ( @@ -252,7 +257,7 @@ export const Components: FC = (): ReactElement => { prevStep={prevStep} resetStep={resetStep} handleAdd={handleAdd} - onDataCapture={(data) => { pendingFormData.current = data; }} + onDataCapture={(data) => { pendingFormData.current = data }} /> { end_date: selectedEvent.end_date, is_online: selectedEvent.is_online, } : undefined} - onDataCapture={(data) => { pendingFormData.current = data; }} + onDataCapture={(data) => { pendingFormData.current = data }} /> { handleDelete={handleDelete} /> - ); -}; - -export default Components; + ) +} diff --git a/apps/backoffice/src/app/(protected)/cms-testimonials/page.tsx b/apps/backoffice/src/routes/_authenticated/cms-testimonials.tsx similarity index 81% rename from apps/backoffice/src/app/(protected)/cms-testimonials/page.tsx rename to apps/backoffice/src/routes/_authenticated/cms-testimonials.tsx index 4523f94..7122624 100644 --- a/apps/backoffice/src/app/(protected)/cms-testimonials/page.tsx +++ b/apps/backoffice/src/routes/_authenticated/cms-testimonials.tsx @@ -1,12 +1,13 @@ -import { FC, Fragment, ReactElement, useRef, useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, Fragment, ReactElement, useRef, useState } from 'react' import { SearchOutlined, EditOutlined, DeleteOutlined, PlusOutlined, -} from '@ant-design/icons'; -import { Button, Input } from '@imphnen-frontend-service/ui/atoms'; -import { DataTable } from '@imphnen-frontend-service/ui/organisms'; +} from '@ant-design/icons' +import { Button, Input } from '@imphnen-frontend-service/ui/atoms' +import { DataTable } from '@imphnen-frontend-service/ui/organisms' import { ColumnDef, getCoreRowModel, @@ -14,27 +15,31 @@ import { PaginationState, RowSelectionState, useReactTable, -} from '@tanstack/react-table'; -import ModalAddTestimonial from './_components/modal-add-testimonial'; -import ModalUpdateTestimonial from './_components/modal-update-testimonial'; -import ModalDeleteTestimonial from './_components/modal-delete-testimonial'; -import { useQueryState } from '@imphnen-frontend-service/utils'; +} from '@tanstack/react-table' +import ModalAddTestimonial from './_components/cms-testimonials/modal-add-testimonial' +import ModalUpdateTestimonial from './_components/cms-testimonials/modal-update-testimonial' +import ModalDeleteTestimonial from './_components/cms-testimonials/modal-delete-testimonial' +import { useQueryState } from '@imphnen-frontend-service/utils' import { useTestimonialList, useCreateTestimonial, useUpdateTestimonial, useDeleteTestimonial, TTestimonialsListItem, -} from '@imphnen-frontend-service/service'; -import React from 'react'; +} from '@imphnen-frontend-service/service' +import React from 'react' -export const Components: FC = (): ReactElement => { - const [showModalAddItem, setShowModalAddItem] = useState(false); - const [showModalUpdateItem, setShowModalUpdateItem] = useState(false); - const [showModalDeleteItem, setShowModalDeleteItem] = useState(false); - const [selectedTestimonial, setSelectedTestimonial] = useState(null); - const [search, setSearch] = useState(''); - const pendingFormData = useRef(null); +export const Route = createFileRoute('/_authenticated/cms-testimonials')({ + component: CmsTestimonialsPage, +}) + +function CmsTestimonialsPage() { + const [showModalAddItem, setShowModalAddItem] = useState(false) + const [showModalUpdateItem, setShowModalUpdateItem] = useState(false) + const [showModalDeleteItem, setShowModalDeleteItem] = useState(false) + const [selectedTestimonial, setSelectedTestimonial] = useState(null) + const [search, setSearch] = useState('') + const pendingFormData = useRef(null) const { step: currentStep, @@ -45,47 +50,47 @@ export const Components: FC = (): ReactElement => { defaultValue: 1, maxValue: 2, minValue: 1, - }); + }) const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 9, - }); + }) - const [rowSelection, setRowSelection] = React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}) const { data: testimonialsData, isLoading } = useTestimonialList({ search, page: pagination.pageIndex + 1, per_page: pagination.pageSize, - }); - const createTestimonial = useCreateTestimonial(); - const updateTestimonial = useUpdateTestimonial(); - const deleteTestimonial = useDeleteTestimonial(); + }) + const createTestimonial = useCreateTestimonial() + const updateTestimonial = useUpdateTestimonial() + const deleteTestimonial = useDeleteTestimonial() - const testimonials: TTestimonialsListItem[] = testimonialsData?.data ?? []; - const totalItems = testimonialsData?.meta?.total ?? testimonials.length; + const testimonials: TTestimonialsListItem[] = testimonialsData?.data ?? [] + const totalItems = testimonialsData?.meta?.total ?? testimonials.length const handleAdd = async (): Promise => { if (pendingFormData.current) { - await createTestimonial.mutateAsync(pendingFormData.current); + await createTestimonial.mutateAsync(pendingFormData.current) } - return true; - }; + return true + } const handleUpdate = async (): Promise => { if (selectedTestimonial && pendingFormData.current) { - await updateTestimonial.mutateAsync({ id: selectedTestimonial.id, data: pendingFormData.current }); + await updateTestimonial.mutateAsync({ id: selectedTestimonial.id, data: pendingFormData.current }) } - return true; - }; + return true + } const handleDelete = async (): Promise => { if (selectedTestimonial) { - await deleteTestimonial.mutateAsync(selectedTestimonial.id); + await deleteTestimonial.mutateAsync(selectedTestimonial.id) } - return true; - }; + return true + } const columns: ColumnDef[] = [ { @@ -119,8 +124,8 @@ export const Components: FC = (): ReactElement => { header: 'Content', accessorKey: 'content', cell: ({ row }) => { - const content = row.original.content; - return content.length > 80 ? `${content.substring(0, 80)}...` : content; + const content = row.original.content + return content.length > 80 ? `${content.substring(0, 80)}...` : content }, }, { @@ -141,9 +146,9 @@ export const Components: FC = (): ReactElement => { variant="primary" size="sm" onClick={(e) => { - e.stopPropagation(); - setSelectedTestimonial(row.original); - setShowModalUpdateItem(true); + e.stopPropagation() + setSelectedTestimonial(row.original) + setShowModalUpdateItem(true) }} className="flex items-center gap-2" > @@ -153,9 +158,9 @@ export const Components: FC = (): ReactElement => { variant="danger" size="sm" onClick={(e) => { - e.stopPropagation(); - setSelectedTestimonial(row.original); - setShowModalDeleteItem(true); + e.stopPropagation() + setSelectedTestimonial(row.original) + setShowModalDeleteItem(true) }} className="flex items-center gap-2" > @@ -164,7 +169,7 @@ export const Components: FC = (): ReactElement => { ), }, - ]; + ] const table = useReactTable({ data: testimonials, @@ -180,7 +185,7 @@ export const Components: FC = (): ReactElement => { onPaginationChange: setPagination, pageCount: Math.ceil(totalItems / pagination.pageSize), manualPagination: true, - }); + }) return ( @@ -236,7 +241,7 @@ export const Components: FC = (): ReactElement => { prevStep={prevStep} resetStep={resetStep} handleAdd={handleAdd} - onDataCapture={(data) => { pendingFormData.current = data; }} + onDataCapture={(data) => { pendingFormData.current = data }} /> { role: selectedTestimonial.role, content: selectedTestimonial.content, } : undefined} - onDataCapture={(data) => { pendingFormData.current = data; }} + onDataCapture={(data) => { pendingFormData.current = data }} /> { handleDelete={handleDelete} /> - ); -}; - -export default Components; + ) +} diff --git a/apps/backoffice/src/app/(protected)/dashboard-dimentorin/page.tsx b/apps/backoffice/src/routes/_authenticated/dashboard-dimentorin.tsx similarity index 82% rename from apps/backoffice/src/app/(protected)/dashboard-dimentorin/page.tsx rename to apps/backoffice/src/routes/_authenticated/dashboard-dimentorin.tsx index 63b3716..0d00b8d 100644 --- a/apps/backoffice/src/app/(protected)/dashboard-dimentorin/page.tsx +++ b/apps/backoffice/src/routes/_authenticated/dashboard-dimentorin.tsx @@ -1,20 +1,25 @@ -import { Button } from "@imphnen-frontend-service/ui/atoms"; -import { BackofficeWrapper } from "@imphnen-frontend-service/ui/organisms"; -import { For } from "@imphnen-frontend-service/utils"; -import { ReactElement } from "react"; -import { UserGrowthChart } from "./_components/chart/user-growth"; -import { SessionStatusChart } from "./_components/chart/session-status"; -import { useMentorList, useUserList, useMySessions } from "@imphnen-frontend-service/service"; +import { createFileRoute } from '@tanstack/react-router' +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { BackofficeWrapper } from '@imphnen-frontend-service/ui/organisms' +import { For } from '@imphnen-frontend-service/utils' +import { ReactElement } from 'react' +import { UserGrowthChart } from './_components/dashboard-dimentorin/chart/user-growth' +import { SessionStatusChart } from './_components/dashboard-dimentorin/chart/session-status' +import { useMentorList, useUserList, useMySessions } from '@imphnen-frontend-service/service' -export default function Components(): ReactElement { - const { data: mentorData } = useMentorList({ per_page: 5, sort_by: 'rating', order: 'desc' }); - const { data: userData } = useUserList({ per_page: 1 }); - const { data: sessionsData } = useMySessions(); +export const Route = createFileRoute('/_authenticated/dashboard-dimentorin')({ + component: DashboardDimentorinPage, +}) - const totalMentors = mentorData?.meta?.total ?? 0; - const totalUsers = userData?.meta?.total ?? 0; - const totalSessions = sessionsData?.total ?? 0; - const topMentors = mentorData?.data ?? []; +function DashboardDimentorinPage(): ReactElement { + const { data: mentorData } = useMentorList({ per_page: 5, sort_by: 'rating', order: 'desc' }) + const { data: userData } = useUserList({ per_page: 1 }) + const { data: sessionsData } = useMySessions() + + const totalMentors = mentorData?.meta?.total ?? 0 + const totalUsers = userData?.meta?.total ?? 0 + const totalSessions = sessionsData?.total ?? 0 + const topMentors = mentorData?.data ?? [] const overviewStats = [ { label: 'Total Users', value: totalUsers }, @@ -22,7 +27,7 @@ export default function Components(): ReactElement { { label: 'Total Sessions', value: totalSessions }, { label: 'Active Mentors', value: topMentors.filter((m) => m.status === 'active').length }, { label: 'Completed Sessions', value: sessionsData?.sessions?.filter((s) => s.status === 'completed').length ?? 0 }, - ]; + ] return ( @@ -113,20 +118,20 @@ export default function Components(): ReactElement { {(() => { - const sessions = sessionsData?.sessions ?? []; - const topicCount: Record = {}; + const sessions = sessionsData?.sessions ?? [] + const topicCount: Record = {} sessions.forEach((s) => { - topicCount[s.topic] = (topicCount[s.topic] ?? 0) + 1; - }); + topicCount[s.topic] = (topicCount[s.topic] ?? 0) + 1 + }) const topTopics = Object.entries(topicCount) .sort(([, a], [, b]) => b - a) - .slice(0, 5); + .slice(0, 5) if (topTopics.length === 0) { return ( Belum ada data - ); + ) } return topTopics.map(([topic, count], index) => ( @@ -134,7 +139,7 @@ export default function Components(): ReactElement { {topic} {count} - )); + )) })()} diff --git a/apps/backoffice/src/app/(protected)/dashboard/page.tsx b/apps/backoffice/src/routes/_authenticated/dashboard.tsx similarity index 85% rename from apps/backoffice/src/app/(protected)/dashboard/page.tsx rename to apps/backoffice/src/routes/_authenticated/dashboard.tsx index f5d3e08..c830069 100644 --- a/apps/backoffice/src/app/(protected)/dashboard/page.tsx +++ b/apps/backoffice/src/routes/_authenticated/dashboard.tsx @@ -1,16 +1,17 @@ +import { createFileRoute } from '@tanstack/react-router' import { PlusOutlined, ReloadOutlined, UsergroupAddOutlined, UsergroupDeleteOutlined, UserSwitchOutlined, -} from '@ant-design/icons'; -import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { FC, Fragment, ReactElement, useRef, useState } from 'react'; -import ModalAddItem from './_components/modal-add-item'; -import ModalEditItem from './_components/modal-edit-item'; -import ModalDeleteItem from './_components/modal-delete-item'; -import { useQueryState } from '@imphnen-frontend-service/utils'; +} from '@ant-design/icons' +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { FC, Fragment, ReactElement, useRef, useState } from 'react' +import ModalAddItem from './_components/dashboard/modal-add-item' +import ModalEditItem from './_components/dashboard/modal-edit-item' +import ModalDeleteItem from './_components/dashboard/modal-delete-item' +import { useQueryState } from '@imphnen-frontend-service/utils' import { useUserList, useGachaItemList, @@ -18,14 +19,18 @@ import { useUpdateGachaItem, useDeleteGachaItem, TGachaItemDto, -} from '@imphnen-frontend-service/service'; +} from '@imphnen-frontend-service/service' -export const Components: FC = (): ReactElement => { - const [showModalAddItem, setShowModalAddItem] = useState(false); - const [showModalEditItem, setShowModalEditItem] = useState(false); - const [showModalDeleteItem, setShowModalDeleteItem] = useState(false); - const [selectedItem, setSelectedItem] = useState(null); - const pendingFormData = useRef(null); +export const Route = createFileRoute('/_authenticated/dashboard')({ + component: DashboardPage, +}) + +function DashboardPage() { + const [showModalAddItem, setShowModalAddItem] = useState(false) + const [showModalEditItem, setShowModalEditItem] = useState(false) + const [showModalDeleteItem, setShowModalDeleteItem] = useState(false) + const [selectedItem, setSelectedItem] = useState(null) + const pendingFormData = useRef(null) const { step: currentStep, @@ -36,20 +41,20 @@ export const Components: FC = (): ReactElement => { defaultValue: 1, maxValue: 2, minValue: 1, - }); + }) - const { data: usersData } = useUserList({ per_page: 1 }); - const { data: gachaItemsData } = useGachaItemList({ per_page: 9 }); - const createItem = useCreateGachaItem(); - const updateItem = useUpdateGachaItem(); - const deleteItem = useDeleteGachaItem(); + const { data: usersData } = useUserList({ per_page: 1 }) + const { data: gachaItemsData } = useGachaItemList({ per_page: 9 }) + const createItem = useCreateGachaItem() + const updateItem = useUpdateGachaItem() + const deleteItem = useDeleteGachaItem() - const totalUsers = usersData?.meta?.total ?? 0; - const gachaItems: TGachaItemDto[] = gachaItemsData?.data ?? []; + const totalUsers = usersData?.meta?.total ?? 0 + const gachaItems: TGachaItemDto[] = gachaItemsData?.data ?? [] const handleAdd = async (): Promise => { if (pendingFormData.current) { - const { itemName, quantity } = pendingFormData.current; + const { itemName, quantity } = pendingFormData.current await createItem.mutateAsync({ item_code: (itemName as string).toLowerCase().replace(/\s+/g, '-'), name: itemName, @@ -61,28 +66,28 @@ export const Components: FC = (): ReactElement => { weight: 1, stock: quantity ?? 1, is_limited: false, - }); + }) } - return true; - }; + return true + } const handleEdit = async (): Promise => { if (selectedItem && pendingFormData.current) { - const { itemName, quantity } = pendingFormData.current; + const { itemName, quantity } = pendingFormData.current await updateItem.mutateAsync({ id: selectedItem.id, data: { name: itemName, stock: quantity }, - }); + }) } - return true; - }; + return true + } const handleDelete = async (): Promise => { if (selectedItem) { - await deleteItem.mutateAsync(selectedItem.id); + await deleteItem.mutateAsync(selectedItem.id) } - return true; - }; + return true + } return ( @@ -181,8 +186,8 @@ export const Components: FC = (): ReactElement => { size="sm" className="text-[10px] text-neutral-500 p-0 font-normal hover:bg-transparent hover:text-primary-500" onClick={() => { - setSelectedItem(item); - setShowModalEditItem(true); + setSelectedItem(item) + setShowModalEditItem(true) }} > Edit @@ -192,8 +197,8 @@ export const Components: FC = (): ReactElement => { size="sm" className="text-[10px] text-red-500 p-0 font-normal hover:bg-transparent hover:text-red-700" onClick={() => { - setSelectedItem(item); - setShowModalDeleteItem(true); + setSelectedItem(item) + setShowModalDeleteItem(true) }} > Delete @@ -224,7 +229,7 @@ export const Components: FC = (): ReactElement => { prevStep={prevStep} resetStep={resetStep} handleAddItem={handleAdd} - onDataCapture={(data) => { pendingFormData.current = data; }} + onDataCapture={(data) => { pendingFormData.current = data }} /> { resetStep={resetStep} handleEditItem={handleEdit} initialValues={selectedItem ? { itemName: selectedItem.name } : undefined} - onDataCapture={(data) => { pendingFormData.current = data; }} + onDataCapture={(data) => { pendingFormData.current = data }} /> setShowModalDeleteItem(false)} - handleDeleteItem={async () => { await handleDelete(); return true; }} + handleDeleteItem={async () => { await handleDelete(); return true }} /> - ); -}; - -export default Components; + ) +} diff --git a/apps/backoffice/src/app/(protected)/feedback-review-dimentorin/page.tsx b/apps/backoffice/src/routes/_authenticated/feedback-review-dimentorin.tsx similarity index 82% rename from apps/backoffice/src/app/(protected)/feedback-review-dimentorin/page.tsx rename to apps/backoffice/src/routes/_authenticated/feedback-review-dimentorin.tsx index d901b86..2814065 100644 --- a/apps/backoffice/src/app/(protected)/feedback-review-dimentorin/page.tsx +++ b/apps/backoffice/src/routes/_authenticated/feedback-review-dimentorin.tsx @@ -1,10 +1,11 @@ -import { SearchOutlined } from "@ant-design/icons"; -import { Button, Input, Select } from "@imphnen-frontend-service/ui/atoms"; -import { BackofficeWrapper, DataTable } from "@imphnen-frontend-service/ui/organisms"; -import { cn, For } from "@imphnen-frontend-service/utils"; -import { ColumnDef, getCoreRowModel, getPaginationRowModel, PaginationState, RowSelectionState, useReactTable } from "@tanstack/react-table"; -import { ReactElement, useState } from "react" -import { useMySessions, TSessionListItem } from "@imphnen-frontend-service/service"; +import { createFileRoute } from '@tanstack/react-router' +import { SearchOutlined } from '@ant-design/icons' +import { Button, Input, Select } from '@imphnen-frontend-service/ui/atoms' +import { BackofficeWrapper, DataTable } from '@imphnen-frontend-service/ui/organisms' +import { cn, For } from '@imphnen-frontend-service/utils' +import { ColumnDef, getCoreRowModel, getPaginationRowModel, PaginationState, RowSelectionState, useReactTable } from '@tanstack/react-table' +import { ReactElement, useState } from 'react' +import { useMySessions, TSessionListItem } from '@imphnen-frontend-service/service' const TABS = { MENTORING: 'Mentoring', @@ -12,30 +13,34 @@ const TABS = { } as const type Tabs = typeof TABS[keyof typeof TABS] -export default function Components(): ReactElement { +export const Route = createFileRoute('/_authenticated/feedback-review-dimentorin')({ + component: FeedbackReviewDimentorinPage, +}) + +function FeedbackReviewDimentorinPage(): ReactElement { const [activeTab, setActiveTab] = useState(TABS.MENTORING) const [rowSelection, setRowSelection] = useState({}) const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 9, - }); + }) const { data: sessionsData, isLoading } = useMySessions( activeTab === TABS.MENTORING ? { status: 'completed' } : undefined - ); + ) const sessions: TSessionListItem[] = activeTab === TABS.MENTORING ? (sessionsData?.sessions ?? []) - : []; + : [] const totalItems = activeTab === TABS.MENTORING ? (sessionsData?.total ?? sessions.length) - : 0; + : 0 const columns: ColumnDef[] = [ { id: 'select', - meta: { cellClassName: cn("w-20") }, + meta: { cellClassName: cn('w-20') }, header: ({ table }) => ( { - const hasRating = !!row.original.rating; + const hasRating = !!row.original.rating return (
{hasRating ? 'Done' : 'To Do'}
- ); + ) }, }, { header: 'Action', - meta: { cellClassName: cn("w-72") }, + meta: { cellClassName: cn('w-72') }, cell: () => ( ), }, - ]; + ] const table = useReactTable({ data: mockData, @@ -184,7 +187,7 @@ export const Components: FC = (): ReactElement => { onPaginationChange: setPagination, pageCount: Math.ceil(mockData.length / pagination.pageSize), manualPagination: false, - }); + }) return ( @@ -219,7 +222,7 @@ export const Components: FC = (): ReactElement => { options={deliveryOptions} onClose={() => setShowFilter(false)} onFilterChange={(value) => { - console.log('Selected filter:', value); + console.log('Selected filter:', value) }} /> @@ -234,11 +237,9 @@ export const Components: FC = (): ReactElement => { isOpen={showModalProcessDelivery} onClose={() => setShowModalProcessDelivery(false)} handleProcessDelivery={() => { - console.log('Action ketika user menekan tombol Proses Pengiriman'); + console.log('Action ketika user menekan tombol Proses Pengiriman') }} /> - ); -}; - -export default Components; + ) +} diff --git a/apps/backoffice/src/app/(protected)/roadmap-dimentorin/page.tsx b/apps/backoffice/src/routes/_authenticated/roadmap-dimentorin.tsx similarity index 83% rename from apps/backoffice/src/app/(protected)/roadmap-dimentorin/page.tsx rename to apps/backoffice/src/routes/_authenticated/roadmap-dimentorin.tsx index d2a0410..ab0b966 100644 --- a/apps/backoffice/src/app/(protected)/roadmap-dimentorin/page.tsx +++ b/apps/backoffice/src/routes/_authenticated/roadmap-dimentorin.tsx @@ -1,10 +1,11 @@ -import { DeleteOutlined, EditOutlined, PlusOutlined, SearchOutlined } from "@ant-design/icons"; -import { Button, Input, Select } from "@imphnen-frontend-service/ui/atoms"; -import { BackofficeWrapper, DataTable } from "@imphnen-frontend-service/ui/organisms"; -import { cn } from "@imphnen-frontend-service/utils"; -import { ColumnDef, getCoreRowModel, getPaginationRowModel, PaginationState, RowSelectionState, useReactTable } from "@tanstack/react-table"; -import { useState } from "react"; -import { ModalCreateRoadmap } from "./_components/modal/create-roadmap"; +import { createFileRoute } from '@tanstack/react-router' +import { DeleteOutlined, EditOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons' +import { Button, Input, Select } from '@imphnen-frontend-service/ui/atoms' +import { BackofficeWrapper, DataTable } from '@imphnen-frontend-service/ui/organisms' +import { cn } from '@imphnen-frontend-service/utils' +import { ColumnDef, getCoreRowModel, getPaginationRowModel, PaginationState, RowSelectionState, useReactTable } from '@tanstack/react-table' +import { useState } from 'react' +import { ModalCreateRoadmap } from './_components/roadmap-dimentorin/modal/create-roadmap' type LearningStatus = 'active' | 'inactive' @@ -22,19 +23,23 @@ const mockData: RoadmapType[] = Array.from({ length: 90 }, (_, i) => ({ status: i % 2 === 0 ? 'active' : 'inactive', })) -export default function Components(): React.ReactElement { +export const Route = createFileRoute('/_authenticated/roadmap-dimentorin')({ + component: RoadmapDimentorinPage, +}) + +function RoadmapDimentorinPage(): React.ReactElement { const [openCreateModal, setOpenCreateModal] = useState(false) const [rowSelection, setRowSelection] = useState({}) const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 9, - }); + }) const columns: ColumnDef[] = [ { id: 'select', - meta: { cellClassName: cn("w-20") }, + meta: { cellClassName: cn('w-20') }, header: ({ table }) => ( { - const status = row.original.status; + const status = row.original.status const statusColors: Record = { inactive: 'bg-danger-200 text-danger-700', active: 'bg-success-200 text-success-500', - }; + } const statusText: Record = { inactive: 'Inactive', active: 'Active', - }; + } return (
{statusText[status]}
- ); + ) }, }, { header: 'Action', - meta: { cellClassName: cn("w-72") }, + meta: { cellClassName: cn('w-72') }, cell: ({ row }) => (
), }, - ]; + ] const table = useReactTable({ data: roles, @@ -162,7 +167,7 @@ export const Components: FC = (): ReactElement => { onPaginationChange: setPagination, pageCount: Math.ceil(totalItems / pagination.pageSize), manualPagination: true, - }); + }) return ( @@ -218,7 +223,7 @@ export const Components: FC = (): ReactElement => { prevStep={prevStep} resetStep={resetStep} handleAdd={handleAdd} - onDataCapture={(data) => { pendingFormData.current = data; }} + onDataCapture={(data) => { pendingFormData.current = data }} /> { resetStep={resetStep} handleUpdate={handleUpdate} initialValues={selectedRole ? { name: selectedRole.name } : undefined} - onDataCapture={(data) => { pendingFormData.current = data; }} + onDataCapture={(data) => { pendingFormData.current = data }} /> { handleDelete={handleDelete} /> - ); -}; - -export default Components; + ) +} diff --git a/apps/backoffice/src/app/(protected)/session-dimentorin/page.tsx b/apps/backoffice/src/routes/_authenticated/session-dimentorin.tsx similarity index 78% rename from apps/backoffice/src/app/(protected)/session-dimentorin/page.tsx rename to apps/backoffice/src/routes/_authenticated/session-dimentorin.tsx index 5db060f..5679668 100644 --- a/apps/backoffice/src/app/(protected)/session-dimentorin/page.tsx +++ b/apps/backoffice/src/routes/_authenticated/session-dimentorin.tsx @@ -1,33 +1,38 @@ -import { SearchOutlined } from "@ant-design/icons"; -import { Button, Input, Select } from "@imphnen-frontend-service/ui/atoms"; -import { BackofficeWrapper, DataTable } from "@imphnen-frontend-service/ui/organisms"; -import { cn } from "@imphnen-frontend-service/utils"; -import { ColumnDef, getCoreRowModel, getPaginationRowModel, PaginationState, RowSelectionState, useReactTable } from "@tanstack/react-table"; -import { ReactElement, useState } from "react"; -import { ModalDetailSession } from "./_components/modal/detail"; -import { useMySessions, TSessionListItem } from "@imphnen-frontend-service/service"; +import { createFileRoute } from '@tanstack/react-router' +import { SearchOutlined } from '@ant-design/icons' +import { Button, Input, Select } from '@imphnen-frontend-service/ui/atoms' +import { BackofficeWrapper, DataTable } from '@imphnen-frontend-service/ui/organisms' +import { cn } from '@imphnen-frontend-service/utils' +import { ColumnDef, getCoreRowModel, getPaginationRowModel, PaginationState, RowSelectionState, useReactTable } from '@tanstack/react-table' +import { ReactElement, useState } from 'react' +import { ModalDetailSession } from './_components/session-dimentorin/modal/detail' +import { useMySessions, TSessionListItem } from '@imphnen-frontend-service/service' -export default function Components(): ReactElement { - const [openDetail, setOpenDetail] = useState(false); - const [statusFilter, setStatusFilter] = useState(''); +export const Route = createFileRoute('/_authenticated/session-dimentorin')({ + component: SessionDimentorinPage, +}) + +function SessionDimentorinPage(): ReactElement { + const [openDetail, setOpenDetail] = useState(false) + const [statusFilter, setStatusFilter] = useState('') const [rowSelection, setRowSelection] = useState({}) const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 9, - }); + }) const { data: sessionsData, isLoading } = useMySessions( statusFilter ? { status: statusFilter } : undefined - ); + ) - const sessions: TSessionListItem[] = sessionsData?.sessions ?? []; - const totalItems = sessionsData?.total ?? sessions.length; + const sessions: TSessionListItem[] = sessionsData?.sessions ?? [] + const totalItems = sessionsData?.total ?? sessions.length const columns: ColumnDef[] = [ { id: 'select', - meta: { cellClassName: cn("w-20") }, + meta: { cellClassName: cn('w-20') }, header: ({ table }) => ( { - const status = row.original.status; + const status = row.original.status const statusColors: Record = { pending: 'bg-warning-200 text-warning-700', confirmed: 'bg-primary-200 text-primary-700', ongoing: 'bg-warning-200 text-warning-700', completed: 'bg-success-200 text-success-500', cancelled: 'bg-danger-200 text-danger-500', - }; + } return (
{status}
- ); + ) }, }, { header: 'Action', - meta: { cellClassName: cn("w-52") }, + meta: { cellClassName: cn('w-52') }, cell: () => ( ), }, - ]; + ] const table = useReactTable({ data: mockTransactions, @@ -143,7 +147,7 @@ export const Components: FC = (): ReactElement => { onPaginationChange: setPagination, pageCount: Math.ceil(mockTransactions.length / pagination.pageSize), manualPagination: false, - }); + }) return ( @@ -180,7 +184,7 @@ export const Components: FC = (): ReactElement => { options={validationOptions} onClose={() => setShowFilter(false)} onFilterChange={(value) => { - console.log('Selected filter:', value); + console.log('Selected filter:', value) }} /> @@ -195,14 +199,12 @@ export const Components: FC = (): ReactElement => { isOpen={showModalValidate} onClose={() => setShowModalValidate(false)} handleValid={() => { - console.log('Action ketika user klik Valid'); + console.log('Action ketika user klik Valid') }} handleInvalid={() => { - console.log('Action ketika user klik Tidak Valid'); + console.log('Action ketika user klik Tidak Valid') }} /> - ); -}; - -export default Components; + ) +} diff --git a/apps/backoffice/src/app/(protected)/users-dimentorin/page.tsx b/apps/backoffice/src/routes/_authenticated/users-dimentorin.tsx similarity index 84% rename from apps/backoffice/src/app/(protected)/users-dimentorin/page.tsx rename to apps/backoffice/src/routes/_authenticated/users-dimentorin.tsx index d307e77..b1c7268 100644 --- a/apps/backoffice/src/app/(protected)/users-dimentorin/page.tsx +++ b/apps/backoffice/src/routes/_authenticated/users-dimentorin.tsx @@ -1,10 +1,11 @@ -import { SearchOutlined } from '@ant-design/icons'; -import { Button, Input, Select } from '@imphnen-frontend-service/ui/atoms'; +import { createFileRoute } from '@tanstack/react-router' +import { SearchOutlined } from '@ant-design/icons' +import { Button, Input, Select } from '@imphnen-frontend-service/ui/atoms' import { BackofficeWrapper, DataTable, -} from '@imphnen-frontend-service/ui/organisms'; -import { cn, For } from '@imphnen-frontend-service/utils'; +} from '@imphnen-frontend-service/ui/organisms' +import { cn, For } from '@imphnen-frontend-service/utils' import { ColumnDef, getCoreRowModel, @@ -12,48 +13,52 @@ import { PaginationState, RowSelectionState, useReactTable, -} from '@tanstack/react-table'; -import { ReactElement, useState } from 'react'; -import { ModalDetailUser } from './_components/modal/detail'; +} from '@tanstack/react-table' +import { ReactElement, useState } from 'react' +import { ModalDetailUser } from './_components/users-dimentorin/modal/detail' import { useMentorList, useUserList, MentorDetailResponseDto, TUsersListItem, -} from '@imphnen-frontend-service/service'; +} from '@imphnen-frontend-service/service' -export default function Components(): ReactElement { - const TABS = ['mentor', 'mentee'] as const; - const [activeTab, setActiveTab] = useState<'mentor' | 'mentee'>('mentor'); - const [showDetail, setShowDetail] = useState(false); - const [selectedUserId, setSelectedUserId] = useState(null); - const [search, setSearch] = useState(''); +export const Route = createFileRoute('/_authenticated/users-dimentorin')({ + component: UsersDimentorinPage, +}) - const [rowSelection, setRowSelection] = useState({}); +function UsersDimentorinPage(): ReactElement { + const TABS = ['mentor', 'mentee'] as const + const [activeTab, setActiveTab] = useState<'mentor' | 'mentee'>('mentor') + const [showDetail, setShowDetail] = useState(false) + const [selectedUserId, setSelectedUserId] = useState(null) + const [search, setSearch] = useState('') + + const [rowSelection, setRowSelection] = useState({}) const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 9, - }); + }) const { data: mentorData, isLoading: mentorLoading } = useMentorList({ search, page: pagination.pageIndex + 1, per_page: pagination.pageSize, - }); + }) const { data: menteeData, isLoading: menteeLoading } = useUserList({ search, page: pagination.pageIndex + 1, per_page: pagination.pageSize, - }); + }) - const mentors: MentorDetailResponseDto[] = mentorData?.data ?? []; - const mentees: TUsersListItem[] = menteeData?.data ?? []; - const mentorTotal = mentorData?.meta?.total ?? mentors.length; - const menteeTotal = menteeData?.meta?.total ?? mentees.length; + const mentors: MentorDetailResponseDto[] = mentorData?.data ?? [] + const mentees: TUsersListItem[] = menteeData?.data ?? [] + const mentorTotal = mentorData?.meta?.total ?? mentors.length + const menteeTotal = menteeData?.meta?.total ?? mentees.length - const isLoading = activeTab === 'mentor' ? mentorLoading : menteeLoading; - const totalItems = activeTab === 'mentor' ? mentorTotal : menteeTotal; + const isLoading = activeTab === 'mentor' ? mentorLoading : menteeLoading + const totalItems = activeTab === 'mentor' ? mentorTotal : menteeTotal const mentorColumns: ColumnDef[] = [ { @@ -97,17 +102,17 @@ export default function Components(): ReactElement { header: 'Status', accessorKey: 'status', cell: ({ row }) => { - const status = row.original.status; + const status = row.original.status const statusColors: Record = { active: 'bg-success-200 text-success-500', pending: 'bg-warning-200 text-warning-700', inactive: 'bg-danger-200 text-danger-500', - }; + } return (
{status}
- ); + ) }, }, { @@ -118,9 +123,9 @@ export default function Components(): ReactElement { variant="primary" size="sm" onClick={(e) => { - e.stopPropagation(); - setSelectedUserId(row.original.id); - setShowDetail(true); + e.stopPropagation() + setSelectedUserId(row.original.id) + setShowDetail(true) }} className="flex items-center gap-2 w-max" > @@ -128,7 +133,7 @@ export default function Components(): ReactElement { ), }, - ]; + ] const menteeColumns: ColumnDef[] = [ { @@ -179,9 +184,9 @@ export default function Components(): ReactElement { variant="primary" size="sm" onClick={(e) => { - e.stopPropagation(); - setSelectedUserId(row.original.id); - setShowDetail(true); + e.stopPropagation() + setSelectedUserId(row.original.id) + setShowDetail(true) }} className="flex items-center gap-2 w-max" > @@ -189,7 +194,7 @@ export default function Components(): ReactElement { ), }, - ]; + ] const mentorTable = useReactTable({ data: mentors, @@ -202,7 +207,7 @@ export default function Components(): ReactElement { onPaginationChange: setPagination, pageCount: Math.ceil(mentorTotal / pagination.pageSize), manualPagination: true, - }); + }) const menteeTable = useReactTable({ data: mentees, @@ -215,7 +220,7 @@ export default function Components(): ReactElement { onPaginationChange: setPagination, pageCount: Math.ceil(menteeTotal / pagination.pageSize), manualPagination: true, - }); + }) return ( @@ -234,8 +239,8 @@ export default function Components(): ReactElement { activeTab === tab && 'bg-white' )} onClick={() => { - setActiveTab(tab); - setPagination((p) => ({ ...p, pageIndex: 0 })); + setActiveTab(tab) + setPagination((p) => ({ ...p, pageIndex: 0 })) }} > {tab} @@ -275,5 +280,5 @@ export default function Components(): ReactElement { userId={selectedUserId} /> - ); + ) } diff --git a/apps/backoffice/src/routes/_public.tsx b/apps/backoffice/src/routes/_public.tsx new file mode 100644 index 0000000..877372e --- /dev/null +++ b/apps/backoffice/src/routes/_public.tsx @@ -0,0 +1,20 @@ +import { createFileRoute, Outlet, redirect } from '@tanstack/react-router' +import { SessionToken } from '@imphnen-frontend-service/service' + +export const Route = createFileRoute('/_public')({ + beforeLoad: () => { + const session = SessionToken.get() + if (session?.token?.access_token) { + throw redirect({ to: '/hackathon-dashboard' }) + } + }, + component: PublicLayout, +}) + +function PublicLayout() { + return ( +
+ +
+ ) +} diff --git a/apps/backoffice/src/app/(public)/auth/login/_hooks/use-login.ts b/apps/backoffice/src/routes/_public/auth/_hooks/use-login.ts similarity index 89% rename from apps/backoffice/src/app/(public)/auth/login/_hooks/use-login.ts rename to apps/backoffice/src/routes/_public/auth/_hooks/use-login.ts index e5a4a01..930e038 100644 --- a/apps/backoffice/src/app/(public)/auth/login/_hooks/use-login.ts +++ b/apps/backoffice/src/routes/_public/auth/_hooks/use-login.ts @@ -5,7 +5,7 @@ import { useBackofficeLogin, } from '@imphnen-frontend-service/service'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useNavigate } from 'react-router'; +import { useNavigate } from '@tanstack/react-router'; import { toast } from 'sonner'; export const useLogin = () => { @@ -25,7 +25,7 @@ export const useLogin = () => { try { await loginMutation.mutateAsync(data); toast.success('Login berhasil!'); - navigate('/hackathon-dashboard'); + navigate({ to: '/hackathon-dashboard' }); } catch (error) { console.error('[Backoffice Login] Error:', error); toast.error((error as Error).message || 'Login gagal'); diff --git a/apps/backoffice/src/app/(public)/auth/login/page.tsx b/apps/backoffice/src/routes/_public/auth/login.tsx similarity index 81% rename from apps/backoffice/src/app/(public)/auth/login/page.tsx rename to apps/backoffice/src/routes/_public/auth/login.tsx index 0c3b03a..bf23176 100644 --- a/apps/backoffice/src/app/(public)/auth/login/page.tsx +++ b/apps/backoffice/src/routes/_public/auth/login.tsx @@ -1,33 +1,38 @@ -import { useState } from 'react'; -import { useLogin, authLoginSchema, TLoginRequest } from '@imphnen-frontend-service/service'; -import { useNavigate } from 'react-router'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { toast } from 'sonner'; -import { Icon } from '@iconify/react'; +import { createFileRoute } from '@tanstack/react-router' +import { useState } from 'react' +import { useLogin, authLoginSchema, TLoginRequest } from '@imphnen-frontend-service/service' +import { useNavigate } from '@tanstack/react-router' +import { useForm } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' +import { toast } from 'sonner' +import { Icon } from '@iconify/react' -export default function LoginPage() { - const navigate = useNavigate(); - const loginMutation = useLogin(); - const [showPassword, setShowPassword] = useState(false); - const [error, setError] = useState(null); +export const Route = createFileRoute('/_public/auth/login')({ + component: LoginPage, +}) + +function LoginPage() { + const navigate = useNavigate() + const loginMutation = useLogin() + const [showPassword, setShowPassword] = useState(false) + const [error, setError] = useState(null) const { register, handleSubmit, formState: { errors, isValid } } = useForm({ resolver: zodResolver(authLoginSchema), mode: 'onChange', defaultValues: { email: '', password: '' }, - }); + }) const onSubmit = handleSubmit(async (data) => { - setError(null); + setError(null) try { - await loginMutation.mutateAsync(data); - toast.success('Login successful!'); - navigate('/'); + await loginMutation.mutateAsync(data) + toast.success('Login successful!') + navigate({ to: '/' }) } catch (err) { - setError((err as Error).message || 'Login failed'); + setError((err as Error).message || 'Login failed') } - }); + }) return (
@@ -89,5 +94,5 @@ export default function LoginPage() {
- ); + ) } diff --git a/apps/backoffice/src/routes/index.tsx b/apps/backoffice/src/routes/index.tsx new file mode 100644 index 0000000..20cb2a2 --- /dev/null +++ b/apps/backoffice/src/routes/index.tsx @@ -0,0 +1,12 @@ +import { createFileRoute, redirect } from '@tanstack/react-router' +import { SessionToken } from '@imphnen-frontend-service/service' + +export const Route = createFileRoute('/')({ + beforeLoad: () => { + const session = SessionToken.get() + if (session?.token?.access_token) { + throw redirect({ to: '/hackathon-dashboard' }) + } + throw redirect({ to: '/auth/login' }) + }, +}) diff --git a/apps/backoffice/vite.config.ts b/apps/backoffice/vite.config.ts index e54704f..ad58ed3 100644 --- a/apps/backoffice/vite.config.ts +++ b/apps/backoffice/vite.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; +import { TanStackRouterVite } from '@tanstack/router-plugin/vite'; export default defineConfig(() => ({ root: __dirname, @@ -15,7 +16,14 @@ export default defineConfig(() => ({ port: 3001, host: 'localhost', }, - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + plugins: [ + TanStackRouterVite({ + routeFileIgnorePattern: '_components|_hooks|_hook|_data', + }), + react(), + nxViteTsPaths(), + nxCopyAssetsPlugin(['*.md']), + ], build: { outDir: '../../dist/apps/backoffice', emptyOutDir: true, diff --git a/apps/dimentorin/src/app/(public)/(home)/page.tsx b/apps/dimentorin/src/app/(public)/(home)/page.tsx deleted file mode 100644 index 87b0dee..0000000 --- a/apps/dimentorin/src/app/(public)/(home)/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { HeroSection } from './_components/hero-section'; -import { WhatWeOfferSection } from './_components/what-we-offer-section'; -import { TestimonialSection } from './_components/testimonial-section'; -import { FAQSection } from './_components/faq-section'; -import { CTASection } from './_components/cta-section'; - -export const Components: FC = (): ReactElement => { - return ( - <> - - - - - - - ); -}; - -export default Components; diff --git a/apps/dimentorin/src/app/(public)/layout.tsx b/apps/dimentorin/src/app/(public)/layout.tsx deleted file mode 100644 index 5e12f2a..0000000 --- a/apps/dimentorin/src/app/(public)/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Outlet } from "react-router-dom"; -import { Header } from "./_components/header"; -import { Footer } from "./_components/footer"; - -export default function Layout() { - return ( -
-
-
- -
-
-
- ); -} diff --git a/apps/dimentorin/src/app/(public)/resources/page.tsx b/apps/dimentorin/src/app/(public)/resources/page.tsx deleted file mode 100644 index 52c95a9..0000000 --- a/apps/dimentorin/src/app/(public)/resources/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { ReactElement } from "react"; -import { Contribute } from "./_components/contribute"; -import { WorkWithUs } from "./_components/work-with-us"; -import { Collaborate } from "./_components/collaborate"; - -export default function Components(): ReactElement { - return ( -
- - - -
- ) -} diff --git a/apps/dimentorin/src/app/auth/forgot/layout.tsx b/apps/dimentorin/src/app/auth/forgot/layout.tsx deleted file mode 100644 index 2d8a74d..0000000 --- a/apps/dimentorin/src/app/auth/forgot/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
- -
- ); -}; - -export default AppLayout; diff --git a/apps/dimentorin/src/app/auth/forgot/otp/layout.tsx b/apps/dimentorin/src/app/auth/forgot/otp/layout.tsx deleted file mode 100644 index 2d8a74d..0000000 --- a/apps/dimentorin/src/app/auth/forgot/otp/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
- -
- ); -}; - -export default AppLayout; diff --git a/apps/dimentorin/src/app/auth/forgot/summon/layout.tsx b/apps/dimentorin/src/app/auth/forgot/summon/layout.tsx deleted file mode 100644 index 2d8a74d..0000000 --- a/apps/dimentorin/src/app/auth/forgot/summon/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
- -
- ); -}; - -export default AppLayout; diff --git a/apps/dimentorin/src/app/auth/google-callback/page.tsx b/apps/dimentorin/src/app/auth/google-callback/page.tsx deleted file mode 100644 index 12c4c16..0000000 --- a/apps/dimentorin/src/app/auth/google-callback/page.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { FC, ReactElement, useEffect } from 'react'; -import { useSearchParams, useNavigate } from 'react-router-dom'; -import { useGoogleCallback, useAuthStore } from '@imphnen-frontend-service/service'; -import { toast } from 'sonner'; - -export const GoogleCallbackPage: FC = (): ReactElement => { - const [searchParams] = useSearchParams(); - const navigate = useNavigate(); - const { setSession, clearSession } = useAuthStore(); - const { mutate: googleCallback } = useGoogleCallback(); - - useEffect(() => { - const handleCallback = async () => { - const code = searchParams.get('code'); - const state = searchParams.get('state'); - const error = searchParams.get('error'); - - if (error) { - toast.error('Google login dibatalkan atau terjadi kesalahan'); - navigate('/auth/login'); - return; - } - - if (!code || !state) { - toast.error('Parameter login Google tidak valid'); - navigate('/auth/login'); - return; - } - - try { - googleCallback( - { code, state }, - { - onSuccess: (response) => { - if (response.token && response.user) { - setSession({ - token: response.token, - user: response.user, - }); - toast.success('Login Google berhasil!'); - navigate('/dashboard'); - } else { - throw new Error('Response data tidak valid'); - } - }, - onError: (error) => { - console.error('Google OAuth callback error:', error); - toast.error('Login Google gagal'); - clearSession(); - navigate('/auth/login'); - }, - } - ); - } catch (error) { - console.error('Google OAuth callback error:', error); - toast.error('Login Google gagal'); - clearSession(); - navigate('/auth/login'); - } - }; - - handleCallback(); - }, [searchParams, navigate, setSession, clearSession, googleCallback]); - - return ( -
-
-
-

- Menyelesaikan Login Google... -

-

- Mohon tunggu sebentar, kami sedang memproses login Anda. -

-
-
- ); -}; - -export default GoogleCallbackPage; diff --git a/apps/dimentorin/src/app/auth/login/layout.tsx b/apps/dimentorin/src/app/auth/login/layout.tsx deleted file mode 100644 index 2d8a74d..0000000 --- a/apps/dimentorin/src/app/auth/login/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
- -
- ); -}; - -export default AppLayout; diff --git a/apps/dimentorin/src/app/auth/register-mentor/layout.tsx b/apps/dimentorin/src/app/auth/register-mentor/layout.tsx deleted file mode 100644 index 2d8a74d..0000000 --- a/apps/dimentorin/src/app/auth/register-mentor/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
- -
- ); -}; - -export default AppLayout; diff --git a/apps/dimentorin/src/app/auth/register-mentor/pending/layout.tsx b/apps/dimentorin/src/app/auth/register-mentor/pending/layout.tsx deleted file mode 100644 index 2d8a74d..0000000 --- a/apps/dimentorin/src/app/auth/register-mentor/pending/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
- -
- ); -}; - -export default AppLayout; diff --git a/apps/dimentorin/src/app/auth/register-mentor/success/layout.tsx b/apps/dimentorin/src/app/auth/register-mentor/success/layout.tsx deleted file mode 100644 index 2d8a74d..0000000 --- a/apps/dimentorin/src/app/auth/register-mentor/success/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
- -
- ); -}; - -export default AppLayout; diff --git a/apps/dimentorin/src/app/auth/register/layout.tsx b/apps/dimentorin/src/app/auth/register/layout.tsx deleted file mode 100644 index 2d8a74d..0000000 --- a/apps/dimentorin/src/app/auth/register/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
- -
- ); -}; - -export default AppLayout; diff --git a/apps/dimentorin/src/app/auth/register/otp/layout.tsx b/apps/dimentorin/src/app/auth/register/otp/layout.tsx deleted file mode 100644 index 2d8a74d..0000000 --- a/apps/dimentorin/src/app/auth/register/otp/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
- -
- ); -}; - -export default AppLayout; diff --git a/apps/dimentorin/src/app/auth/register/success/layout.tsx b/apps/dimentorin/src/app/auth/register/success/layout.tsx deleted file mode 100644 index 2d8a74d..0000000 --- a/apps/dimentorin/src/app/auth/register/success/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
- -
- ); -}; - -export default AppLayout; diff --git a/apps/dimentorin/src/app/layout.tsx b/apps/dimentorin/src/app/layout.tsx deleted file mode 100644 index bc85aa9..0000000 --- a/apps/dimentorin/src/app/layout.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Outlet, ScrollRestoration } from "react-router-dom"; - -export default function RootLayout() { - return ( - <> - - - - ) -} \ No newline at end of file diff --git a/apps/dimentorin/src/main.tsx b/apps/dimentorin/src/main.tsx index 5120810..39288b5 100644 --- a/apps/dimentorin/src/main.tsx +++ b/apps/dimentorin/src/main.tsx @@ -1,37 +1,22 @@ -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, - ModalLoginProvider, - 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 { ModalLoginProvider, 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( @@ -42,5 +27,4 @@ createRoot(rootElement).render( -); -// force deploy +) diff --git a/apps/dimentorin/src/middleware.ts b/apps/dimentorin/src/middleware.ts deleted file mode 100644 index 0d99b36..0000000 --- a/apps/dimentorin/src/middleware.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { - PERMISSIONS, - SessionToken, - SessionUser, -} from '@imphnen-frontend-service/service'; -import { LoaderFunctionArgs, redirect } from 'react-router'; - -const mappingPublicRoutes = [ - '/', - '/auth/login', - '/auth/forgot', - '/auth/forgot/otp', - '/auth/register', - '/auth/register/otp', - '/auth/register/success', - '/auth/new-password', - '/auth/register-mentor', - '/auth/register-mentor/pending', - '/auth/register-mentor/success', - '/auth/google-callback', - '/auth/google-oauth-popup', - '/resources', -];const mappingRoutePermissions = [ - { - path: '/dashboard', - permissions: [], - }, - { - path: '/users', - permissions: [PERMISSIONS.USERS.READ_LIST], - }, - { - path: '/users/create', - permissions: [PERMISSIONS.USERS.CREATE], - }, - { - path: '/users/update', - permissions: [PERMISSIONS.USERS.UPDATE], - }, - { - path: '/users/detail', - permissions: [PERMISSIONS.USERS.READ_DETAIL], - }, - { - path: '/roles', - permissions: [PERMISSIONS.ROLES.READ_LIST], - }, - { - path: '/roles/create', - permissions: [PERMISSIONS.ROLES.CREATE], - }, - { - path: '/roles/update', - permissions: [PERMISSIONS.ROLES.UPDATE], - }, - { - path: '/roles/detail', - permissions: [PERMISSIONS.ROLES.READ_DETAIL], - }, - { - path: '/permissions', - permissions: [PERMISSIONS.PERMISSIONS.READ_LIST], - }, - { - path: '/permissions/create', - permissions: [PERMISSIONS.PERMISSIONS.CREATE], - }, - { - path: '/permissions/update', - permissions: [PERMISSIONS.PERMISSIONS.UPDATE], - }, - { - path: '/permissions/detail', - permissions: [PERMISSIONS.PERMISSIONS.READ_DETAIL], - }, -]; - -const mappingPublicPrefixRoutes = [ - '/mentoring', - '/articles', -] - -export const middleware = async ({ request }: LoaderFunctionArgs) => { - const url = new URL(request.url); - const pathname = url.pathname; - const session = SessionUser.get(); - const session_token = SessionToken.get(); - const token = session_token?.token?.access_token; - const userPermissions = - session?.role?.permissions?.map?.((perm) => perm?.name) ?? []; - - if (mappingPublicPrefixRoutes.some((prefix) => pathname.startsWith(prefix))) { - return null; - } - - if (mappingPublicRoutes.includes(pathname)) { - if (token) return redirect('/dashboard'); - return null; - } - - if (!session) return redirect('/auth/login'); - - const matchedRoute = mappingRoutePermissions.find( - (route) => route.path === pathname - ); - - if (matchedRoute) { - const hasPermission = - !matchedRoute.permissions || - matchedRoute.permissions.some((perm) => userPermissions.includes(perm)); - - if (!hasPermission) { - return '/dashboard'; - } - } - - return null; -}; diff --git a/apps/dimentorin/src/routeTree.gen.ts b/apps/dimentorin/src/routeTree.gen.ts new file mode 100644 index 0000000..00d81b9 --- /dev/null +++ b/apps/dimentorin/src/routeTree.gen.ts @@ -0,0 +1,25 @@ +/* prettier-ignore-start */ + +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file is auto-generated by TanStack Router + +// Import Routes + +import { Route as rootRoute } from './routes/__root' + +// Create/Update Routes + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath {} +} + +// Create and export the route tree + +export const routeTree = rootRoute diff --git a/apps/dimentorin/src/routes/__root.tsx b/apps/dimentorin/src/routes/__root.tsx new file mode 100644 index 0000000..b0209d6 --- /dev/null +++ b/apps/dimentorin/src/routes/__root.tsx @@ -0,0 +1,10 @@ +import { createRootRoute, Outlet, ScrollRestoration } from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: () => ( + <> + + + + ), +}) diff --git a/apps/dimentorin/src/routes/_authenticated.tsx b/apps/dimentorin/src/routes/_authenticated.tsx new file mode 100644 index 0000000..ccce983 --- /dev/null +++ b/apps/dimentorin/src/routes/_authenticated.tsx @@ -0,0 +1,16 @@ +import { createFileRoute, Outlet, redirect } from '@tanstack/react-router' +import { SessionToken } from '@imphnen-frontend-service/service' + +export const Route = createFileRoute('/_authenticated')({ + beforeLoad: () => { + const session = SessionToken.get() + if (!session?.token?.access_token) { + throw redirect({ to: '/auth/login' }) + } + }, + component: AuthenticatedLayout, +}) + +function AuthenticatedLayout() { + return +} diff --git a/apps/dimentorin/src/app/dashboard/layout.tsx b/apps/dimentorin/src/routes/_authenticated/dashboard.tsx similarity index 81% rename from apps/dimentorin/src/app/dashboard/layout.tsx rename to apps/dimentorin/src/routes/_authenticated/dashboard.tsx index ca6234b..88ef5e9 100644 --- a/apps/dimentorin/src/app/dashboard/layout.tsx +++ b/apps/dimentorin/src/routes/_authenticated/dashboard.tsx @@ -1,21 +1,20 @@ -import { Outlet, Link, useLocation, useNavigate } from 'react-router'; -import { useAuthStore } from '@imphnen-frontend-service/service'; -import { Icon } from '@iconify/react'; -import { useEffect } from 'react'; +import { createFileRoute, Outlet, Link, useLocation, useNavigate } from '@tanstack/react-router' +import { useAuthStore } from '@imphnen-frontend-service/service' +import { Icon } from '@iconify/react' const navItems = [ { path: '/dashboard', label: 'Dashboard', icon: 'mdi:view-dashboard' }, { path: '/dashboard/settings', label: 'Settings', icon: 'mdi:cog' }, -]; +] -export default function DashboardLayout() { - const { session, clearSession } = useAuthStore(); - const location = useLocation(); - const navigate = useNavigate(); +export const Route = createFileRoute('/_authenticated/dashboard')({ + component: DashboardLayout, +}) - useEffect(() => { - if (!session?.token) navigate('/auth/login'); - }, [session, navigate]); +function DashboardLayout() { + const { session, clearSession } = useAuthStore() + const location = useLocation() + const navigate = useNavigate() return (
@@ -48,7 +47,7 @@ export default function DashboardLayout() { {session?.user?.fullname || session?.user?.email}
- ); + ) } diff --git a/apps/dimentorin/src/app/dashboard/page.tsx b/apps/dimentorin/src/routes/_authenticated/dashboard_/index.tsx similarity index 93% rename from apps/dimentorin/src/app/dashboard/page.tsx rename to apps/dimentorin/src/routes/_authenticated/dashboard_/index.tsx index a47db30..69baf64 100644 --- a/apps/dimentorin/src/app/dashboard/page.tsx +++ b/apps/dimentorin/src/routes/_authenticated/dashboard_/index.tsx @@ -1,28 +1,32 @@ -import { Link } from 'react-router'; +import { createFileRoute, Link } from '@tanstack/react-router' import { useAuthStore, useSessionQuery, useMySessions, useMentorMe, -} from '@imphnen-frontend-service/service'; -import { Icon } from '@iconify/react'; +} from '@imphnen-frontend-service/service' +import { Icon } from '@iconify/react' -export default function DashboardPage() { - const { session } = useAuthStore(); - const { data: meData } = useSessionQuery(['mentor', 'sessions']); - const { data: sessionsData } = useMySessions(); - const { data: mentorData } = useMentorMe(); +export const Route = createFileRoute('/_authenticated/dashboard/')({ + component: DashboardIndexPage, +}) - const user = session?.user; - const mentor = meData?.user?.mentor; - const sessions = sessionsData?.data || []; +function DashboardIndexPage() { + const { session } = useAuthStore() + const { data: meData } = useSessionQuery(['mentor', 'sessions']) + const { data: sessionsData } = useMySessions() + const { data: mentorData } = useMentorMe() + + const user = session?.user + const mentor = meData?.user?.mentor + const sessions = sessionsData?.data || [] const upcomingSessions = sessions.filter( (s: { status: string }) => s.status === 'confirmed' || s.status === 'pending' - ); + ) const completedSessions = sessions.filter( (s: { status: string }) => s.status === 'completed' - ); + ) return (
@@ -169,5 +173,5 @@ export default function DashboardPage() {
- ); + ) } diff --git a/apps/dimentorin/src/app/dashboard/settings/page.tsx b/apps/dimentorin/src/routes/_authenticated/dashboard_/settings.tsx similarity index 95% rename from apps/dimentorin/src/app/dashboard/settings/page.tsx rename to apps/dimentorin/src/routes/_authenticated/dashboard_/settings.tsx index 7974260..a55d81b 100644 --- a/apps/dimentorin/src/app/dashboard/settings/page.tsx +++ b/apps/dimentorin/src/routes/_authenticated/dashboard_/settings.tsx @@ -1,12 +1,13 @@ -import { useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router' +import { useState } from 'react' import { useAuthStore, useSessionQuery, -} from '@imphnen-frontend-service/service'; -import { Icon } from '@iconify/react'; -import { toast } from 'sonner'; +} from '@imphnen-frontend-service/service' +import { Icon } from '@iconify/react' +import { toast } from 'sonner' -type SettingsSection = 'account' | 'privacy' | 'preferences' | 'faq'; +type SettingsSection = 'account' | 'privacy' | 'preferences' | 'faq' const faqItems = [ { q: 'How do I book a mentoring session?', a: 'Browse available mentors, select one, and click "Book Session" to schedule a meeting.' }, @@ -14,22 +15,26 @@ const faqItems = [ { q: 'Can I cancel a session?', a: 'Yes, you can cancel a pending or confirmed session from your dashboard before the scheduled time.' }, { q: 'How do I update my profile?', a: 'Go to Settings > Account Details to update your personal information.' }, { q: 'Is my data secure?', a: 'Yes, we use encryption and secure protocols to protect your data. See our Privacy Policy for details.' }, -]; +] -export default function SettingsPage() { - const { session } = useAuthStore(); - const { data: meData } = useSessionQuery(); - const [activeSection, setActiveSection] = useState('account'); - const [expandedFaq, setExpandedFaq] = useState(null); +export const Route = createFileRoute('/_authenticated/dashboard/settings')({ + component: SettingsPage, +}) - const user = session?.user; +function SettingsPage() { + const { session } = useAuthStore() + const { data: meData } = useSessionQuery() + const [activeSection, setActiveSection] = useState('account') + const [expandedFaq, setExpandedFaq] = useState(null) + + const user = session?.user const sections = [ { id: 'account' as const, label: 'Account Details', icon: 'mdi:account-circle' }, { id: 'privacy' as const, label: 'Privacy & Security', icon: 'mdi:shield-lock' }, { id: 'preferences' as const, label: 'Preferences', icon: 'mdi:tune' }, { id: 'faq' as const, label: 'FAQ & Support', icon: 'mdi:help-circle' }, - ]; + ] return (
@@ -181,5 +186,5 @@ export default function SettingsPage() {
- ); + ) } diff --git a/apps/dimentorin/src/app/_components/icons/index.ts b/apps/dimentorin/src/routes/_components/icons/index.ts similarity index 100% rename from apps/dimentorin/src/app/_components/icons/index.ts rename to apps/dimentorin/src/routes/_components/icons/index.ts diff --git a/apps/dimentorin/src/app/_components/icons/steam.tsx b/apps/dimentorin/src/routes/_components/icons/steam.tsx similarity index 100% rename from apps/dimentorin/src/app/_components/icons/steam.tsx rename to apps/dimentorin/src/routes/_components/icons/steam.tsx diff --git a/apps/dimentorin/src/app/_hooks/use-google-login.ts b/apps/dimentorin/src/routes/_hooks/use-google-login.ts similarity index 96% rename from apps/dimentorin/src/app/_hooks/use-google-login.ts rename to apps/dimentorin/src/routes/_hooks/use-google-login.ts index 446e6a8..a187976 100644 --- a/apps/dimentorin/src/app/_hooks/use-google-login.ts +++ b/apps/dimentorin/src/routes/_hooks/use-google-login.ts @@ -1,6 +1,5 @@ import { useCallback } from 'react'; import { toast } from 'sonner'; -import { useNavigate } from 'react-router-dom'; import { useAuthStore } from '@imphnen-frontend-service/service'; export interface GoogleLoginResponse { @@ -38,7 +37,6 @@ export interface GoogleLoginResponse { } export const useGoogleLogin = () => { - const navigate = useNavigate(); const { setSession } = useAuthStore(); const handleGoogleLogin = useCallback(async () => { @@ -105,7 +103,7 @@ export const useGoogleLogin = () => { }); toast.success('Login berhasil!'); - navigate(0); + window.location.reload(); } else { toast.error('Data login tidak lengkap'); } @@ -129,7 +127,7 @@ export const useGoogleLogin = () => { console.error('Google login error:', error); toast.error('Terjadi kesalahan saat login dengan Google'); } - }, [navigate, setSession]); + }, [setSession]); return { handleGoogleLogin, diff --git a/apps/dimentorin/src/app/_hooks/use-login.ts b/apps/dimentorin/src/routes/_hooks/use-login.ts similarity index 100% rename from apps/dimentorin/src/app/_hooks/use-login.ts rename to apps/dimentorin/src/routes/_hooks/use-login.ts diff --git a/apps/dimentorin/src/app/_hooks/use-otp.ts b/apps/dimentorin/src/routes/_hooks/use-otp.ts similarity index 100% rename from apps/dimentorin/src/app/_hooks/use-otp.ts rename to apps/dimentorin/src/routes/_hooks/use-otp.ts diff --git a/apps/dimentorin/src/app/_hooks/use-register.ts b/apps/dimentorin/src/routes/_hooks/use-register.ts similarity index 100% rename from apps/dimentorin/src/app/_hooks/use-register.ts rename to apps/dimentorin/src/routes/_hooks/use-register.ts diff --git a/apps/dimentorin/src/app/_hooks/use-resend-otp.ts b/apps/dimentorin/src/routes/_hooks/use-resend-otp.ts similarity index 100% rename from apps/dimentorin/src/app/_hooks/use-resend-otp.ts rename to apps/dimentorin/src/routes/_hooks/use-resend-otp.ts diff --git a/apps/dimentorin/src/routes/_public.tsx b/apps/dimentorin/src/routes/_public.tsx new file mode 100644 index 0000000..6e2e1e9 --- /dev/null +++ b/apps/dimentorin/src/routes/_public.tsx @@ -0,0 +1,20 @@ +import { createFileRoute, Outlet, redirect } from '@tanstack/react-router' +import { SessionToken } from '@imphnen-frontend-service/service' + +export const Route = createFileRoute('/_public')({ + beforeLoad: () => { + const session = SessionToken.get() + if (session?.token?.access_token) { + throw redirect({ to: '/dashboard' }) + } + }, + component: PublicLayout, +}) + +function PublicLayout() { + return ( +
+ +
+ ) +} diff --git a/apps/dimentorin/src/app/auth/forgot/page.tsx b/apps/dimentorin/src/routes/_public/auth/forgot.tsx similarity index 71% rename from apps/dimentorin/src/app/auth/forgot/page.tsx rename to apps/dimentorin/src/routes/_public/auth/forgot.tsx index 31e9668..6fa127c 100644 --- a/apps/dimentorin/src/app/auth/forgot/page.tsx +++ b/apps/dimentorin/src/routes/_public/auth/forgot.tsx @@ -1,9 +1,14 @@ -import { FC, ReactElement } from 'react'; -import { RegisterResetBanner } from "@imphnen-frontend-service/ui/organisms"; -import { ForgotStep } from "@imphnen-frontend-service/ui/molecules"; -import { Button, Input } from '@imphnen-frontend-service/ui/atoms'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement } from 'react' +import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms' +import { ForgotStep } from '@imphnen-frontend-service/ui/molecules' +import { Button, Input } from '@imphnen-frontend-service/ui/atoms' -export const Components: FC = (): ReactElement => { +export const Route = createFileRoute('/_public/auth/forgot')({ + component: ForgotPasswordPage, +}) + +function ForgotPasswordPage(): ReactElement { return (
@@ -18,7 +23,5 @@ export const Components: FC = (): ReactElement => {
- ); -}; - -export default Components; \ No newline at end of file + ) +} diff --git a/apps/dimentorin/src/app/auth/forgot/otp/page.tsx b/apps/dimentorin/src/routes/_public/auth/forgot_/otp.tsx similarity index 61% rename from apps/dimentorin/src/app/auth/forgot/otp/page.tsx rename to apps/dimentorin/src/routes/_public/auth/forgot_/otp.tsx index 99d2daf..5998f87 100644 --- a/apps/dimentorin/src/app/auth/forgot/otp/page.tsx +++ b/apps/dimentorin/src/routes/_public/auth/forgot_/otp.tsx @@ -1,9 +1,14 @@ -import { FC, ReactElement } from 'react'; -import { RegisterResetBanner } from "@imphnen-frontend-service/ui/organisms"; -import { ForgotStep, OtpForm } from "@imphnen-frontend-service/ui/molecules"; -import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement } from 'react' +import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms' +import { ForgotStep, OtpForm } from '@imphnen-frontend-service/ui/molecules' +import { Button } from '@imphnen-frontend-service/ui/atoms' -export const Components: FC = (): ReactElement => { +export const Route = createFileRoute('/_public/auth/forgot/otp')({ + component: ForgotOtpPage, +}) + +function ForgotOtpPage(): ReactElement { return (
@@ -17,7 +22,5 @@ export const Components: FC = (): ReactElement => {
- ); -}; - -export default Components; \ No newline at end of file + ) +} diff --git a/apps/dimentorin/src/app/auth/forgot/summon/page.tsx b/apps/dimentorin/src/routes/_public/auth/forgot_/summon.tsx similarity index 78% rename from apps/dimentorin/src/app/auth/forgot/summon/page.tsx rename to apps/dimentorin/src/routes/_public/auth/forgot_/summon.tsx index 913a04f..9b815ea 100644 --- a/apps/dimentorin/src/app/auth/forgot/summon/page.tsx +++ b/apps/dimentorin/src/routes/_public/auth/forgot_/summon.tsx @@ -1,9 +1,14 @@ -import { FC, ReactElement } from 'react'; -import { RegisterResetBanner } from "@imphnen-frontend-service/ui/organisms"; -import { ForgotStep } from "@imphnen-frontend-service/ui/molecules"; -import { Button, Input } from '@imphnen-frontend-service/ui/atoms'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement } from 'react' +import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms' +import { ForgotStep } from '@imphnen-frontend-service/ui/molecules' +import { Button, Input } from '@imphnen-frontend-service/ui/atoms' -export const Components: FC = (): ReactElement => { +export const Route = createFileRoute('/_public/auth/forgot/summon')({ + component: ForgotSummonPage, +}) + +function ForgotSummonPage(): ReactElement { return (
@@ -20,15 +25,13 @@ export const Components: FC = (): ReactElement => {
💡 Tips dari kami:
-
Buat kombinasi huruf besar, kecil, angka, dan simbol untuk kekuatan maksimal!
-
Pastikan kamu ingat password-mu atau simpan di tempat aman~
+
Buat kombinasi huruf besar, kecil, angka, dan simbol untuk kekuatan maksimal!
+
Pastikan kamu ingat password-mu atau simpan di tempat aman~
Masukkan password baru dan bersiaplah untuk kembali bertualang!
- ); -}; - -export default Components; \ No newline at end of file + ) +} diff --git a/apps/dimentorin/src/routes/_public/auth/google-callback.tsx b/apps/dimentorin/src/routes/_public/auth/google-callback.tsx new file mode 100644 index 0000000..6c8f31f --- /dev/null +++ b/apps/dimentorin/src/routes/_public/auth/google-callback.tsx @@ -0,0 +1,83 @@ +import { createFileRoute, useNavigate } from '@tanstack/react-router' +import { FC, ReactElement, useEffect } from 'react' +import { useGoogleCallback, useAuthStore } from '@imphnen-frontend-service/service' +import { toast } from 'sonner' + +export const Route = createFileRoute('/_public/auth/google-callback')({ + validateSearch: (search: Record) => ({ + code: (search.code as string) || '', + state: (search.state as string) || '', + error: (search.error as string) || '', + }), + component: GoogleCallbackPage, +}) + +function GoogleCallbackPage(): ReactElement { + const { code, state, error: errorParam } = Route.useSearch() + const navigate = useNavigate() + const { setSession, clearSession } = useAuthStore() + const { mutate: googleCallback } = useGoogleCallback() + + useEffect(() => { + const handleCallback = async () => { + if (errorParam) { + toast.error('Google login dibatalkan atau terjadi kesalahan') + navigate({ to: '/auth/login' }) + return + } + + if (!code || !state) { + toast.error('Parameter login Google tidak valid') + navigate({ to: '/auth/login' }) + return + } + + try { + googleCallback( + { code, state }, + { + onSuccess: (response) => { + if (response.token && response.user) { + setSession({ + token: response.token, + user: response.user, + }) + toast.success('Login Google berhasil!') + navigate({ to: '/dashboard' }) + } else { + throw new Error('Response data tidak valid') + } + }, + onError: (error) => { + console.error('Google OAuth callback error:', error) + toast.error('Login Google gagal') + clearSession() + navigate({ to: '/auth/login' }) + }, + } + ) + } catch (error) { + console.error('Google OAuth callback error:', error) + toast.error('Login Google gagal') + clearSession() + navigate({ to: '/auth/login' }) + } + } + + handleCallback() + }, [code, state, errorParam, navigate, setSession, clearSession, googleCallback]) + + return ( +
+
+
+

+ Menyelesaikan Login Google... +

+

+ Mohon tunggu sebentar, kami sedang memproses login Anda. +

+
+
+ ) +} diff --git a/apps/dimentorin/src/app/auth/google-oauth-popup/page.tsx b/apps/dimentorin/src/routes/_public/auth/google-oauth-popup.tsx similarity index 62% rename from apps/dimentorin/src/app/auth/google-oauth-popup/page.tsx rename to apps/dimentorin/src/routes/_public/auth/google-oauth-popup.tsx index a3f76fa..615a709 100644 --- a/apps/dimentorin/src/app/auth/google-oauth-popup/page.tsx +++ b/apps/dimentorin/src/routes/_public/auth/google-oauth-popup.tsx @@ -1,35 +1,40 @@ -import { FC, ReactElement, useEffect } from 'react'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement, useEffect } from 'react' -let globalIsProcessed = false; +let globalIsProcessed = false -export const GoogleOAuthPopupPage: FC = (): ReactElement => { +export const Route = createFileRoute('/_public/auth/google-oauth-popup')({ + component: GoogleOAuthPopupPage, +}) + +function GoogleOAuthPopupPage(): ReactElement { useEffect(() => { if (globalIsProcessed) { - return; + return } const callBackend = async (code: string, state: string) => { if (globalIsProcessed) { - return; + return } - globalIsProcessed = true; + globalIsProcessed = true try { - const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:4099'; - let callbackUrl; + const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:4099' + let callbackUrl if (baseUrl.endsWith('/v1')) { - callbackUrl = `${baseUrl}/auth/google/callback`; + callbackUrl = `${baseUrl}/auth/google/callback` } else { - callbackUrl = `${baseUrl}/v1/auth/google/callback`; + callbackUrl = `${baseUrl}/v1/auth/google/callback` } - const url = new URL(callbackUrl); - url.searchParams.append('code', code); - url.searchParams.append('state', state); - url.searchParams.append('redirect_uri', `${window.location.origin}/auth/google-oauth-popup`); + const url = new URL(callbackUrl) + url.searchParams.append('code', code) + url.searchParams.append('state', state) + url.searchParams.append('redirect_uri', `${window.location.origin}/auth/google-oauth-popup`) const response = await fetch(url.toString(), { method: 'GET', @@ -39,14 +44,14 @@ export const GoogleOAuthPopupPage: FC = (): ReactElement => { }, mode: 'cors', credentials: 'omit', - }); + }) if (!response.ok) { - const errorText = await response.text(); - throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`); + const errorText = await response.text() + throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`) } - const data = await response.json(); + const data = await response.json() window.opener?.postMessage( { @@ -54,11 +59,11 @@ export const GoogleOAuthPopupPage: FC = (): ReactElement => { payload: data, }, window.location.origin - ); - window.close(); + ) + window.close() } catch (error) { - globalIsProcessed = false; + globalIsProcessed = false window.opener?.postMessage( { @@ -66,41 +71,41 @@ export const GoogleOAuthPopupPage: FC = (): ReactElement => { error: `Failed to process OAuth callback: ${error instanceof Error ? error.message : String(error)}`, }, window.location.origin - ); - window.close(); + ) + window.close() } - }; + } const handleOAuthResponse = () => { - const isPopup = window.opener && window.opener !== window; + const isPopup = window.opener && window.opener !== window const detectJsonResponse = () => { try { - const bodyText = document.body.innerText || document.body.textContent || ''; - const trimmedText = bodyText.trim(); + const bodyText = document.body.innerText || document.body.textContent || '' + const trimmedText = bodyText.trim() if (trimmedText.startsWith('{') && trimmedText.endsWith('}')) { - const parsedJson = JSON.parse(trimmedText); + const parsedJson = JSON.parse(trimmedText) if (parsedJson && typeof parsedJson === 'object') { - const hasAccessToken = parsedJson.access_token || parsedJson.token || parsedJson.accessToken; + const hasAccessToken = parsedJson.access_token || parsedJson.token || parsedJson.accessToken if (hasAccessToken) { - return parsedJson; + return parsedJson } } } } catch { - return null; + return null } - return null; - }; + return null + } const checkOAuthParams = () => { - const urlParams = new URLSearchParams(window.location.search); - const code = urlParams.get('code'); - const state = urlParams.get('state'); - const error = urlParams.get('error'); + const urlParams = new URLSearchParams(window.location.search) + const code = urlParams.get('code') + const state = urlParams.get('state') + const error = urlParams.get('error') if (error) { if (isPopup) { @@ -110,23 +115,23 @@ export const GoogleOAuthPopupPage: FC = (): ReactElement => { error: error, }, window.location.origin - ); - window.close(); + ) + window.close() } - return true; + return true } if (code && state) { if (isPopup) { - callBackend(code, state); + callBackend(code, state) } - return true; + return true } - return false; - }; + return false + } - const immediateJson = detectJsonResponse(); + const immediateJson = detectJsonResponse() if (immediateJson && isPopup) { window.opener?.postMessage( { @@ -134,21 +139,21 @@ export const GoogleOAuthPopupPage: FC = (): ReactElement => { payload: immediateJson, }, window.location.origin - ); - window.close(); - return; + ) + window.close() + return } if (checkOAuthParams()) { - return; + return } - let attempts = 0; - const maxAttempts = 50; + let attempts = 0 + const maxAttempts = 50 const checkForJson = () => { - attempts++; - const jsonResponse = detectJsonResponse(); + attempts++ + const jsonResponse = detectJsonResponse() if (jsonResponse && isPopup) { window.opener?.postMessage( @@ -157,13 +162,13 @@ export const GoogleOAuthPopupPage: FC = (): ReactElement => { payload: jsonResponse, }, window.location.origin - ); - window.close(); - return; + ) + window.close() + return } if (attempts < maxAttempts) { - setTimeout(checkForJson, 500); + setTimeout(checkForJson, 500) } else if (isPopup) { window.opener?.postMessage( { @@ -171,16 +176,16 @@ export const GoogleOAuthPopupPage: FC = (): ReactElement => { error: 'Timeout waiting for response', }, window.location.origin - ); - window.close(); + ) + window.close() } - }; + } - setTimeout(checkForJson, 1000); - }; + setTimeout(checkForJson, 1000) + } - setTimeout(handleOAuthResponse, 100); - }, []); + setTimeout(handleOAuthResponse, 100) + }, []) return (
@@ -194,7 +199,5 @@ export const GoogleOAuthPopupPage: FC = (): ReactElement => {

- ); -}; - -export default GoogleOAuthPopupPage; + ) +} diff --git a/apps/dimentorin/src/app/auth/login/page.tsx b/apps/dimentorin/src/routes/_public/auth/login.tsx similarity index 74% rename from apps/dimentorin/src/app/auth/login/page.tsx rename to apps/dimentorin/src/routes/_public/auth/login.tsx index fe78e61..23f660a 100644 --- a/apps/dimentorin/src/app/auth/login/page.tsx +++ b/apps/dimentorin/src/routes/_public/auth/login.tsx @@ -1,68 +1,72 @@ -import { useState, useEffect } from 'react'; -import { useGitHubAuth, useLogin, authLoginSchema, TLoginRequest } from '@imphnen-frontend-service/service'; -import { GithubOutlined } from '@ant-design/icons'; -import { useNavigate, Link } from 'react-router'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { toast } from 'sonner'; -import { Icon } from '@iconify/react'; +import { createFileRoute, Link, useNavigate } from '@tanstack/react-router' +import { useState, useEffect } from 'react' +import { useGitHubAuth, useLogin, authLoginSchema, TLoginRequest } from '@imphnen-frontend-service/service' +import { GithubOutlined } from '@ant-design/icons' +import { useForm } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' +import { toast } from 'sonner' +import { Icon } from '@iconify/react' -export default function LoginPage() { - const navigate = useNavigate(); - const { signInWithGitHub } = useGitHubAuth(); - const loginMutation = useLogin(); - const [isGithubLoading, setIsGithubLoading] = useState(false); - const [error, setError] = useState(null); - const [showPassword, setShowPassword] = useState(false); +export const Route = createFileRoute('/_public/auth/login')({ + component: LoginPage, +}) + +function LoginPage() { + const navigate = useNavigate() + const { signInWithGitHub } = useGitHubAuth() + const loginMutation = useLogin() + const [isGithubLoading, setIsGithubLoading] = useState(false) + const [error, setError] = useState(null) + const [showPassword, setShowPassword] = useState(false) const { register, handleSubmit, formState: { errors, isValid } } = useForm({ resolver: zodResolver(authLoginSchema), mode: 'onChange', defaultValues: { email: '', password: '' }, - }); + }) useEffect(() => { - const hashParams = new URLSearchParams(globalThis.location.hash.substring(1)); - const urlParams = new URLSearchParams(globalThis.location.search); - const accessToken = hashParams.get('access_token') || urlParams.get('access_token'); - const type = hashParams.get('type') || urlParams.get('type'); + const hashParams = new URLSearchParams(globalThis.location.hash.substring(1)) + const urlParams = new URLSearchParams(globalThis.location.search) + const accessToken = hashParams.get('access_token') || urlParams.get('access_token') + const type = hashParams.get('type') || urlParams.get('type') if (accessToken && (type === 'recovery' || type === 'magiclink' || !type)) { - toast.info('Redirecting to password reset...'); - navigate('/auth/reset-password?access_token=' + accessToken); + toast.info('Redirecting to password reset...') + navigate({ to: '/auth/login', search: { access_token: accessToken } }) } - }, [navigate]); + }, [navigate]) const onSubmit = handleSubmit(async (data) => { - setError(null); + setError(null) try { - await loginMutation.mutateAsync(data); - toast.success('Login successful!'); - navigate('/dashboard'); + await loginMutation.mutateAsync(data) + toast.success('Login successful!') + navigate({ to: '/dashboard' }) } catch (err) { - setError((err as Error).message || 'Login failed'); + setError((err as Error).message || 'Login failed') } - }); + }) const handleGithubLogin = async () => { try { - setIsGithubLoading(true); - const result = await signInWithGitHub(); - if (result?.url) globalThis.location.href = result.url; - else { setIsGithubLoading(false); setError('Failed to get GitHub OAuth URL'); } + setIsGithubLoading(true) + const result = await signInWithGitHub() + if (result?.url) globalThis.location.href = result.url + else { setIsGithubLoading(false); setError('Failed to get GitHub OAuth URL') } } catch (err) { - setError((err as Error).message || 'GitHub login failed'); - setIsGithubLoading(false); + setError((err as Error).message || 'GitHub login failed') + setIsGithubLoading(false) } - }; + } return (
- +
@@ -87,7 +91,7 @@ export default function LoginPage() {
- Forgot password? + Forgot password?
-

Don't have an account? Sign up

+

Don't have an account? Sign up

By signing in, you agree to our Terms of Service and Privacy Policy

- ); + ) } diff --git a/apps/dimentorin/src/app/auth/register-mentor/page.tsx b/apps/dimentorin/src/routes/_public/auth/register-mentor.tsx similarity index 94% rename from apps/dimentorin/src/app/auth/register-mentor/page.tsx rename to apps/dimentorin/src/routes/_public/auth/register-mentor.tsx index 88b6305..d1966cd 100644 --- a/apps/dimentorin/src/app/auth/register-mentor/page.tsx +++ b/apps/dimentorin/src/routes/_public/auth/register-mentor.tsx @@ -1,10 +1,15 @@ -import { FC, ReactElement, useState } from 'react'; -import { ControlledInputField, RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms'; -import { Button, Select } from '@imphnen-frontend-service/ui/atoms'; -import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons'; -import { InputField, RegisterMentorStep, SelectField } from '@imphnen-frontend-service/ui/molecules'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement, useState } from 'react' +import { ControlledInputField, RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms' +import { Button, Select } from '@imphnen-frontend-service/ui/atoms' +import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons' +import { InputField, RegisterMentorStep, SelectField } from '@imphnen-frontend-service/ui/molecules' -export const Components: FC = (): ReactElement => { +export const Route = createFileRoute('/_public/auth/register-mentor')({ + component: RegisterMentorPage, +}) + +function RegisterMentorPage(): ReactElement { const [ step, setStep ] = useState(1) return ( @@ -118,7 +123,7 @@ export const Components: FC = (): ReactElement => { { /> {
- ); -}; - -export default Components; + ) +} diff --git a/apps/dimentorin/src/app/auth/register-mentor/pending/page.tsx b/apps/dimentorin/src/routes/_public/auth/register-mentor_/pending.tsx similarity index 82% rename from apps/dimentorin/src/app/auth/register-mentor/pending/page.tsx rename to apps/dimentorin/src/routes/_public/auth/register-mentor_/pending.tsx index d25e357..c664df8 100644 --- a/apps/dimentorin/src/app/auth/register-mentor/pending/page.tsx +++ b/apps/dimentorin/src/routes/_public/auth/register-mentor_/pending.tsx @@ -1,9 +1,14 @@ -import { FC, ReactElement, useEffect } from 'react'; -import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms'; -import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { ArrowRightOutlined } from '@ant-design/icons'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement } from 'react' +import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms' +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { ArrowRightOutlined } from '@ant-design/icons' -export const Components: FC = (): ReactElement => { +export const Route = createFileRoute('/_public/auth/register-mentor/pending')({ + component: RegisterMentorPendingPage, +}) + +function RegisterMentorPendingPage(): ReactElement { return (
@@ -19,7 +24,7 @@ export const Components: FC = (): ReactElement => { - +

@@ -35,7 +40,5 @@ export const Components: FC = (): ReactElement => {

- ); -}; - -export default Components; + ) +} diff --git a/apps/dimentorin/src/app/auth/register-mentor/success/page.tsx b/apps/dimentorin/src/routes/_public/auth/register-mentor_/success.tsx similarity index 84% rename from apps/dimentorin/src/app/auth/register-mentor/success/page.tsx rename to apps/dimentorin/src/routes/_public/auth/register-mentor_/success.tsx index c318b6b..376e4a8 100644 --- a/apps/dimentorin/src/app/auth/register-mentor/success/page.tsx +++ b/apps/dimentorin/src/routes/_public/auth/register-mentor_/success.tsx @@ -1,9 +1,14 @@ -import { FC, ReactElement, useEffect } from 'react'; -import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms'; -import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { ArrowRightOutlined } from '@ant-design/icons'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement } from 'react' +import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms' +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { ArrowRightOutlined } from '@ant-design/icons' -export const Components: FC = (): ReactElement => { +export const Route = createFileRoute('/_public/auth/register-mentor/success')({ + component: RegisterMentorSuccessPage, +}) + +function RegisterMentorSuccessPage(): ReactElement { return (
@@ -51,7 +56,5 @@ export const Components: FC = (): ReactElement => {
- ); -}; - -export default Components; + ) +} diff --git a/apps/dimentorin/src/app/auth/register/page.tsx b/apps/dimentorin/src/routes/_public/auth/register.tsx similarity index 87% rename from apps/dimentorin/src/app/auth/register/page.tsx rename to apps/dimentorin/src/routes/_public/auth/register.tsx index bdf60a6..5ee855e 100644 --- a/apps/dimentorin/src/app/auth/register/page.tsx +++ b/apps/dimentorin/src/routes/_public/auth/register.tsx @@ -1,11 +1,16 @@ -import { FC, ReactElement } from 'react'; -import { ControlledInputField, RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms'; -import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { ArrowLeftOutlined } from '@ant-design/icons'; -import { useRegisterHook } from '../../_hooks/use-register'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement } from 'react' +import { ControlledInputField, RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms' +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { ArrowLeftOutlined } from '@ant-design/icons' +import { useRegisterHook } from '../../_hooks/use-register' -export const Components: FC = (): ReactElement => { - const { form, onSubmit, isLoading } = useRegisterHook(); +export const Route = createFileRoute('/_public/auth/register')({ + component: RegisterPage, +}) + +function RegisterPage(): ReactElement { + const { form, onSubmit, isLoading } = useRegisterHook() return (
@@ -54,7 +59,7 @@ export const Components: FC = (): ReactElement => { placeholder="Contoh : 088877665544" control={form.control} name={'phone_number'} - /> + />
{ type="password" placeholder="Buat password sekeren jurus ultimate-mu!" control={form.control} - name={'password'} + name={'password'} />
"Senpai~! Pastikan password-mu sekuat pertahanan kastil!" @@ -107,7 +112,5 @@ export const Components: FC = (): ReactElement => { - ); -}; - -export default Components; + ) +} diff --git a/apps/dimentorin/src/app/auth/register/otp/page.tsx b/apps/dimentorin/src/routes/_public/auth/register_/otp.tsx similarity index 58% rename from apps/dimentorin/src/app/auth/register/otp/page.tsx rename to apps/dimentorin/src/routes/_public/auth/register_/otp.tsx index 9026713..ab41bea 100644 --- a/apps/dimentorin/src/app/auth/register/otp/page.tsx +++ b/apps/dimentorin/src/routes/_public/auth/register_/otp.tsx @@ -1,15 +1,22 @@ -import { FC, ReactElement, useEffect, useState } from 'react'; -import { ControlledInputField, RegisterResetBanner } from "@imphnen-frontend-service/ui/organisms"; -import { useOtpHook } from '../../../_hooks/use-otp'; -import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { useSearchParams } from 'react-router-dom'; -import { useResendOtpHook } from '../../../_hooks/use-resend-otp'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement, useEffect, useState } from 'react' +import { ControlledInputField, RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms' +import { useOtpHook } from '../../../_hooks/use-otp' +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { useResendOtpHook } from '../../../_hooks/use-resend-otp' -export const Components: FC = (): ReactElement => { +export const Route = createFileRoute('/_public/auth/register/otp')({ + validateSearch: (search: Record) => ({ + email: (search.email as string) || '', + }), + component: RegisterOtpPage, +}) + +function RegisterOtpPage(): ReactElement { const { form, onSubmit, isLoading } = useOtpHook() - const [ searchParams ] = useSearchParams() - const [ disabled, setDisabled] = useState(true); - const [ timeLeft, setTimeLeft ] = useState(5 * 60); + const { email } = Route.useSearch() + const [disabled, setDisabled] = useState(true) + const [timeLeft, setTimeLeft] = useState(5 * 60) const { resendOTP } = useResendOtpHook() useEffect(() => { @@ -17,22 +24,22 @@ export const Components: FC = (): ReactElement => { const interval = setInterval(() => { setTimeLeft(prev => { if (prev <= 1) { - clearInterval(interval); - setDisabled(false); - return 0; + clearInterval(interval) + setDisabled(false) + return 0 } - return prev - 1; - }); - }, 1000); - return () => clearInterval(interval); + return prev - 1 + }) + }, 1000) + return () => clearInterval(interval) } - }, [disabled]); + }, [disabled]) const formatTime = (seconds: number) => { - const m = Math.floor(seconds / 60); - const s = seconds % 60; - return `${m}:${s.toString().padStart(2, '0')}`; - }; + const m = Math.floor(seconds / 60) + const s = seconds % 60 + return `${m}:${s.toString().padStart(2, '0')}` + } return (
@@ -40,7 +47,7 @@ export const Components: FC = (): ReactElement => {

Verifikasi Email

-
Yeay~! Pesan dari dunia lain sudah dikirimkan ke {searchParams.get("email")}
+
Yeay~! Pesan dari dunia lain sudah dikirimkan ke {email}
{ name={'otp'} maxLength={6} control={form.control} - /> + />
- ); -}; - -export default Components; \ No newline at end of file + ) +} diff --git a/apps/dimentorin/src/app/auth/register/success/page.tsx b/apps/dimentorin/src/routes/_public/auth/register_/success.tsx similarity index 85% rename from apps/dimentorin/src/app/auth/register/success/page.tsx rename to apps/dimentorin/src/routes/_public/auth/register_/success.tsx index 7c6ed5b..55e7c15 100644 --- a/apps/dimentorin/src/app/auth/register/success/page.tsx +++ b/apps/dimentorin/src/routes/_public/auth/register_/success.tsx @@ -1,9 +1,14 @@ -import { FC, ReactElement, useEffect } from 'react'; -import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms'; -import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { ArrowRightOutlined } from '@ant-design/icons'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement, useEffect } from 'react' +import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms' +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { ArrowRightOutlined } from '@ant-design/icons' -export const Components: FC = (): ReactElement => { +export const Route = createFileRoute('/_public/auth/register/success')({ + component: RegisterSuccessPage, +}) + +function RegisterSuccessPage(): ReactElement { useEffect(() => { setTimeout(() => { document.location.href = "/auth/login" @@ -57,7 +62,5 @@ export const Components: FC = (): ReactElement => { - ); -}; - -export default Components; + ) +} diff --git a/apps/dimentorin/src/routes/_site.tsx b/apps/dimentorin/src/routes/_site.tsx new file mode 100644 index 0000000..ac6fe45 --- /dev/null +++ b/apps/dimentorin/src/routes/_site.tsx @@ -0,0 +1,19 @@ +import { createFileRoute, Outlet } from '@tanstack/react-router' +import { Header } from './_site/_components/header' +import { Footer } from './_site/_components/footer' + +export const Route = createFileRoute('/_site')({ + component: SiteLayout, +}) + +function SiteLayout() { + return ( +
+
+
+ +
+
+
+ ) +} diff --git a/apps/dimentorin/src/app/(public)/_components/footer.tsx b/apps/dimentorin/src/routes/_site/_components/footer.tsx similarity index 99% rename from apps/dimentorin/src/app/(public)/_components/footer.tsx rename to apps/dimentorin/src/routes/_site/_components/footer.tsx index 8fa3a0e..7c69841 100644 --- a/apps/dimentorin/src/app/(public)/_components/footer.tsx +++ b/apps/dimentorin/src/routes/_site/_components/footer.tsx @@ -1,7 +1,7 @@ import { DiscordOutlined, GithubOutlined, InstagramOutlined } from '@ant-design/icons'; import { cn, For } from '@imphnen-frontend-service/utils'; import React, { FC, useRef } from 'react'; -import { Link } from 'react-router-dom'; +import { Link } from '@tanstack/react-router'; import { motion, useInView, Variants } from 'framer-motion'; import { SteamIcon } from '../../_components/icons'; diff --git a/apps/dimentorin/src/app/(public)/_components/header.tsx b/apps/dimentorin/src/routes/_site/_components/header.tsx similarity index 96% rename from apps/dimentorin/src/app/(public)/_components/header.tsx rename to apps/dimentorin/src/routes/_site/_components/header.tsx index 5d78641..7faae0b 100644 --- a/apps/dimentorin/src/app/(public)/_components/header.tsx +++ b/apps/dimentorin/src/routes/_site/_components/header.tsx @@ -3,7 +3,7 @@ import { Button } from "@imphnen-frontend-service/ui/atoms"; import { cn, For, Show } from "@imphnen-frontend-service/utils"; import { motion, useMotionValueEvent, useScroll, Variants } from "framer-motion"; import { FC, useMemo, useState } from "react"; -import { Link, NavLink, useLocation } from "react-router-dom"; +import { Link, useLocation } from "@tanstack/react-router"; const MENUS: { label: string; href: string }[] = [ { label: "Home", href: "/" }, @@ -73,7 +73,7 @@ export const Header: FC = () => { isActive(menu.href) && "text-primary-500 hover:text-primary-500" )} > - {menu.label} + {menu.label} )} diff --git a/apps/dimentorin/src/app/(public)/(home)/_components/cta-section.tsx b/apps/dimentorin/src/routes/_site/_components/home/cta-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/(home)/_components/cta-section.tsx rename to apps/dimentorin/src/routes/_site/_components/home/cta-section.tsx diff --git a/apps/dimentorin/src/app/(public)/(home)/_components/faq-section.tsx b/apps/dimentorin/src/routes/_site/_components/home/faq-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/(home)/_components/faq-section.tsx rename to apps/dimentorin/src/routes/_site/_components/home/faq-section.tsx diff --git a/apps/dimentorin/src/app/(public)/(home)/_components/hero-section.tsx b/apps/dimentorin/src/routes/_site/_components/home/hero-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/(home)/_components/hero-section.tsx rename to apps/dimentorin/src/routes/_site/_components/home/hero-section.tsx diff --git a/apps/dimentorin/src/app/(public)/(home)/_components/testimonial-section.tsx b/apps/dimentorin/src/routes/_site/_components/home/testimonial-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/(home)/_components/testimonial-section.tsx rename to apps/dimentorin/src/routes/_site/_components/home/testimonial-section.tsx diff --git a/apps/dimentorin/src/app/(public)/(home)/_components/what-we-offer-section.tsx b/apps/dimentorin/src/routes/_site/_components/home/what-we-offer-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/(home)/_components/what-we-offer-section.tsx rename to apps/dimentorin/src/routes/_site/_components/home/what-we-offer-section.tsx diff --git a/apps/dimentorin/src/app/(public)/articles/page.tsx b/apps/dimentorin/src/routes/_site/articles.tsx similarity index 88% rename from apps/dimentorin/src/app/(public)/articles/page.tsx rename to apps/dimentorin/src/routes/_site/articles.tsx index 51c09ec..aff3d63 100644 --- a/apps/dimentorin/src/app/(public)/articles/page.tsx +++ b/apps/dimentorin/src/routes/_site/articles.tsx @@ -1,13 +1,18 @@ -import { ReactElement, useRef, useState } from "react"; -import { ArticleCard } from "./_components/card/article"; -import { cn, For } from "@imphnen-frontend-service/utils"; -import { Button } from "@imphnen-frontend-service/ui/atoms"; -import { motion, useInView, Variants } from "framer-motion"; +import { createFileRoute } from '@tanstack/react-router' +import { ReactElement, useRef, useState } from 'react' +import { ArticleCard } from './articles_/_components/card/article' +import { cn, For } from '@imphnen-frontend-service/utils' +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { motion, useInView, Variants } from 'framer-motion' const CATEGORIES = ['UI/UX Design', 'Software/Web Dev', 'Data & AI', 'Cloud & DevOps', 'Cybersecurity', 'IT & Network', 'Project Management', 'QA & Testing'] as const type Category = typeof CATEGORIES[number] -export default function Components(): ReactElement { +export const Route = createFileRoute('/_site/articles')({ + component: ArticlesPage, +}) + +function ArticlesPage(): ReactElement { const [activeTab, setActiveTab] = useState('UI/UX Design') const ref = useRef(null) diff --git a/apps/dimentorin/src/app/(public)/articles/[slug]/page.tsx b/apps/dimentorin/src/routes/_site/articles_/$slug.tsx similarity index 92% rename from apps/dimentorin/src/app/(public)/articles/[slug]/page.tsx rename to apps/dimentorin/src/routes/_site/articles_/$slug.tsx index 6dda909..abbaa4d 100644 --- a/apps/dimentorin/src/app/(public)/articles/[slug]/page.tsx +++ b/apps/dimentorin/src/routes/_site/articles_/$slug.tsx @@ -1,9 +1,14 @@ -import { Icon } from "@iconify/react"; -import { Button } from "@imphnen-frontend-service/ui/atoms"; -import { cn } from "@imphnen-frontend-service/utils"; -import { motion } from "framer-motion"; +import { createFileRoute } from '@tanstack/react-router' +import { Icon } from '@iconify/react' +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { cn } from '@imphnen-frontend-service/utils' +import { motion } from 'framer-motion' -export default function DetailArticle() { +export const Route = createFileRoute('/_site/articles/$slug')({ + component: DetailArticlePage, +}) + +function DetailArticlePage() { return (
{ - const [search, setSearch] = useState(''); +export const Route = createFileRoute('/_site/mentoring')({ + component: MentoringPage, +}) + +function MentoringPage(): ReactElement { + const [search, setSearch] = useState('') const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 8, - }); + }) const { data: mentorData, isLoading } = useMentorList({ page: pagination.pageIndex + 1, per_page: pagination.pageSize, search: search || undefined, - }); + }) - const mentors = mentorData?.data ?? []; - const totalItems = mentorData?.meta?.total ?? 0; + const mentors = mentorData?.data ?? [] + const totalItems = mentorData?.meta?.total ?? 0 const table = useReactTable({ data: mentors, @@ -33,11 +38,11 @@ export const Components: FC = (): ReactElement => { getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), onPaginationChange: (updater) => { - setPagination(updater); + setPagination(updater) }, pageCount: Math.ceil(totalItems / pagination.pageSize) || 1, manualPagination: true, - }); + }) const ref = useRef(null) const isInView = useInView(ref, { once: true, amount: 0.2 }) @@ -101,8 +106,8 @@ export const Components: FC = (): ReactElement => { className="relative min-w-full w-full" value={search} onChange={(e) => { - setSearch(e.target.value); - setPagination((p) => ({ ...p, pageIndex: 0 })); + setSearch(e.target.value) + setPagination((p) => ({ ...p, pageIndex: 0 })) }} /> @@ -134,5 +139,3 @@ export const Components: FC = (): ReactElement => {
) } - -export default Components diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/page.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id.tsx similarity index 72% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/page.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id.tsx index b3cc2ea..0e40111 100644 --- a/apps/dimentorin/src/app/(public)/mentoring/[id]/page.tsx +++ b/apps/dimentorin/src/routes/_site/mentoring_/$id.tsx @@ -1,19 +1,23 @@ +import { createFileRoute } from '@tanstack/react-router' import { FC, useState } from 'react' -import { useParams } from 'react-router-dom' -import { ProfileSection } from './_components/sections/profile' -import { StatisticsSection } from './_components/sections/senpai-statistics' -import { TopicsSection } from './_components/sections/topics' -import { ExperienceSection } from './_components/sections/experience' -import { EducationSection } from './_components/sections/education' -import { SenpaiScheduleSection } from './_components/sections/senpai-schedule' +import { ProfileSection } from './$id/_components/sections/profile' +import { StatisticsSection } from './$id/_components/sections/senpai-statistics' +import { TopicsSection } from './$id/_components/sections/topics' +import { ExperienceSection } from './$id/_components/sections/experience' +import { EducationSection } from './$id/_components/sections/education' +import { SenpaiScheduleSection } from './$id/_components/sections/senpai-schedule' import { Button } from '@imphnen-frontend-service/ui/atoms' -import { AppointmentModal } from './_components/modals/appointment' +import { AppointmentModal } from './$id/_components/modals/appointment' import { useMentorById } from '@imphnen-frontend-service/service' -export const Components: FC = () => { +export const Route = createFileRoute('/_site/mentoring/$id')({ + component: MentorDetailPage, +}) + +function MentorDetailPage() { const [open, setOpen] = useState(false) - const params = useParams() - const mentorId = params?.id ?? '' + const { id } = Route.useParams() + const mentorId = id ?? '' const { data: mentor, isLoading } = useMentorById(mentorId) if (isLoading) { @@ -63,5 +67,3 @@ export const Components: FC = () => { ) } - -export default Components diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/index.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/index.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/index.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/index.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/payment.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/payment.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/payment.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/payment.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/profile.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/profile.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/profile.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/profile.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/qris-payement.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/qris-payement.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/qris-payement.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/qris-payement.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/schedule.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/schedule.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/schedule.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/schedule.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/success.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/success.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/success.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/success.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/topic.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/topic.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/topic.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/topic.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/va-payment.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/va-payment.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/va-payment.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/modals/appointment/steps/va-payment.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/education.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/education.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/education.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/education.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/experience.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/experience.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/experience.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/experience.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/profile.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/profile.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/profile.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/profile.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/senpai-schedule.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/senpai-schedule.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/senpai-schedule.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/senpai-schedule.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/senpai-statistics.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/senpai-statistics.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/senpai-statistics.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/senpai-statistics.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/topics.tsx b/apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/topics.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/[id]/_components/sections/topics.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/$id/_components/sections/topics.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/_components/banner-section.tsx b/apps/dimentorin/src/routes/_site/mentoring_/_components/banner-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/_components/banner-section.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/_components/banner-section.tsx diff --git a/apps/dimentorin/src/app/(public)/mentoring/_components/mentor-card.tsx b/apps/dimentorin/src/routes/_site/mentoring_/_components/mentor-card.tsx similarity index 98% rename from apps/dimentorin/src/app/(public)/mentoring/_components/mentor-card.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/_components/mentor-card.tsx index b3905e0..5e9e322 100644 --- a/apps/dimentorin/src/app/(public)/mentoring/_components/mentor-card.tsx +++ b/apps/dimentorin/src/routes/_site/mentoring_/_components/mentor-card.tsx @@ -1,6 +1,6 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; import { FC } from 'react'; -import { Link } from 'react-router-dom'; +import { Link } from '@tanstack/react-router'; import type { MentorDetailResponseDto } from '@imphnen-frontend-service/service'; interface MentorCardProps { diff --git a/apps/dimentorin/src/app/(public)/mentoring/_components/topics.tsx b/apps/dimentorin/src/routes/_site/mentoring_/_components/topics.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/mentoring/_components/topics.tsx rename to apps/dimentorin/src/routes/_site/mentoring_/_components/topics.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/page.tsx b/apps/dimentorin/src/routes/_site/profile.tsx similarity index 73% rename from apps/dimentorin/src/app/(public)/profile/page.tsx rename to apps/dimentorin/src/routes/_site/profile.tsx index 5ee9f77..7515556 100644 --- a/apps/dimentorin/src/app/(public)/profile/page.tsx +++ b/apps/dimentorin/src/routes/_site/profile.tsx @@ -1,37 +1,40 @@ -'use client'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement, useState } from 'react' +import { ProfileForm, ProfileSidebar, ProfileHeader } from './profile_/_components' +import { ArrowLeftOutlined } from '@ant-design/icons' +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { NotificationModal, NotificationType } from './profile_/_components/modals/notification-modal' +import { ProfileProvider, useProfile } from './profile_/_components/contexts/profile-context' +import { EditProfileModal } from './profile_/_components/modals/edit-profile-modal' -import { FC, ReactElement, useState } from 'react'; -import { ProfileForm, ProfileSidebar, ProfileHeader } from './_components'; -import { ArrowLeftOutlined } from '@ant-design/icons'; -import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { NotificationModal, NotificationType } from './_components/modals/notification-modal'; -import { ProfileProvider, useProfile } from './_components/contexts/profile-context'; -import { EditProfileModal } from './_components/modals/edit-profile-modal'; +export const Route = createFileRoute('/_site/profile')({ + component: ProfilePage, +}) -export const Components: FC = (): ReactElement => { +function ProfilePage(): ReactElement { return ( - ); -}; + ) +} const ProfileContent: FC = (): ReactElement => { - const { isLoading, error } = useProfile(); + const { isLoading, error } = useProfile() const [notification, setNotification] = useState<{ - isOpen: boolean; - type: 'success' | 'error'; - title: string; - message?: string; + isOpen: boolean + type: 'success' | 'error' + title: string + message?: string }>({ isOpen: false, type: 'success', title: '', message: '' - }); + }) - const [isEditProfileModalOpen, setIsEditProfileModalOpen] = useState(false); + const [isEditProfileModalOpen, setIsEditProfileModalOpen] = useState(false) const showNotification = (type: NotificationType['type'], title: string, message?: string) => { setNotification({ @@ -39,20 +42,20 @@ const ProfileContent: FC = (): ReactElement => { type, title, message - }); - }; + }) + } const hideNotification = () => { - setNotification(prev => ({ ...prev, isOpen: false })); - }; + setNotification(prev => ({ ...prev, isOpen: false })) + } const openEditProfileModal = () => { - setIsEditProfileModalOpen(true); - }; + setIsEditProfileModalOpen(true) + } const closeEditProfileModal = () => { - setIsEditProfileModalOpen(false); - }; + setIsEditProfileModalOpen(false) + } if (isLoading) { return ( @@ -62,7 +65,7 @@ const ProfileContent: FC = (): ReactElement => {

Loading profile...

- ); + ) } if (error) { @@ -73,7 +76,7 @@ const ProfileContent: FC = (): ReactElement => {

Please try again later.

- ); + ) } return ( @@ -126,7 +129,5 @@ const ProfileContent: FC = (): ReactElement => { showNotification={showNotification} /> - ); -}; - -export default Components; + ) +} diff --git a/apps/dimentorin/src/app/(public)/profile/[id]/page.tsx b/apps/dimentorin/src/routes/_site/profile_/$id.tsx similarity index 76% rename from apps/dimentorin/src/app/(public)/profile/[id]/page.tsx rename to apps/dimentorin/src/routes/_site/profile_/$id.tsx index e067535..f7a2c19 100644 --- a/apps/dimentorin/src/app/(public)/profile/[id]/page.tsx +++ b/apps/dimentorin/src/routes/_site/profile_/$id.tsx @@ -1,17 +1,18 @@ -'use client'; +import { createFileRoute } from '@tanstack/react-router' +import { FC, ReactElement, useState } from 'react' +import { ProfileForm, ProfileSidebar, ProfileHeader } from './_components' +import { ArrowLeftOutlined } from '@ant-design/icons' +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { NotificationModal, NotificationType } from './_components/modals/notification-modal' +import { ProfileProvider, useProfile } from './_components/contexts/profile-context' +import { EditProfileModal } from './_components/modals/edit-profile-modal' -import { FC, ReactElement, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import { ProfileForm, ProfileSidebar, ProfileHeader } from '../_components'; -import { ArrowLeftOutlined } from '@ant-design/icons'; -import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { NotificationModal, NotificationType } from '../_components/modals/notification-modal'; -import { ProfileProvider, useProfile } from '../_components/contexts/profile-context'; -import { EditProfileModal } from '../_components/modals/edit-profile-modal'; +export const Route = createFileRoute('/_site/profile/$id')({ + component: ProfileByIdPage, +}) -const ProfileByIdPage: FC = (): ReactElement => { - const params = useParams(); - const id = (params && params.id) ? params.id as string : undefined; +function ProfileByIdPage(): ReactElement { + const { id } = Route.useParams() if (!id) { return ( @@ -20,39 +21,39 @@ const ProfileByIdPage: FC = (): ReactElement => {

Profile ID not found.

- ); + ) } return ( - ); -}; + ) +} const ProfileByIdContent: FC = (): ReactElement => { - const { profileData, isLoading, error, profileType } = useProfile(); + const { profileData, isLoading, error, profileType } = useProfile() const [notification, setNotification] = useState<{ - isOpen: boolean; - type: 'success' | 'error'; - title: string; - message?: string; + isOpen: boolean + type: 'success' | 'error' + title: string + message?: string }>({ isOpen: false, type: 'success', title: '', message: '' - }); + }) - const [isEditProfileModalOpen, setIsEditProfileModalOpen] = useState(false); + const [isEditProfileModalOpen, setIsEditProfileModalOpen] = useState(false) const getProfileTitle = () => { if (profileData?.fullname) { - return `${profileData.fullname}'s Profile`; + return `${profileData.fullname}'s Profile` } - return profileType === 'user' ? 'User Profile' : 'Mentor Profile'; - }; + return profileType === 'user' ? 'User Profile' : 'Mentor Profile' + } const showNotification = (type: NotificationType['type'], title: string, message?: string) => { setNotification({ @@ -60,22 +61,22 @@ const ProfileByIdContent: FC = (): ReactElement => { type, title, message - }); - }; + }) + } const hideNotification = () => { - setNotification(prev => ({ ...prev, isOpen: false })); - }; + setNotification(prev => ({ ...prev, isOpen: false })) + } const openEditProfileModal = () => { - setIsEditProfileModalOpen(true); - }; + setIsEditProfileModalOpen(true) + } const closeEditProfileModal = () => { - setIsEditProfileModalOpen(false); - }; + setIsEditProfileModalOpen(false) + } - const isViewOnly = true; + const isViewOnly = true if (isLoading) { return ( @@ -85,7 +86,7 @@ const ProfileByIdContent: FC = (): ReactElement => {

Loading profile...

- ); + ) } if (error) { @@ -96,7 +97,7 @@ const ProfileByIdContent: FC = (): ReactElement => {

Profile not found or you don't have permission to view it.

- ); + ) } return ( @@ -158,7 +159,5 @@ const ProfileByIdContent: FC = (): ReactElement => { /> )} - ); -}; - -export default ProfileByIdPage; + ) +} diff --git a/apps/dimentorin/src/app/(public)/profile/_components/buttons/edit-section-button.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/buttons/edit-section-button.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/buttons/edit-section-button.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/buttons/edit-section-button.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/buttons/index.ts b/apps/dimentorin/src/routes/_site/profile_/_components/buttons/index.ts similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/buttons/index.ts rename to apps/dimentorin/src/routes/_site/profile_/_components/buttons/index.ts diff --git a/apps/dimentorin/src/app/(public)/profile/_components/buttons/modal-button.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/buttons/modal-button.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/buttons/modal-button.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/buttons/modal-button.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/contexts/index.ts b/apps/dimentorin/src/routes/_site/profile_/_components/contexts/index.ts similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/contexts/index.ts rename to apps/dimentorin/src/routes/_site/profile_/_components/contexts/index.ts diff --git a/apps/dimentorin/src/app/(public)/profile/_components/contexts/profile-context.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/contexts/profile-context.tsx similarity index 98% rename from apps/dimentorin/src/app/(public)/profile/_components/contexts/profile-context.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/contexts/profile-context.tsx index 70ef3cc..f9c7cb8 100644 --- a/apps/dimentorin/src/app/(public)/profile/_components/contexts/profile-context.tsx +++ b/apps/dimentorin/src/routes/_site/profile_/_components/contexts/profile-context.tsx @@ -1,7 +1,7 @@ 'use client'; import React, { createContext, useContext, useMemo, useCallback } from 'react'; -import { useParams } from 'react-router-dom'; +import { useParams } from '@tanstack/react-router'; import { useQueryClient } from '@tanstack/react-query'; import { useAuthStore, @@ -65,7 +65,7 @@ export const ProfileProvider: React.FC = ({ profileId, profileType: forcedProfileType }) => { - const params = useParams(); + const params = useParams({ strict: false }); const { session } = useAuthStore(); const queryClient = useQueryClient(); diff --git a/apps/dimentorin/src/app/(public)/profile/_components/guards/mentor-guard.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/guards/mentor-guard.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/guards/mentor-guard.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/guards/mentor-guard.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/index.ts b/apps/dimentorin/src/routes/_site/profile_/_components/index.ts similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/index.ts rename to apps/dimentorin/src/routes/_site/profile_/_components/index.ts diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/cv-modal.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/modals/cv-modal.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/cv-modal.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/cv-modal.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/description-modal.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/modals/description-modal.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/description-modal.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/description-modal.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/edit-profile-modal.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/modals/edit-profile-modal.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/edit-profile-modal.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/edit-profile-modal.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/education-modal.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/modals/education-modal.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/education-modal.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/education-modal.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/experience-modal.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/modals/experience-modal.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/experience-modal.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/experience-modal.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/index.ts b/apps/dimentorin/src/routes/_site/profile_/_components/modals/index.ts similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/index.ts rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/index.ts diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/languages-modal.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/modals/languages-modal.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/languages-modal.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/languages-modal.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/notification-modal.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/modals/notification-modal.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/notification-modal.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/notification-modal.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/personal-info-modal.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/modals/personal-info-modal.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/personal-info-modal.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/personal-info-modal.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/profile-basic-info-modal.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/modals/profile-basic-info-modal.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/profile-basic-info-modal.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/profile-basic-info-modal.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/skills-modal.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/modals/skills-modal.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/skills-modal.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/skills-modal.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/modals/social-media-modal.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/modals/social-media-modal.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/modals/social-media-modal.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/modals/social-media-modal.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/profile/index.ts b/apps/dimentorin/src/routes/_site/profile_/_components/profile/index.ts similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/profile/index.ts rename to apps/dimentorin/src/routes/_site/profile_/_components/profile/index.ts diff --git a/apps/dimentorin/src/app/(public)/profile/_components/profile/profile-form-handlers.ts b/apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-form-handlers.ts similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/profile/profile-form-handlers.ts rename to apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-form-handlers.ts diff --git a/apps/dimentorin/src/app/(public)/profile/_components/profile/profile-form-hooks.ts b/apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-form-hooks.ts similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/profile/profile-form-hooks.ts rename to apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-form-hooks.ts diff --git a/apps/dimentorin/src/app/(public)/profile/_components/profile/profile-form-types.ts b/apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-form-types.ts similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/profile/profile-form-types.ts rename to apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-form-types.ts diff --git a/apps/dimentorin/src/app/(public)/profile/_components/profile/profile-form.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-form.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/profile/profile-form.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-form.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/profile/profile-header.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-header.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/profile/profile-header.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-header.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/profile/profile-info-new.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-info-new.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/profile/profile-info-new.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-info-new.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/profile/profile-info.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-info.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/profile/profile-info.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-info.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/profile/profile-sidebar.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-sidebar.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/profile/profile-sidebar.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-sidebar.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/profile/profile-tabs.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-tabs.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/profile/profile-tabs.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/profile/profile-tabs.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/sections/contact-info-section.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/sections/contact-info-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/sections/contact-info-section.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/sections/contact-info-section.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/sections/cv-resume-section.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/sections/cv-resume-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/sections/cv-resume-section.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/sections/cv-resume-section.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/sections/description-section.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/sections/description-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/sections/description-section.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/sections/description-section.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/sections/education-section.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/sections/education-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/sections/education-section.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/sections/education-section.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/sections/experiences-section.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/sections/experiences-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/sections/experiences-section.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/sections/experiences-section.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/sections/index.ts b/apps/dimentorin/src/routes/_site/profile_/_components/sections/index.ts similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/sections/index.ts rename to apps/dimentorin/src/routes/_site/profile_/_components/sections/index.ts diff --git a/apps/dimentorin/src/app/(public)/profile/_components/sections/languages-section.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/sections/languages-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/sections/languages-section.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/sections/languages-section.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/sections/personal-info-section.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/sections/personal-info-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/sections/personal-info-section.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/sections/personal-info-section.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/sections/skills-section.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/sections/skills-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/sections/skills-section.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/sections/skills-section.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/sections/social-media-section.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/sections/social-media-section.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/sections/social-media-section.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/sections/social-media-section.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/shared/file-uploader.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/shared/file-uploader.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/shared/file-uploader.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/shared/file-uploader.tsx diff --git a/apps/dimentorin/src/app/(public)/profile/_components/shared/index.ts b/apps/dimentorin/src/routes/_site/profile_/_components/shared/index.ts similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/shared/index.ts rename to apps/dimentorin/src/routes/_site/profile_/_components/shared/index.ts diff --git a/apps/dimentorin/src/app/(public)/profile/_components/shared/section-wrapper.tsx b/apps/dimentorin/src/routes/_site/profile_/_components/shared/section-wrapper.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/profile/_components/shared/section-wrapper.tsx rename to apps/dimentorin/src/routes/_site/profile_/_components/shared/section-wrapper.tsx diff --git a/apps/dimentorin/src/routes/_site/resources.tsx b/apps/dimentorin/src/routes/_site/resources.tsx new file mode 100644 index 0000000..70e2f2e --- /dev/null +++ b/apps/dimentorin/src/routes/_site/resources.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' +import { ReactElement } from 'react' +import { Contribute } from './resources_/_components/contribute' +import { WorkWithUs } from './resources_/_components/work-with-us' +import { Collaborate } from './resources_/_components/collaborate' + +export const Route = createFileRoute('/_site/resources')({ + component: ResourcesPage, +}) + +function ResourcesPage(): ReactElement { + return ( +
+ + + +
+ ) +} diff --git a/apps/dimentorin/src/app/(public)/resources/_components/collaborate.tsx b/apps/dimentorin/src/routes/_site/resources_/_components/collaborate.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/resources/_components/collaborate.tsx rename to apps/dimentorin/src/routes/_site/resources_/_components/collaborate.tsx diff --git a/apps/dimentorin/src/app/(public)/resources/_components/contribute.tsx b/apps/dimentorin/src/routes/_site/resources_/_components/contribute.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/resources/_components/contribute.tsx rename to apps/dimentorin/src/routes/_site/resources_/_components/contribute.tsx diff --git a/apps/dimentorin/src/app/(public)/resources/_components/work-with-us.tsx b/apps/dimentorin/src/routes/_site/resources_/_components/work-with-us.tsx similarity index 100% rename from apps/dimentorin/src/app/(public)/resources/_components/work-with-us.tsx rename to apps/dimentorin/src/routes/_site/resources_/_components/work-with-us.tsx diff --git a/apps/dimentorin/src/routes/index.tsx b/apps/dimentorin/src/routes/index.tsx new file mode 100644 index 0000000..10505de --- /dev/null +++ b/apps/dimentorin/src/routes/index.tsx @@ -0,0 +1,35 @@ +import { createFileRoute, redirect } from '@tanstack/react-router' +import { SessionToken } from '@imphnen-frontend-service/service' +import { HeroSection } from './_site/_components/home/hero-section' +import { WhatWeOfferSection } from './_site/_components/home/what-we-offer-section' +import { TestimonialSection } from './_site/_components/home/testimonial-section' +import { FAQSection } from './_site/_components/home/faq-section' +import { CTASection } from './_site/_components/home/cta-section' +import { Header } from './_site/_components/header' +import { Footer } from './_site/_components/footer' + +export const Route = createFileRoute('/')({ + beforeLoad: () => { + const session = SessionToken.get() + if (session?.token?.access_token) { + throw redirect({ to: '/dashboard' }) + } + }, + component: HomePage, +}) + +function HomePage() { + return ( +
+
+
+ + + + + +
+
+
+ ) +} diff --git a/apps/dimentorin/vite.config.ts b/apps/dimentorin/vite.config.ts index 2fd3870..f3b6c1c 100644 --- a/apps/dimentorin/vite.config.ts +++ b/apps/dimentorin/vite.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; +import { TanStackRouterVite } from '@tanstack/router-plugin/vite'; export default defineConfig(() => ({ root: __dirname, @@ -15,7 +16,14 @@ export default defineConfig(() => ({ port: 3001, host: 'localhost', }, - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + plugins: [ + TanStackRouterVite({ + routeFileIgnorePattern: '_components|_hooks|_hook|_data', + }), + react(), + nxViteTsPaths(), + nxCopyAssetsPlugin(['*.md']), + ], build: { outDir: '../../dist/apps/dimentorin', emptyOutDir: true, diff --git a/apps/gacha/src/app/layout.tsx b/apps/gacha/src/app/layout.tsx deleted file mode 100644 index 2b27454..0000000 --- a/apps/gacha/src/app/layout.tsx +++ /dev/null @@ -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 ( - -
- - -
-
- ); -}; -export default AppLayout; diff --git a/apps/gacha/src/main.tsx b/apps/gacha/src/main.tsx index 185c11d..cf947ab 100644 --- a/apps/gacha/src/main.tsx +++ b/apps/gacha/src/main.tsx @@ -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( - + -); +) diff --git a/apps/gacha/src/middleware.ts b/apps/gacha/src/middleware.ts deleted file mode 100644 index e0e4025..0000000 --- a/apps/gacha/src/middleware.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { LoaderFunctionArgs } from 'react-router'; - -export const middleware = async ({ request }: LoaderFunctionArgs) => { - return null; -}; diff --git a/apps/gacha/src/routeTree.gen.ts b/apps/gacha/src/routeTree.gen.ts new file mode 100644 index 0000000..786525b --- /dev/null +++ b/apps/gacha/src/routeTree.gen.ts @@ -0,0 +1,2 @@ +// This file is auto-generated by TanStack Router +export const routeTree = {} as any diff --git a/apps/gacha/src/routes/__root.tsx b/apps/gacha/src/routes/__root.tsx new file mode 100644 index 0000000..c7aa04a --- /dev/null +++ b/apps/gacha/src/routes/__root.tsx @@ -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 ( + +
+ + +
+
+ ) +} diff --git a/apps/gacha/src/app/_components/form/modal-form-forgot-password.tsx b/apps/gacha/src/routes/_components/form/modal-form-forgot-password.tsx similarity index 100% rename from apps/gacha/src/app/_components/form/modal-form-forgot-password.tsx rename to apps/gacha/src/routes/_components/form/modal-form-forgot-password.tsx diff --git a/apps/gacha/src/app/_components/form/modal-form-login.tsx b/apps/gacha/src/routes/_components/form/modal-form-login.tsx similarity index 100% rename from apps/gacha/src/app/_components/form/modal-form-login.tsx rename to apps/gacha/src/routes/_components/form/modal-form-login.tsx diff --git a/apps/gacha/src/app/_components/form/modal-form-register.tsx b/apps/gacha/src/routes/_components/form/modal-form-register.tsx similarity index 100% rename from apps/gacha/src/app/_components/form/modal-form-register.tsx rename to apps/gacha/src/routes/_components/form/modal-form-register.tsx diff --git a/apps/gacha/src/app/_components/form/modal-form-verify-email.tsx b/apps/gacha/src/routes/_components/form/modal-form-verify-email.tsx similarity index 100% rename from apps/gacha/src/app/_components/form/modal-form-verify-email.tsx rename to apps/gacha/src/routes/_components/form/modal-form-verify-email.tsx diff --git a/apps/gacha/src/app/_components/item/gacha-item.tsx b/apps/gacha/src/routes/_components/item/gacha-item.tsx similarity index 100% rename from apps/gacha/src/app/_components/item/gacha-item.tsx rename to apps/gacha/src/routes/_components/item/gacha-item.tsx diff --git a/apps/gacha/src/app/_hooks/use-login.ts b/apps/gacha/src/routes/_hooks/use-login.ts similarity index 94% rename from apps/gacha/src/app/_hooks/use-login.ts rename to apps/gacha/src/routes/_hooks/use-login.ts index c0d64dc..05c5f37 100644 --- a/apps/gacha/src/app/_hooks/use-login.ts +++ b/apps/gacha/src/routes/_hooks/use-login.ts @@ -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; diff --git a/apps/gacha/src/app/_hooks/use-register.ts b/apps/gacha/src/routes/_hooks/use-register.ts similarity index 100% rename from apps/gacha/src/app/_hooks/use-register.ts rename to apps/gacha/src/routes/_hooks/use-register.ts diff --git a/apps/gacha/src/app/_hooks/use-verify-email.ts b/apps/gacha/src/routes/_hooks/use-verify-email.ts similarity index 92% rename from apps/gacha/src/app/_hooks/use-verify-email.ts rename to apps/gacha/src/routes/_hooks/use-verify-email.ts index 7591be3..1d40769 100644 --- a/apps/gacha/src/app/_hooks/use-verify-email.ts +++ b/apps/gacha/src/routes/_hooks/use-verify-email.ts @@ -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'), diff --git a/apps/gacha/src/app/page.tsx b/apps/gacha/src/routes/index.tsx similarity index 94% rename from apps/gacha/src/app/page.tsx rename to apps/gacha/src/routes/index.tsx index 140bf16..c90d90d 100644 --- a/apps/gacha/src/app/page.tsx +++ b/apps/gacha/src/routes/index.tsx @@ -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 => { /> ); -}; - -export default Components; +} diff --git a/apps/gacha/vite.config.ts b/apps/gacha/vite.config.ts index a28b76a..8bebc3c 100644 --- a/apps/gacha/vite.config.ts +++ b/apps/gacha/vite.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; +import { TanStackRouterVite } from '@tanstack/router-plugin/vite'; export default defineConfig(() => ({ root: __dirname, @@ -15,7 +16,14 @@ export default defineConfig(() => ({ port: 3001, host: 'localhost', }, - plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + plugins: [ + TanStackRouterVite({ + routeFileIgnorePattern: '_components|_hooks|_hook|_data', + }), + react(), + nxViteTsPaths(), + nxCopyAssetsPlugin(['*.md']), + ], build: { outDir: '../../dist/apps/gacha', emptyOutDir: true, diff --git a/apps/hackathon/src/app/404.tsx b/apps/hackathon/src/app/404.tsx deleted file mode 100644 index 3a2e703..0000000 --- a/apps/hackathon/src/app/404.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Link } from 'react-router-dom'; - -export default function NotFoundPage() { - return ( -
-
-

404

-

- Page Not Found -

-

- The page you are looking for doesn't exist or has been moved. -

- - Go Back Home - -
-
- ); -} diff --git a/apps/hackathon/src/app/auth/callback/page.tsx b/apps/hackathon/src/app/auth/callback/page.tsx deleted file mode 100644 index 86451e4..0000000 --- a/apps/hackathon/src/app/auth/callback/page.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { FC, ReactElement, useEffect, useState, useRef } from 'react'; -import { useNavigate } from 'react-router'; -import { useGitHubCallback } from '@imphnen-frontend-service/service'; -import { toast } from 'sonner'; - -const CallbackPage: FC = (): ReactElement => { - const navigate = useNavigate(); - const { mutateAsync: exchangeGitHubCode } = useGitHubCallback(); - const [isProcessing, setIsProcessing] = useState(true); - const [error, setError] = useState(null); - const hasRunRef = useRef(false); - - useEffect(() => { - const handleCallback = async () => { - if (hasRunRef.current) { - return; - } - hasRunRef.current = true; - - try { - const hashParams = new URLSearchParams(globalThis.location.hash.substring(1)); - const urlParams = new URLSearchParams(globalThis.location.search); - - const type = hashParams.get('type') || urlParams.get('type'); - const accessToken = hashParams.get('access_token') || urlParams.get('access_token'); - - console.log('[Callback] Params:', { type, accessToken: !!accessToken, hash: globalThis.location.hash, search: globalThis.location.search }); - - if (accessToken) { - setIsProcessing(false); - - if (type === 'recovery' || type === 'magiclink') { - toast.success('Email verified! Please set your new password.'); - navigate('/auth/reset-password?access_token=' + accessToken); - return; - } - - if (type === 'signup' || type === 'email_confirmation') { - toast.success('Email verified successfully! Please log in to continue.'); - navigate('/auth/login'); - return; - } - - toast.success('Email verified! Please set your new password.'); - navigate('/auth/reset-password?access_token=' + accessToken); - return; - } - - const code = urlParams.get('code'); - - if (!code) { - throw new Error('No authorization code received'); - } - - const result = await exchangeGitHubCode({ code }); - - toast.success('Login successful!'); - setIsProcessing(false); - - if (result.user.location) { - globalThis.location.replace('/dashboard'); - } else { - globalThis.location.replace('/onboarding/user'); - } - } catch (err) { - console.error('[Callback] Error:', err); - setError((err as Error).message); - setIsProcessing(false); - toast.error('An error occurred during login'); - - setTimeout(() => { - navigate('/auth/login'); - }, 3000); - } - }; - - handleCallback(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - if (error) { - const isPrivateEmailError = - error.toLowerCase().includes('failed to create user') || - error.toLowerCase().includes('email') || - error.toLowerCase().includes('user record'); - - return ( -
-
-
-
⚠️
-

- GitHub Login Failed -

-

{error}

-
- - {isPrivateEmailError && ( -
-

- Is your GitHub email set to private? -

-

- GitHub login requires a public email address. Please follow these steps: -

-
    -
  1. - Go to{' '} - - GitHub Email Settings - -
  2. -
  3. Uncheck "Keep my email addresses private"
  4. -
  5. - Or go to{' '} - - Profile Settings - {' '} - and set a public email -
  6. -
  7. Try signing in with GitHub again
  8. -
-

- Alternatively, you can sign up using email and password instead. -

-
- )} - -

- Redirecting to login page in 3 seconds... -

-
-
- ); - } - - return ( -
-
-
-

- Completing login... -

-

Please wait

-
-
- ); -}; - -export default CallbackPage; diff --git a/apps/hackathon/src/app/auth/forgot-password/page.tsx b/apps/hackathon/src/app/auth/forgot-password/page.tsx deleted file mode 100644 index 5a6c4d3..0000000 --- a/apps/hackathon/src/app/auth/forgot-password/page.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { useState } from 'react'; -import { useForgotPassword } from '@imphnen-frontend-service/service'; -import { Link, useNavigate } from 'react-router'; -import { toast } from 'sonner'; -import { Icon } from '@iconify/react'; -import ThemeToggle from '../../../components/theme-toggle'; - -export default function ForgotPasswordPage() { - const [email, setEmail] = useState(''); - const [emailSent, setEmailSent] = useState(false); - const navigate = useNavigate(); - const forgotPasswordMutation = useForgotPassword(); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!email) { - toast.error('Please enter your email'); - return; - } - - try { - await forgotPasswordMutation.mutateAsync({ email }); - - setEmailSent(true); - toast.success('Password reset email sent! Check your inbox.'); - } catch (err) { - toast.error((err as Error).message || 'Failed to send reset email'); - } - }; - - if (emailSent) { - return ( -
-
-
-
- -
-

- Check Your Email -

-

- We've sent a password reset link to {email} -

-
- -
-

- Click the link in the email to reset your password. The link will - expire in 1 hour. -

- - - - - - -
-
-
- ); - } - - return ( -
-
-
- - -
-
-

- Forgot Password? -

-

- No worries, we'll send you reset instructions -

-
- - -
- - setEmail(e.target.value)} - placeholder="your@email.com" - disabled={forgotPasswordMutation.isPending} - className="bg-white dark:bg-gray-800 text-gray-900 dark:text-white w-full px-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 dark:disabled:bg-gray-700 disabled:cursor-not-allowed" - required - /> -
- - - -
-
- ); -} diff --git a/apps/hackathon/src/app/auth/reset-password/page.tsx b/apps/hackathon/src/app/auth/reset-password/page.tsx deleted file mode 100644 index ac509d2..0000000 --- a/apps/hackathon/src/app/auth/reset-password/page.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { useState, useEffect } from 'react'; -import { useResetPassword, useAuthStore } from '@imphnen-frontend-service/service'; -import { useNavigate } from 'react-router'; -import { toast } from 'sonner'; -import { Icon } from '@iconify/react'; - -export default function ResetPasswordPage() { - const navigate = useNavigate(); - const { clearSession } = useAuthStore(); - const resetPasswordMutation = useResetPassword(); - const [password, setPassword] = useState(''); - const [confirmPassword, setConfirmPassword] = useState(''); - const [accessToken, setAccessToken] = useState(null); - const [showPassword, setShowPassword] = useState(false); - const [showConfirmPassword, setShowConfirmPassword] = useState(false); - - useEffect(() => { - const hashParams = new URLSearchParams(globalThis.location.hash.substring(1)); - const queryParams = new URLSearchParams(globalThis.location.search); - const token = hashParams.get('access_token') || queryParams.get('access_token'); - - if (token) { - setAccessToken(token); - } else { - toast.error('Invalid or expired reset link'); - setTimeout(() => navigate('/auth/forgot-password'), 2000); - } - }, [navigate]); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (password !== confirmPassword) { - toast.error('Passwords do not match'); - return; - } - - if (password.length < 6) { - toast.error('Password must be at least 6 characters'); - return; - } - - if (!accessToken) { - toast.error('Invalid reset token'); - return; - } - - try { - await resetPasswordMutation.mutateAsync({ - access_token: accessToken, - new_password: password, - }); - - toast.success('Password updated successfully!'); - - clearSession(); - navigate('/auth/login'); - } catch (err) { - toast.error((err as Error).message || 'Failed to reset password'); - } - }; - - if (!accessToken) { - return ( -
-
-
-

- Verifying reset link... -

-
-
- ); - } - - return ( -
-
-
-

- Set New Password -

-

- Enter your new password below -

-
- -
-
- -
- setPassword(e.target.value)} - placeholder="••••••••" - disabled={resetPasswordMutation.isPending} - className="w-full px-4 py-2.5 pr-12 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed bg-white dark:bg-gray-800 text-gray-900 dark:text-white" - required - minLength={6} - /> - -
-
- -
- -
- setConfirmPassword(e.target.value)} - placeholder="••••••••" - disabled={resetPasswordMutation.isPending} - className="w-full px-4 py-2.5 pr-12 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed bg-white dark:bg-gray-800 text-gray-900 dark:text-white" - required - minLength={6} - /> - -
-
- - -
-
-
- ); -} diff --git a/apps/hackathon/src/app/auth/signup/page.tsx b/apps/hackathon/src/app/auth/signup/page.tsx deleted file mode 100644 index 97eaadd..0000000 --- a/apps/hackathon/src/app/auth/signup/page.tsx +++ /dev/null @@ -1,402 +0,0 @@ -import { useState } from 'react'; -import { useGitHubAuth, useSignup } from '@imphnen-frontend-service/service'; -import { GithubOutlined } from '@ant-design/icons'; -import { useNavigate, Link } from 'react-router'; -import { toast } from 'sonner'; -import { Icon } from '@iconify/react'; -import { ThemeToggle } from '../../../components/theme-toggle'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { z } from 'zod'; - -const signupSchema = z - .object({ - fullname: z - .string() - .min(1, 'Full name is required') - .min(2, 'Full name must be at least 2 characters'), - email: z - .string() - .min(1, 'Email is required') - .email('Please enter a valid email address'), - password: z - .string() - .min(1, 'Password is required') - .min(6, 'Password must be at least 6 characters'), - confirmPassword: z.string().min(1, 'Please confirm your password'), - }) - .refine((data) => data.password === data.confirmPassword, { - message: 'Passwords do not match', - path: ['confirmPassword'], - }); - -type SignupFormData = z.infer; - -const REGISTRATION_DEADLINE = new Date('2025-11-30T16:29:00Z'); - -export default function SignupPage() { - const navigate = useNavigate(); - - const isRegistrationClosed = new Date() >= REGISTRATION_DEADLINE; - const { signInWithGitHub } = useGitHubAuth(); - const signupMutation = useSignup(); - const [isGithubLoading, setIsGithubLoading] = useState(false); - const [error, setError] = useState(null); - const [registrationSuccess, setRegistrationSuccess] = useState(false); - const [registeredEmail, setRegisteredEmail] = useState(''); - const [showPassword, setShowPassword] = useState(false); - const [showConfirmPassword, setShowConfirmPassword] = useState(false); - - const { - register, - handleSubmit, - formState: { errors, isValid }, - } = useForm({ - resolver: zodResolver(signupSchema), - mode: 'onChange', - }); - - const onSubmit = async (data: SignupFormData) => { - setError(null); - - try { - const result = await signupMutation.mutateAsync({ - email: data.email, - password: data.password, - fullname: data.fullname, - }); - toast.success(result.message); - setRegisteredEmail(data.email); - setRegistrationSuccess(true); - } catch (err) { - console.error('[Signup] Email signup failed:', err); - setError((err as Error).message || 'Signup failed'); - } - }; - - if (isRegistrationClosed) { - return ( -
-
-
-
- -
-

- Registration Closed -

-

- The registration period for this hackathon has ended. -

-
- -
-

- Thank you for your interest! Registration closed on November 30, 2025 at 23:29 WIB. -

- - - - - - -
-
-
- ); - } - - if (registrationSuccess) { - return ( -
-
-
-
- -
-

- Check Your Email -

-

- We've sent an activation link to{' '} - {registeredEmail} -

-
- -
-

- Click the link in the email to activate your account. The link - will expire in 24 hours. -

- -
-

- Don't forget to check your spam folder if you don't see the - email. -

-
- - - - - - -
-
-
- ); - } - - const handleGithubLogin = async () => { - try { - setIsGithubLoading(true); - - const result = await signInWithGitHub(); - - if (result?.url) { - globalThis.location.href = result.url; - } else { - setIsGithubLoading(false); - setError('Failed to get GitHub OAuth URL'); - } - } catch (err) { - console.error('[Signup] GitHub login failed:', err); - setError((err as Error).message || 'GitHub login failed'); - setIsGithubLoading(false); - } - }; - - const inputBaseClass = - 'w-full px-4 py-2.5 border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 disabled:cursor-not-allowed'; - const inputErrorClass = 'border-red-500 dark:border-red-500'; - const inputNormalClass = 'border-gray-300 dark:border-gray-600'; - - return ( -
-
-
- - -
- -
-

- Create Account -

-

- Join the hackathon community -

-
- - {error && ( -
-

{error}

-
- )} - -
-
- - - {errors.fullname && ( -

- {errors.fullname.message} -

- )} -
- -
- - - {errors.email && ( -

- {errors.email.message} -

- )} -
- -
- -
- - -
- {errors.password && ( -

- {errors.password.message} -

- )} -
- -
- -
- - -
- {errors.confirmPassword && ( -

- {errors.confirmPassword.message} -

- )} -
- - -
- -
-
- - OR - -
-
- - - -

- Make sure your GitHub email is{' '} - - set to public - {' '} - for GitHub sign up to work. -

- -
-

- Already have an account?{' '} - - Sign in - -

-
- -
-

- By signing up, you agree to our Terms of Service and Privacy Policy -

-
-
-
- ); -} diff --git a/apps/hackathon/src/app/dashboard/layout.tsx b/apps/hackathon/src/app/dashboard/layout.tsx deleted file mode 100644 index c83f6a1..0000000 --- a/apps/hackathon/src/app/dashboard/layout.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { FC, ReactElement, useState } from 'react'; -import { Outlet } from 'react-router'; -import { Sidebar } from '../../components/sidebar'; - -const DashboardLayout: FC = (): ReactElement => { - const [sidebarOpen, setSidebarOpen] = useState(false); - - return ( -
- setSidebarOpen(false)} /> - -
- -
- -

- Hackathon -

-
- -
- -
-
-
- ); -}; - -export default DashboardLayout; diff --git a/apps/hackathon/src/app/error.tsx b/apps/hackathon/src/app/error.tsx deleted file mode 100644 index 3859b00..0000000 --- a/apps/hackathon/src/app/error.tsx +++ /dev/null @@ -1,29 +0,0 @@ -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 ( -
-
-

Oops!

-

- Sorry, an unexpected error has occurred. -

-

{errorMessage}

-
-
- ); -} diff --git a/apps/hackathon/src/app/layout.tsx b/apps/hackathon/src/app/layout.tsx deleted file mode 100644 index df0c86e..0000000 --- a/apps/hackathon/src/app/layout.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { - Outlet, - ScrollRestoration, - useLocation, - useNavigate, -} from 'react-router-dom'; -import { useEffect, useState } from 'react'; -import { useAuthStore, useUserMe } from '@imphnen-frontend-service/service'; - -const ONBOARDING_ROUTES = new Set(['/onboarding/user']); - -export default function RootLayout() { - const location = useLocation(); - const navigate = useNavigate(); - const { session } = useAuthStore(); - const { data: userData, isLoading: isUserLoading } = useUserMe(); - const [isChecking, setIsChecking] = useState(true); - - useEffect(() => { - const checkAuth = async () => { - const pathname = location.pathname; - - if (pathname.startsWith('/hackathons')) { - setIsChecking(false); - return; - } - - if (pathname === '/auth/callback') { - setIsChecking(false); - return; - } - - if (pathname.startsWith('/auth')) { - if (session && pathname !== '/auth/reset-password') { - navigate('/dashboard', { replace: true }); - setIsChecking(false); - return; - } - setIsChecking(false); - return; - } - - if (pathname === '/') { - setIsChecking(false); - return; - } - - if (pathname.startsWith('/certificate/')) { - setIsChecking(false); - return; - } - - if (!session) { - navigate('/auth/login', { replace: true }); - setIsChecking(false); - return; - } - - if (isUserLoading) { - return; - } - - if (!ONBOARDING_ROUTES.has(pathname)) { - const hasLocation = !!userData?.data?.location || !!session?.user?.location; - - if (!hasLocation) { - navigate('/onboarding/user', { replace: true }); - setIsChecking(false); - return; - } - } - - setIsChecking(false); - }; - - checkAuth(); - }, [location.pathname, navigate, session, userData, isUserLoading]); - - if (isChecking) { - return ( -
-
-
-

Loading...

-
-
- ); - } - - return ( - <> - - - - ); -} diff --git a/apps/hackathon/src/app/profile/page.tsx b/apps/hackathon/src/app/profile/page.tsx deleted file mode 100644 index b6e4a26..0000000 --- a/apps/hackathon/src/app/profile/page.tsx +++ /dev/null @@ -1,377 +0,0 @@ -import { FC, ReactElement, useState, useEffect } from 'react'; -import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; -import { Button, Textarea } from '@imphnen-frontend-service/ui/atoms'; -import { useForm, Controller } from 'react-hook-form'; -import { - userEditProfileSchema, - TUserEditProfileForm, - useUpdateUserMe, - useUploadAvatar, - useAuthStore, -} from '@imphnen-frontend-service/service'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { toast } from 'sonner'; -import { CitySelect } from '../../components/city-select'; -import { Icon } from '@iconify/react'; - -const ROLE_OPTIONS = [ - 'Frontend Developer', - 'Backend Developer', - 'Full Stack Developer', - 'DevOps Engineer', - 'UI/UX Designer', - 'Product Manager', - 'Data Scientist', - 'Mobile Developer', -]; - -type ProfileModalProps = { - open: boolean; - onClose: () => void; -}; - -const ProfilePage: FC = ({ - open, - onClose, -}): ReactElement | null => { - const [avatarFile, setAvatarFile] = useState(null); - const [avatarPreview, setAvatarPreview] = useState(''); - - const { mutateAsync: updateUser, isPending: isUpdating } = useUpdateUserMe(); - const { mutateAsync: uploadAvatar, isPending: isUploading } = - useUploadAvatar(); - const { session } = useAuthStore(); - - const form = useForm({ - resolver: zodResolver(userEditProfileSchema), - mode: 'all', - defaultValues: { - fullname: session?.user?.fullname || '', - avatar: session?.user?.avatar || null, - location: session?.user?.location || '', - bio: session?.user?.bio || '', - skills: session?.user?.skills || [], - }, - }); - - useEffect(() => { - if (session?.user?.avatar && !avatarPreview) { - setAvatarPreview(session.user.avatar); - } - if (session?.user?.fullname) { - form.setValue('fullname', session.user.fullname); - } - if (session?.user?.location) { - form.setValue('location', session.user.location); - } - if (session?.user?.bio) { - form.setValue('bio', session.user.bio); - } - if (session?.user?.skills) { - form.setValue('skills', session.user.skills); - } - }, [ - session?.user?.avatar, - session?.user?.fullname, - session?.user?.location, - session?.user?.bio, - session?.user?.skills, - avatarPreview, - form, - ]); - - const handleAvatarChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (file) { - if (file.size > 2 * 1024 * 1024) { - toast.error('The file is too large. Maximum size is 2MB.'); - e.target.value = ''; - return; - } - - if (!file.type.startsWith('image/')) { - toast.error('The file must be an image'); - e.target.value = ''; - return; - } - - setAvatarFile(file); - const reader = new FileReader(); - reader.onloadend = () => { - setAvatarPreview(reader.result as string); - }; - reader.readAsDataURL(file); - } - }; - - const onSubmit = form.handleSubmit(async (data) => { - try { - let avatarUrl = session?.user?.avatar || null; - - if (avatarFile) { - const uploadResult = await uploadAvatar(avatarFile); - avatarUrl = uploadResult.data.url; - } - - await updateUser({ - fullname: data.fullname, - avatar: avatarUrl, - location: data.location, - bio: data.bio, - skills: data.skills, - }); - - toast.success('Profile updated successfully!'); - - await new Promise((resolve) => setTimeout(resolve, 100)); - - onClose(); - } catch (error) { - console.error('Profile update failed:', error); - toast.error( - `Failed to update profile: ${ - error instanceof Error ? error.message : 'Unknown error' - }` - ); - } - }); - - const isLoading = isUpdating || isUploading; - - return open ? ( -
-
-
-
-
-

- Edit Profile -

- -
-

- Update your photo and name -

-
- -
-
-
- {avatarPreview ? ( - Avatar preview - ) : ( -
- -
- )} - -
-

- Click the camera icon to change your photo -
- Format: JPG, PNG. Max 2MB -

-
- - - -
- - ( - - )} - /> -
- -
- - ( -
-
- {ROLE_OPTIONS.map((role) => ( - - ))} -
-
- )} - /> -
- -
- - ( -
-