From 4240e8eb5197e2992e0b4e676d61d2b0cd8e7130 Mon Sep 17 00:00:00 2001
From: maulanasdqn
Date: Sat, 11 Apr 2026 17:04:14 +0700
Subject: [PATCH] feat: migrate all 5 Vite apps from react-router to TanStack
Router
Migrated backoffice, hackathon, dimentorin, gacha, qrcampaign, and
infra from custom react-router file-based routing to TanStack Router
file-based routing following the tanstack-frontend-best-practice
convention.
Key changes per app:
- New routes/ directory with __root.tsx, _public.tsx, _authenticated.tsx
- Auth guards via beforeLoad (replaces old middleware.ts)
- createFileRoute pattern for all page components
- TanStackRouterVite plugin in vite.config for auto route generation
- _components/_hooks folders colocated with routes (ignored by router)
- routeTree.gen.ts auto-generated on dev/build
Convention:
- _public/* routes redirect to dashboard if authenticated
- _authenticated/* routes redirect to /auth/login if not authenticated
- $param for dynamic segments (was [param] in old convention)
- _layout suffix for pathless layout routes
Removed:
- Old src/app/ directories from all apps
- Old src/middleware.ts files
- Custom convertPagesToRoute utility (no longer needed)
- react-router dependency usage (kept in package.json for shared libs)
Co-Authored-By: Claude Opus 4.6 (1M context)
---
apps/backoffice/src/app/(public)/layout.tsx | 12 -
apps/backoffice/src/app/404.tsx | 23 -
apps/backoffice/src/app/error.tsx | 29 --
apps/backoffice/src/main.tsx | 48 +-
apps/backoffice/src/middleware.ts | 100 ----
apps/backoffice/src/routeTree.gen.ts | 2 +
apps/backoffice/src/routes/__root.tsx | 5 +
.../layout.tsx => routes/_authenticated.tsx} | 29 +-
.../accounts}/modal-edit-account.tsx | 0
.../cms-events}/modal-add-event.tsx | 0
.../cms-events}/modal-delete-event.tsx | 0
.../cms-events}/modal-update-event.tsx | 0
.../modal-add-testimonial.tsx | 0
.../modal-delete-testimonial.tsx | 0
.../modal-update-testimonial.tsx | 0
.../chart/session-status.tsx | 0
.../chart/user-growth.tsx | 0
.../_components/dashboard}/modal-add-item.tsx | 0
.../dashboard}/modal-delete-item.tsx | 0
.../dashboard}/modal-edit-item.tsx | 0
.../gacha-roll}/modal-add-item.tsx | 0
.../gacha-roll}/modal-delete-item.tsx | 0
.../gacha-roll}/modal-update-item.tsx | 0
.../submission-modal.tsx | 0
.../modal-team-detail-new.tsx | 0
.../team-banner-placeholder.tsx | 0
.../hackathon-users}/modal-user-detail.tsx | 0
.../permissions}/modal-add-permission.tsx | 0
.../permissions}/modal-delete-permission.tsx | 0
.../permissions}/modal-update-permission.tsx | 0
.../prizes}/modal-process-item.tsx | 0
.../modal/create-roadmap/index.tsx | 0
.../_components/roles}/modal-add-role.tsx | 0
.../_components/roles}/modal-delete-role.tsx | 0
.../_components/roles}/modal-update-role.tsx | 0
.../modal/detail/index.tsx | 0
.../settings-dimentorin}/general.tsx | 0
.../settings-dimentorin}/notification.tsx | 0
.../settings-dimentorin}/payment.tsx | 0
.../settings-dimentorin}/security.tsx | 0
.../user-roles-permission.tsx | 0
.../transactions}/modal-validate.tsx | 0
.../users-dimentorin}/modal/delete/index.tsx | 0
.../modal/detail/account-profile.tsx | 0
.../modal/detail/activity-log.tsx | 0
.../modal/detail/detail-profile.tsx | 0
.../users-dimentorin}/modal/detail/index.tsx | 0
.../users-dimentorin}/modal/detail/type.d.ts | 0
.../modal/suspend-or-ban/index.tsx | 0
.../users-dimentorin}/modal/type.d.ts | 0
.../_hooks/cms-events}/use-item.ts | 0
.../_hooks/cms-testimonials}/use-item.ts | 0
.../_hooks/dashboard}/use-item.ts | 0
.../_hooks/gacha-roll}/use-item.ts | 0
.../_hooks/permissions}/use-item.ts | 0
.../_authenticated/_hooks/roles}/use-item.ts | 0
.../_authenticated/accounts.tsx} | 73 +--
.../_authenticated/cms-events.tsx} | 103 +++--
.../_authenticated/cms-testimonials.tsx} | 107 ++---
.../_authenticated/dashboard-dimentorin.tsx} | 51 +-
.../_authenticated/dashboard.tsx} | 91 ++--
.../feedback-review-dimentorin.tsx} | 47 +-
.../_authenticated/gacha-roll.tsx} | 109 ++---
.../_authenticated/hackathon-dashboard.tsx} | 33 +-
.../_authenticated/hackathon-submissions.tsx} | 157 ++++---
.../_authenticated/hackathon-teams.tsx} | 176 +++----
.../_authenticated/hackathon-users.tsx} | 200 ++++----
.../_authenticated/permissions.tsx} | 103 +++--
.../_authenticated/prizes.tsx} | 87 ++--
.../_authenticated/roadmap-dimentorin.tsx} | 41 +-
.../_authenticated/roles.tsx} | 103 +++--
.../_authenticated/session-dimentorin.tsx} | 51 +-
.../_authenticated/settings-dimentorin.tsx} | 37 +-
.../_authenticated/transactions.tsx} | 72 +--
.../_authenticated/users-dimentorin.tsx} | 85 ++--
apps/backoffice/src/routes/_public.tsx | 20 +
.../_public/auth}/_hooks/use-login.ts | 4 +-
.../_public/auth/login.tsx} | 45 +-
apps/backoffice/src/routes/index.tsx | 12 +
apps/backoffice/vite.config.ts | 10 +-
.../src/app/(public)/(home)/page.tsx | 20 -
apps/dimentorin/src/app/(public)/layout.tsx | 15 -
.../src/app/(public)/resources/page.tsx | 14 -
.../dimentorin/src/app/auth/forgot/layout.tsx | 12 -
.../src/app/auth/forgot/otp/layout.tsx | 12 -
.../src/app/auth/forgot/summon/layout.tsx | 12 -
.../src/app/auth/google-callback/page.tsx | 80 ----
apps/dimentorin/src/app/auth/login/layout.tsx | 12 -
.../src/app/auth/register-mentor/layout.tsx | 12 -
.../auth/register-mentor/pending/layout.tsx | 12 -
.../auth/register-mentor/success/layout.tsx | 12 -
.../src/app/auth/register/layout.tsx | 12 -
.../src/app/auth/register/otp/layout.tsx | 12 -
.../src/app/auth/register/success/layout.tsx | 12 -
apps/dimentorin/src/app/layout.tsx | 10 -
apps/dimentorin/src/main.tsx | 48 +-
apps/dimentorin/src/middleware.ts | 118 -----
apps/dimentorin/src/routeTree.gen.ts | 25 +
apps/dimentorin/src/routes/__root.tsx | 10 +
apps/dimentorin/src/routes/_authenticated.tsx | 16 +
.../_authenticated/dashboard.tsx} | 27 +-
.../_authenticated/dashboard_/index.tsx} | 32 +-
.../_authenticated/dashboard_/settings.tsx} | 33 +-
.../_components/icons/index.ts | 0
.../_components/icons/steam.tsx | 0
.../_hooks/use-google-login.ts | 6 +-
.../src/{app => routes}/_hooks/use-login.ts | 0
.../src/{app => routes}/_hooks/use-otp.ts | 0
.../{app => routes}/_hooks/use-register.ts | 0
.../{app => routes}/_hooks/use-resend-otp.ts | 0
apps/dimentorin/src/routes/_public.tsx | 20 +
.../_public/auth/forgot.tsx} | 21 +-
.../_public/auth/forgot_/otp.tsx} | 21 +-
.../_public/auth/forgot_/summon.tsx} | 25 +-
.../routes/_public/auth/google-callback.tsx | 83 ++++
.../_public/auth/google-oauth-popup.tsx} | 139 +++---
.../_public/auth/login.tsx} | 86 ++--
.../_public/auth/register-mentor.tsx} | 27 +-
.../auth/register-mentor_/pending.tsx} | 23 +-
.../auth/register-mentor_/success.tsx} | 21 +-
.../_public/auth/register.tsx} | 29 +-
.../_public/auth/register_/otp.tsx} | 63 +--
.../_public/auth/register_/success.tsx} | 21 +-
apps/dimentorin/src/routes/_site.tsx | 19 +
.../_site}/_components/footer.tsx | 2 +-
.../_site}/_components/header.tsx | 4 +-
.../_site/_components/home}/cta-section.tsx | 0
.../_site/_components/home}/faq-section.tsx | 0
.../_site/_components/home}/hero-section.tsx | 0
.../_components/home}/testimonial-section.tsx | 0
.../home}/what-we-offer-section.tsx | 0
.../page.tsx => routes/_site/articles.tsx} | 17 +-
.../_site/articles_/$slug.tsx} | 15 +-
.../articles_}/_components/card/article.tsx | 2 +-
.../page.tsx => routes/_site/mentoring.tsx} | 49 +-
.../_site/mentoring_/$id.tsx} | 28 +-
.../_components/modals/appointment/index.tsx | 0
.../modals/appointment/steps/payment.tsx | 0
.../modals/appointment/steps/profile.tsx | 0
.../appointment/steps/qris-payement.tsx | 0
.../modals/appointment/steps/schedule.tsx | 0
.../modals/appointment/steps/success.tsx | 0
.../modals/appointment/steps/topic.tsx | 0
.../modals/appointment/steps/va-payment.tsx | 0
.../$id}/_components/sections/education.tsx | 0
.../$id}/_components/sections/experience.tsx | 0
.../$id}/_components/sections/profile.tsx | 0
.../_components/sections/senpai-schedule.tsx | 0
.../sections/senpai-statistics.tsx | 0
.../$id}/_components/sections/topics.tsx | 0
.../_components/banner-section.tsx | 0
.../mentoring_}/_components/mentor-card.tsx | 2 +-
.../_site/mentoring_}/_components/topics.tsx | 0
.../page.tsx => routes/_site/profile.tsx} | 65 +--
.../_site/profile_/$id.tsx} | 79 ++--
.../buttons/edit-section-button.tsx | 0
.../profile_}/_components/buttons/index.ts | 0
.../_components/buttons/modal-button.tsx | 0
.../profile_}/_components/contexts/index.ts | 0
.../_components/contexts/profile-context.tsx | 4 +-
.../_components/guards/mentor-guard.tsx | 0
.../_site/profile_}/_components/index.ts | 0
.../profile_}/_components/modals/cv-modal.tsx | 0
.../_components/modals/description-modal.tsx | 0
.../_components/modals/edit-profile-modal.tsx | 0
.../_components/modals/education-modal.tsx | 0
.../_components/modals/experience-modal.tsx | 0
.../profile_}/_components/modals/index.ts | 0
.../_components/modals/languages-modal.tsx | 0
.../_components/modals/notification-modal.tsx | 0
.../modals/personal-info-modal.tsx | 0
.../modals/profile-basic-info-modal.tsx | 0
.../_components/modals/skills-modal.tsx | 0
.../_components/modals/social-media-modal.tsx | 0
.../profile_}/_components/profile/index.ts | 0
.../profile/profile-form-handlers.ts | 0
.../_components/profile/profile-form-hooks.ts | 0
.../_components/profile/profile-form-types.ts | 0
.../_components/profile/profile-form.tsx | 0
.../_components/profile/profile-header.tsx | 0
.../_components/profile/profile-info-new.tsx | 0
.../_components/profile/profile-info.tsx | 0
.../_components/profile/profile-sidebar.tsx | 0
.../_components/profile/profile-tabs.tsx | 0
.../sections/contact-info-section.tsx | 0
.../sections/cv-resume-section.tsx | 0
.../sections/description-section.tsx | 0
.../sections/education-section.tsx | 0
.../sections/experiences-section.tsx | 0
.../profile_}/_components/sections/index.ts | 0
.../sections/languages-section.tsx | 0
.../sections/personal-info-section.tsx | 0
.../_components/sections/skills-section.tsx | 0
.../sections/social-media-section.tsx | 0
.../_components/shared/file-uploader.tsx | 0
.../profile_}/_components/shared/index.ts | 0
.../_components/shared/section-wrapper.tsx | 0
.../dimentorin/src/routes/_site/resources.tsx | 19 +
.../resources_}/_components/collaborate.tsx | 0
.../resources_}/_components/contribute.tsx | 0
.../resources_}/_components/work-with-us.tsx | 0
apps/dimentorin/src/routes/index.tsx | 35 ++
apps/dimentorin/vite.config.ts | 10 +-
apps/gacha/src/app/layout.tsx | 16 -
apps/gacha/src/main.tsx | 48 +-
apps/gacha/src/middleware.ts | 5 -
apps/gacha/src/routeTree.gen.ts | 2 +
apps/gacha/src/routes/__root.tsx | 18 +
.../form/modal-form-forgot-password.tsx | 0
.../_components/form/modal-form-login.tsx | 0
.../_components/form/modal-form-register.tsx | 0
.../form/modal-form-verify-email.tsx | 0
.../_components/item/gacha-item.tsx | 0
.../src/{app => routes}/_hooks/use-login.ts | 4 +-
.../{app => routes}/_hooks/use-register.ts | 0
.../_hooks/use-verify-email.ts | 4 +-
.../src/{app/page.tsx => routes/index.tsx} | 33 +-
apps/gacha/vite.config.ts | 10 +-
apps/hackathon/src/app/404.tsx | 23 -
apps/hackathon/src/app/auth/callback/page.tsx | 160 -------
.../src/app/auth/forgot-password/page.tsx | 125 -----
.../src/app/auth/reset-password/page.tsx | 158 -------
apps/hackathon/src/app/auth/signup/page.tsx | 402 ----------------
apps/hackathon/src/app/dashboard/layout.tsx | 46 --
apps/hackathon/src/app/error.tsx | 29 --
apps/hackathon/src/app/layout.tsx | 96 ----
apps/hackathon/src/app/profile/page.tsx | 377 ---------------
.../src/app/teams/[teamId]/layout.tsx | 46 --
apps/hackathon/src/app/teams/layout.tsx | 48 --
.../src/app/users/[userId]/layout.tsx | 46 --
apps/hackathon/src/main.tsx | 43 +-
apps/hackathon/src/middleware.ts | 120 -----
apps/hackathon/src/routeTree.gen.ts | 3 +
apps/hackathon/src/routes/__root.tsx | 5 +
apps/hackathon/src/routes/_authenticated.tsx | 92 ++++
.../_components/profile-modal.tsx | 243 ++++++++++
.../_authenticated/dashboard.tsx} | 14 +-
.../_authenticated/onboarding/user.tsx} | 10 +-
.../_authenticated/teams/$teamId.tsx} | 21 +-
.../_authenticated/teams/$teamId/chat.tsx} | 13 +-
.../_authenticated/teams/$teamId/edit.tsx} | 26 +-
.../_authenticated/teams/$teamId/members.tsx} | 18 +-
.../teams/$teamId/submission.tsx} | 17 +-
.../_authenticated/teams/$teamId/submit.tsx} | 31 +-
.../_authenticated/teams/browse.tsx} | 25 +-
.../_authenticated/teams/create.tsx} | 15 +-
.../_authenticated/users/$userId.tsx} | 13 +-
apps/hackathon/src/routes/_public.tsx | 9 +
.../src/routes/_public/auth/callback.tsx | 123 +++++
.../routes/_public/auth/forgot-password.tsx | 74 +++
.../_public/auth/login.tsx} | 90 ++--
.../routes/_public/auth/reset-password.tsx | 87 ++++
.../src/routes/_public/auth/signup.tsx | 227 +++++++++
.../_public/certificate/$certId.tsx} | 44 +-
.../_public/certificate/winner/$certId.tsx} | 66 ++-
.../_public/maintenance.tsx} | 17 +-
.../page.tsx => routes/_public/winners.tsx} | 7 +-
.../src/{app/page.tsx => routes/index.tsx} | 341 ++++----------
apps/hackathon/vite.config.ts | 10 +-
apps/infra/src/main.tsx | 25 +-
apps/infra/src/routeTree.gen.ts | 2 +
apps/infra/src/routes/__root.tsx | 5 +
.../src/{app/App.tsx => routes/index.tsx} | 14 +-
apps/infra/vite.config.ts | 10 +-
apps/qrcampaign/src/app/404.tsx | 23 -
apps/qrcampaign/src/app/admin/layout.tsx | 10 -
apps/qrcampaign/src/app/admin/page.tsx | 3 -
apps/qrcampaign/src/app/error.tsx | 29 --
.../admin/components/RequireAdmin.tsx | 7 +-
.../features/admin/pages/AdminDashboard.tsx | 2 +-
.../features/auth/components/RequireAuth.tsx | 7 +-
.../src/app/features/auth/pages/LoginPage.tsx | 2 +-
apps/qrcampaign/src/components/Sidebar.tsx | 2 +-
apps/qrcampaign/src/main.tsx | 50 +-
apps/qrcampaign/src/routeTree.gen.ts | 2 +
apps/qrcampaign/src/routes/__root.tsx | 5 +
.../layout.tsx => routes/_authenticated.tsx} | 58 +--
.../src/routes/_authenticated/admin.tsx | 16 +
.../_authenticated/admin/campaigns.tsx} | 103 +++--
.../src/routes/_authenticated/admin/index.tsx | 9 +
.../_authenticated/admin/users.tsx} | 97 ++--
.../_authenticated/index.tsx} | 81 ++--
apps/qrcampaign/src/routes/_public.tsx | 16 +
.../_public/auth/forgot-password.tsx} | 46 +-
.../_public/auth/login.tsx} | 64 +--
.../_public/auth/reset-password.tsx} | 76 +--
.../_public/auth/signup.tsx} | 96 ++--
.../page.tsx => routes/auth/callback.tsx} | 100 ++--
apps/qrcampaign/vite.config.mts | 10 +-
package-lock.json | 434 +++++++++++++++++-
package.json | 2 +
291 files changed, 3889 insertions(+), 4585 deletions(-)
delete mode 100644 apps/backoffice/src/app/(public)/layout.tsx
delete mode 100644 apps/backoffice/src/app/404.tsx
delete mode 100644 apps/backoffice/src/app/error.tsx
delete mode 100644 apps/backoffice/src/middleware.ts
create mode 100644 apps/backoffice/src/routeTree.gen.ts
create mode 100644 apps/backoffice/src/routes/__root.tsx
rename apps/backoffice/src/{app/(protected)/layout.tsx => routes/_authenticated.tsx} (76%)
rename apps/backoffice/src/{app/(protected)/accounts/_components => routes/_authenticated/_components/accounts}/modal-edit-account.tsx (100%)
rename apps/backoffice/src/{app/(protected)/cms-events/_components => routes/_authenticated/_components/cms-events}/modal-add-event.tsx (100%)
rename apps/backoffice/src/{app/(protected)/cms-events/_components => routes/_authenticated/_components/cms-events}/modal-delete-event.tsx (100%)
rename apps/backoffice/src/{app/(protected)/cms-events/_components => routes/_authenticated/_components/cms-events}/modal-update-event.tsx (100%)
rename apps/backoffice/src/{app/(protected)/cms-testimonials/_components => routes/_authenticated/_components/cms-testimonials}/modal-add-testimonial.tsx (100%)
rename apps/backoffice/src/{app/(protected)/cms-testimonials/_components => routes/_authenticated/_components/cms-testimonials}/modal-delete-testimonial.tsx (100%)
rename apps/backoffice/src/{app/(protected)/cms-testimonials/_components => routes/_authenticated/_components/cms-testimonials}/modal-update-testimonial.tsx (100%)
rename apps/backoffice/src/{app/(protected)/dashboard-dimentorin/_components => routes/_authenticated/_components/dashboard-dimentorin}/chart/session-status.tsx (100%)
rename apps/backoffice/src/{app/(protected)/dashboard-dimentorin/_components => routes/_authenticated/_components/dashboard-dimentorin}/chart/user-growth.tsx (100%)
rename apps/backoffice/src/{app/(protected)/dashboard/_components => routes/_authenticated/_components/dashboard}/modal-add-item.tsx (100%)
rename apps/backoffice/src/{app/(protected)/dashboard/_components => routes/_authenticated/_components/dashboard}/modal-delete-item.tsx (100%)
rename apps/backoffice/src/{app/(protected)/dashboard/_components => routes/_authenticated/_components/dashboard}/modal-edit-item.tsx (100%)
rename apps/backoffice/src/{app/(protected)/gacha-roll/_components => routes/_authenticated/_components/gacha-roll}/modal-add-item.tsx (100%)
rename apps/backoffice/src/{app/(protected)/gacha-roll/_components => routes/_authenticated/_components/gacha-roll}/modal-delete-item.tsx (100%)
rename apps/backoffice/src/{app/(protected)/gacha-roll/_components => routes/_authenticated/_components/gacha-roll}/modal-update-item.tsx (100%)
rename apps/backoffice/src/{app/(protected)/hackathon-submissions/_components => routes/_authenticated/_components/hackathon-submissions}/submission-modal.tsx (100%)
rename apps/backoffice/src/{app/(protected)/hackathon-teams/_components => routes/_authenticated/_components/hackathon-teams}/modal-team-detail-new.tsx (100%)
rename apps/backoffice/src/{app/(protected)/hackathon-teams/_components => routes/_authenticated/_components/hackathon-teams}/team-banner-placeholder.tsx (100%)
rename apps/backoffice/src/{app/(protected)/hackathon-users/_components => routes/_authenticated/_components/hackathon-users}/modal-user-detail.tsx (100%)
rename apps/backoffice/src/{app/(protected)/permissions/_components => routes/_authenticated/_components/permissions}/modal-add-permission.tsx (100%)
rename apps/backoffice/src/{app/(protected)/permissions/_components => routes/_authenticated/_components/permissions}/modal-delete-permission.tsx (100%)
rename apps/backoffice/src/{app/(protected)/permissions/_components => routes/_authenticated/_components/permissions}/modal-update-permission.tsx (100%)
rename apps/backoffice/src/{app/(protected)/prizes/_components => routes/_authenticated/_components/prizes}/modal-process-item.tsx (100%)
rename apps/backoffice/src/{app/(protected)/roadmap-dimentorin/_components => routes/_authenticated/_components/roadmap-dimentorin}/modal/create-roadmap/index.tsx (100%)
rename apps/backoffice/src/{app/(protected)/roles/_components => routes/_authenticated/_components/roles}/modal-add-role.tsx (100%)
rename apps/backoffice/src/{app/(protected)/roles/_components => routes/_authenticated/_components/roles}/modal-delete-role.tsx (100%)
rename apps/backoffice/src/{app/(protected)/roles/_components => routes/_authenticated/_components/roles}/modal-update-role.tsx (100%)
rename apps/backoffice/src/{app/(protected)/session-dimentorin/_components => routes/_authenticated/_components/session-dimentorin}/modal/detail/index.tsx (100%)
rename apps/backoffice/src/{app/(protected)/settings-dimentorin/_components => routes/_authenticated/_components/settings-dimentorin}/general.tsx (100%)
rename apps/backoffice/src/{app/(protected)/settings-dimentorin/_components => routes/_authenticated/_components/settings-dimentorin}/notification.tsx (100%)
rename apps/backoffice/src/{app/(protected)/settings-dimentorin/_components => routes/_authenticated/_components/settings-dimentorin}/payment.tsx (100%)
rename apps/backoffice/src/{app/(protected)/settings-dimentorin/_components => routes/_authenticated/_components/settings-dimentorin}/security.tsx (100%)
rename apps/backoffice/src/{app/(protected)/settings-dimentorin/_components => routes/_authenticated/_components/settings-dimentorin}/user-roles-permission.tsx (100%)
rename apps/backoffice/src/{app/(protected)/transactions/_components => routes/_authenticated/_components/transactions}/modal-validate.tsx (100%)
rename apps/backoffice/src/{app/(protected)/users-dimentorin/_components => routes/_authenticated/_components/users-dimentorin}/modal/delete/index.tsx (100%)
rename apps/backoffice/src/{app/(protected)/users-dimentorin/_components => routes/_authenticated/_components/users-dimentorin}/modal/detail/account-profile.tsx (100%)
rename apps/backoffice/src/{app/(protected)/users-dimentorin/_components => routes/_authenticated/_components/users-dimentorin}/modal/detail/activity-log.tsx (100%)
rename apps/backoffice/src/{app/(protected)/users-dimentorin/_components => routes/_authenticated/_components/users-dimentorin}/modal/detail/detail-profile.tsx (100%)
rename apps/backoffice/src/{app/(protected)/users-dimentorin/_components => routes/_authenticated/_components/users-dimentorin}/modal/detail/index.tsx (100%)
rename apps/backoffice/src/{app/(protected)/users-dimentorin/_components => routes/_authenticated/_components/users-dimentorin}/modal/detail/type.d.ts (100%)
rename apps/backoffice/src/{app/(protected)/users-dimentorin/_components => routes/_authenticated/_components/users-dimentorin}/modal/suspend-or-ban/index.tsx (100%)
rename apps/backoffice/src/{app/(protected)/users-dimentorin/_components => routes/_authenticated/_components/users-dimentorin}/modal/type.d.ts (100%)
rename apps/backoffice/src/{app/(protected)/cms-events/_hook => routes/_authenticated/_hooks/cms-events}/use-item.ts (100%)
rename apps/backoffice/src/{app/(protected)/cms-testimonials/_hook => routes/_authenticated/_hooks/cms-testimonials}/use-item.ts (100%)
rename apps/backoffice/src/{app/(protected)/dashboard/_hook => routes/_authenticated/_hooks/dashboard}/use-item.ts (100%)
rename apps/backoffice/src/{app/(protected)/gacha-roll/_hook => routes/_authenticated/_hooks/gacha-roll}/use-item.ts (100%)
rename apps/backoffice/src/{app/(protected)/permissions/_hook => routes/_authenticated/_hooks/permissions}/use-item.ts (100%)
rename apps/backoffice/src/{app/(protected)/roles/_hook => routes/_authenticated/_hooks/roles}/use-item.ts (100%)
rename apps/backoffice/src/{app/(protected)/accounts/page.tsx => routes/_authenticated/accounts.tsx} (83%)
rename apps/backoffice/src/{app/(protected)/cms-events/page.tsx => routes/_authenticated/cms-events.tsx} (82%)
rename apps/backoffice/src/{app/(protected)/cms-testimonials/page.tsx => routes/_authenticated/cms-testimonials.tsx} (81%)
rename apps/backoffice/src/{app/(protected)/dashboard-dimentorin/page.tsx => routes/_authenticated/dashboard-dimentorin.tsx} (82%)
rename apps/backoffice/src/{app/(protected)/dashboard/page.tsx => routes/_authenticated/dashboard.tsx} (85%)
rename apps/backoffice/src/{app/(protected)/feedback-review-dimentorin/page.tsx => routes/_authenticated/feedback-review-dimentorin.tsx} (82%)
rename apps/backoffice/src/{app/(protected)/gacha-roll/page.tsx => routes/_authenticated/gacha-roll.tsx} (81%)
rename apps/backoffice/src/{app/(protected)/hackathon-dashboard/page.tsx => routes/_authenticated/hackathon-dashboard.tsx} (77%)
rename apps/backoffice/src/{app/(protected)/hackathon-submissions/page.tsx => routes/_authenticated/hackathon-submissions.tsx} (70%)
rename apps/backoffice/src/{app/(protected)/hackathon-teams/page.tsx => routes/_authenticated/hackathon-teams.tsx} (76%)
rename apps/backoffice/src/{app/(protected)/hackathon-users/page.tsx => routes/_authenticated/hackathon-users.tsx} (77%)
rename apps/backoffice/src/{app/(protected)/permissions/page.tsx => routes/_authenticated/permissions.tsx} (81%)
rename apps/backoffice/src/{app/(protected)/prizes/page.tsx => routes/_authenticated/prizes.tsx} (85%)
rename apps/backoffice/src/{app/(protected)/roadmap-dimentorin/page.tsx => routes/_authenticated/roadmap-dimentorin.tsx} (83%)
rename apps/backoffice/src/{app/(protected)/roles/page.tsx => routes/_authenticated/roles.tsx} (80%)
rename apps/backoffice/src/{app/(protected)/session-dimentorin/page.tsx => routes/_authenticated/session-dimentorin.tsx} (78%)
rename apps/backoffice/src/{app/(protected)/settings-dimentorin/page.tsx => routes/_authenticated/settings-dimentorin.tsx} (52%)
rename apps/backoffice/src/{app/(protected)/transactions/page.tsx => routes/_authenticated/transactions.tsx} (83%)
rename apps/backoffice/src/{app/(protected)/users-dimentorin/page.tsx => routes/_authenticated/users-dimentorin.tsx} (84%)
create mode 100644 apps/backoffice/src/routes/_public.tsx
rename apps/backoffice/src/{app/(public)/auth/login => routes/_public/auth}/_hooks/use-login.ts (89%)
rename apps/backoffice/src/{app/(public)/auth/login/page.tsx => routes/_public/auth/login.tsx} (81%)
create mode 100644 apps/backoffice/src/routes/index.tsx
delete mode 100644 apps/dimentorin/src/app/(public)/(home)/page.tsx
delete mode 100644 apps/dimentorin/src/app/(public)/layout.tsx
delete mode 100644 apps/dimentorin/src/app/(public)/resources/page.tsx
delete mode 100644 apps/dimentorin/src/app/auth/forgot/layout.tsx
delete mode 100644 apps/dimentorin/src/app/auth/forgot/otp/layout.tsx
delete mode 100644 apps/dimentorin/src/app/auth/forgot/summon/layout.tsx
delete mode 100644 apps/dimentorin/src/app/auth/google-callback/page.tsx
delete mode 100644 apps/dimentorin/src/app/auth/login/layout.tsx
delete mode 100644 apps/dimentorin/src/app/auth/register-mentor/layout.tsx
delete mode 100644 apps/dimentorin/src/app/auth/register-mentor/pending/layout.tsx
delete mode 100644 apps/dimentorin/src/app/auth/register-mentor/success/layout.tsx
delete mode 100644 apps/dimentorin/src/app/auth/register/layout.tsx
delete mode 100644 apps/dimentorin/src/app/auth/register/otp/layout.tsx
delete mode 100644 apps/dimentorin/src/app/auth/register/success/layout.tsx
delete mode 100644 apps/dimentorin/src/app/layout.tsx
delete mode 100644 apps/dimentorin/src/middleware.ts
create mode 100644 apps/dimentorin/src/routeTree.gen.ts
create mode 100644 apps/dimentorin/src/routes/__root.tsx
create mode 100644 apps/dimentorin/src/routes/_authenticated.tsx
rename apps/dimentorin/src/{app/dashboard/layout.tsx => routes/_authenticated/dashboard.tsx} (81%)
rename apps/dimentorin/src/{app/dashboard/page.tsx => routes/_authenticated/dashboard_/index.tsx} (93%)
rename apps/dimentorin/src/{app/dashboard/settings/page.tsx => routes/_authenticated/dashboard_/settings.tsx} (95%)
rename apps/dimentorin/src/{app => routes}/_components/icons/index.ts (100%)
rename apps/dimentorin/src/{app => routes}/_components/icons/steam.tsx (100%)
rename apps/dimentorin/src/{app => routes}/_hooks/use-google-login.ts (96%)
rename apps/dimentorin/src/{app => routes}/_hooks/use-login.ts (100%)
rename apps/dimentorin/src/{app => routes}/_hooks/use-otp.ts (100%)
rename apps/dimentorin/src/{app => routes}/_hooks/use-register.ts (100%)
rename apps/dimentorin/src/{app => routes}/_hooks/use-resend-otp.ts (100%)
create mode 100644 apps/dimentorin/src/routes/_public.tsx
rename apps/dimentorin/src/{app/auth/forgot/page.tsx => routes/_public/auth/forgot.tsx} (71%)
rename apps/dimentorin/src/{app/auth/forgot/otp/page.tsx => routes/_public/auth/forgot_/otp.tsx} (61%)
rename apps/dimentorin/src/{app/auth/forgot/summon/page.tsx => routes/_public/auth/forgot_/summon.tsx} (78%)
create mode 100644 apps/dimentorin/src/routes/_public/auth/google-callback.tsx
rename apps/dimentorin/src/{app/auth/google-oauth-popup/page.tsx => routes/_public/auth/google-oauth-popup.tsx} (62%)
rename apps/dimentorin/src/{app/auth/login/page.tsx => routes/_public/auth/login.tsx} (74%)
rename apps/dimentorin/src/{app/auth/register-mentor/page.tsx => routes/_public/auth/register-mentor.tsx} (94%)
rename apps/dimentorin/src/{app/auth/register-mentor/pending/page.tsx => routes/_public/auth/register-mentor_/pending.tsx} (82%)
rename apps/dimentorin/src/{app/auth/register-mentor/success/page.tsx => routes/_public/auth/register-mentor_/success.tsx} (84%)
rename apps/dimentorin/src/{app/auth/register/page.tsx => routes/_public/auth/register.tsx} (87%)
rename apps/dimentorin/src/{app/auth/register/otp/page.tsx => routes/_public/auth/register_/otp.tsx} (58%)
rename apps/dimentorin/src/{app/auth/register/success/page.tsx => routes/_public/auth/register_/success.tsx} (85%)
create mode 100644 apps/dimentorin/src/routes/_site.tsx
rename apps/dimentorin/src/{app/(public) => routes/_site}/_components/footer.tsx (99%)
rename apps/dimentorin/src/{app/(public) => routes/_site}/_components/header.tsx (96%)
rename apps/dimentorin/src/{app/(public)/(home)/_components => routes/_site/_components/home}/cta-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/(home)/_components => routes/_site/_components/home}/faq-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/(home)/_components => routes/_site/_components/home}/hero-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/(home)/_components => routes/_site/_components/home}/testimonial-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/(home)/_components => routes/_site/_components/home}/what-we-offer-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/articles/page.tsx => routes/_site/articles.tsx} (88%)
rename apps/dimentorin/src/{app/(public)/articles/[slug]/page.tsx => routes/_site/articles_/$slug.tsx} (92%)
rename apps/dimentorin/src/{app/(public)/articles => routes/_site/articles_}/_components/card/article.tsx (98%)
rename apps/dimentorin/src/{app/(public)/mentoring/page.tsx => routes/_site/mentoring.tsx} (79%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id]/page.tsx => routes/_site/mentoring_/$id.tsx} (72%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/modals/appointment/index.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/modals/appointment/steps/payment.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/modals/appointment/steps/profile.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/modals/appointment/steps/qris-payement.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/modals/appointment/steps/schedule.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/modals/appointment/steps/success.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/modals/appointment/steps/topic.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/modals/appointment/steps/va-payment.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/sections/education.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/sections/experience.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/sections/profile.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/sections/senpai-schedule.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/sections/senpai-statistics.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring/[id] => routes/_site/mentoring_/$id}/_components/sections/topics.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring => routes/_site/mentoring_}/_components/banner-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/mentoring => routes/_site/mentoring_}/_components/mentor-card.tsx (98%)
rename apps/dimentorin/src/{app/(public)/mentoring => routes/_site/mentoring_}/_components/topics.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile/page.tsx => routes/_site/profile.tsx} (73%)
rename apps/dimentorin/src/{app/(public)/profile/[id]/page.tsx => routes/_site/profile_/$id.tsx} (76%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/buttons/edit-section-button.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/buttons/index.ts (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/buttons/modal-button.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/contexts/index.ts (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/contexts/profile-context.tsx (98%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/guards/mentor-guard.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/index.ts (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/cv-modal.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/description-modal.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/edit-profile-modal.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/education-modal.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/experience-modal.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/index.ts (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/languages-modal.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/notification-modal.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/personal-info-modal.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/profile-basic-info-modal.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/skills-modal.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/modals/social-media-modal.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/profile/index.ts (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/profile/profile-form-handlers.ts (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/profile/profile-form-hooks.ts (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/profile/profile-form-types.ts (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/profile/profile-form.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/profile/profile-header.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/profile/profile-info-new.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/profile/profile-info.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/profile/profile-sidebar.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/profile/profile-tabs.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/sections/contact-info-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/sections/cv-resume-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/sections/description-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/sections/education-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/sections/experiences-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/sections/index.ts (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/sections/languages-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/sections/personal-info-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/sections/skills-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/sections/social-media-section.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/shared/file-uploader.tsx (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/shared/index.ts (100%)
rename apps/dimentorin/src/{app/(public)/profile => routes/_site/profile_}/_components/shared/section-wrapper.tsx (100%)
create mode 100644 apps/dimentorin/src/routes/_site/resources.tsx
rename apps/dimentorin/src/{app/(public)/resources => routes/_site/resources_}/_components/collaborate.tsx (100%)
rename apps/dimentorin/src/{app/(public)/resources => routes/_site/resources_}/_components/contribute.tsx (100%)
rename apps/dimentorin/src/{app/(public)/resources => routes/_site/resources_}/_components/work-with-us.tsx (100%)
create mode 100644 apps/dimentorin/src/routes/index.tsx
delete mode 100644 apps/gacha/src/app/layout.tsx
delete mode 100644 apps/gacha/src/middleware.ts
create mode 100644 apps/gacha/src/routeTree.gen.ts
create mode 100644 apps/gacha/src/routes/__root.tsx
rename apps/gacha/src/{app => routes}/_components/form/modal-form-forgot-password.tsx (100%)
rename apps/gacha/src/{app => routes}/_components/form/modal-form-login.tsx (100%)
rename apps/gacha/src/{app => routes}/_components/form/modal-form-register.tsx (100%)
rename apps/gacha/src/{app => routes}/_components/form/modal-form-verify-email.tsx (100%)
rename apps/gacha/src/{app => routes}/_components/item/gacha-item.tsx (100%)
rename apps/gacha/src/{app => routes}/_hooks/use-login.ts (94%)
rename apps/gacha/src/{app => routes}/_hooks/use-register.ts (100%)
rename apps/gacha/src/{app => routes}/_hooks/use-verify-email.ts (92%)
rename apps/gacha/src/{app/page.tsx => routes/index.tsx} (94%)
delete mode 100644 apps/hackathon/src/app/404.tsx
delete mode 100644 apps/hackathon/src/app/auth/callback/page.tsx
delete mode 100644 apps/hackathon/src/app/auth/forgot-password/page.tsx
delete mode 100644 apps/hackathon/src/app/auth/reset-password/page.tsx
delete mode 100644 apps/hackathon/src/app/auth/signup/page.tsx
delete mode 100644 apps/hackathon/src/app/dashboard/layout.tsx
delete mode 100644 apps/hackathon/src/app/error.tsx
delete mode 100644 apps/hackathon/src/app/layout.tsx
delete mode 100644 apps/hackathon/src/app/profile/page.tsx
delete mode 100644 apps/hackathon/src/app/teams/[teamId]/layout.tsx
delete mode 100644 apps/hackathon/src/app/teams/layout.tsx
delete mode 100644 apps/hackathon/src/app/users/[userId]/layout.tsx
delete mode 100644 apps/hackathon/src/middleware.ts
create mode 100644 apps/hackathon/src/routeTree.gen.ts
create mode 100644 apps/hackathon/src/routes/__root.tsx
create mode 100644 apps/hackathon/src/routes/_authenticated.tsx
create mode 100644 apps/hackathon/src/routes/_authenticated/_components/profile-modal.tsx
rename apps/hackathon/src/{app/dashboard/page.tsx => routes/_authenticated/dashboard.tsx} (98%)
rename apps/hackathon/src/{app/onboarding/user/page.tsx => routes/_authenticated/onboarding/user.tsx} (97%)
rename apps/hackathon/src/{app/teams/[teamId]/page.tsx => routes/_authenticated/teams/$teamId.tsx} (98%)
rename apps/hackathon/src/{app/teams/[teamId]/chat/page.tsx => routes/_authenticated/teams/$teamId/chat.tsx} (97%)
rename apps/hackathon/src/{app/teams/[teamId]/edit/page.tsx => routes/_authenticated/teams/$teamId/edit.tsx} (96%)
rename apps/hackathon/src/{app/teams/[teamId]/members/page.tsx => routes/_authenticated/teams/$teamId/members.tsx} (97%)
rename apps/hackathon/src/{app/teams/[teamId]/submission/page.tsx => routes/_authenticated/teams/$teamId/submission.tsx} (96%)
rename apps/hackathon/src/{app/teams/[teamId]/submit/page.tsx => routes/_authenticated/teams/$teamId/submit.tsx} (96%)
rename apps/hackathon/src/{app/teams/browse/page.tsx => routes/_authenticated/teams/browse.tsx} (96%)
rename apps/hackathon/src/{app/teams/create/page.tsx => routes/_authenticated/teams/create.tsx} (97%)
rename apps/hackathon/src/{app/users/[userId]/page.tsx => routes/_authenticated/users/$userId.tsx} (96%)
create mode 100644 apps/hackathon/src/routes/_public.tsx
create mode 100644 apps/hackathon/src/routes/_public/auth/callback.tsx
create mode 100644 apps/hackathon/src/routes/_public/auth/forgot-password.tsx
rename apps/hackathon/src/{app/auth/login/page.tsx => routes/_public/auth/login.tsx} (74%)
create mode 100644 apps/hackathon/src/routes/_public/auth/reset-password.tsx
create mode 100644 apps/hackathon/src/routes/_public/auth/signup.tsx
rename apps/hackathon/src/{app/certificate/[certId]/page.tsx => routes/_public/certificate/$certId.tsx} (93%)
rename apps/hackathon/src/{app/certificate/winner/[certId]/page.tsx => routes/_public/certificate/winner/$certId.tsx} (92%)
rename apps/hackathon/src/{app/maintenance/page.tsx => routes/_public/maintenance.tsx} (64%)
rename apps/hackathon/src/{app/winners/page.tsx => routes/_public/winners.tsx} (98%)
rename apps/hackathon/src/{app/page.tsx => routes/index.tsx} (76%)
create mode 100644 apps/infra/src/routeTree.gen.ts
create mode 100644 apps/infra/src/routes/__root.tsx
rename apps/infra/src/{app/App.tsx => routes/index.tsx} (98%)
delete mode 100644 apps/qrcampaign/src/app/404.tsx
delete mode 100644 apps/qrcampaign/src/app/admin/layout.tsx
delete mode 100644 apps/qrcampaign/src/app/admin/page.tsx
delete mode 100644 apps/qrcampaign/src/app/error.tsx
create mode 100644 apps/qrcampaign/src/routeTree.gen.ts
create mode 100644 apps/qrcampaign/src/routes/__root.tsx
rename apps/qrcampaign/src/{app/layout.tsx => routes/_authenticated.tsx} (50%)
create mode 100644 apps/qrcampaign/src/routes/_authenticated/admin.tsx
rename apps/qrcampaign/src/{app/admin/campaigns/page.tsx => routes/_authenticated/admin/campaigns.tsx} (80%)
create mode 100644 apps/qrcampaign/src/routes/_authenticated/admin/index.tsx
rename apps/qrcampaign/src/{app/admin/users/page.tsx => routes/_authenticated/admin/users.tsx} (76%)
rename apps/qrcampaign/src/{app/page.tsx => routes/_authenticated/index.tsx} (72%)
create mode 100644 apps/qrcampaign/src/routes/_public.tsx
rename apps/qrcampaign/src/{app/auth/forgot-password/page.tsx => routes/_public/auth/forgot-password.tsx} (83%)
rename apps/qrcampaign/src/{app/auth/login/page.tsx => routes/_public/auth/login.tsx} (79%)
rename apps/qrcampaign/src/{app/auth/reset-password/page.tsx => routes/_public/auth/reset-password.tsx} (80%)
rename apps/qrcampaign/src/{app/auth/signup/page.tsx => routes/_public/auth/signup.tsx} (83%)
rename apps/qrcampaign/src/{app/auth/callback/page.tsx => routes/auth/callback.tsx} (74%)
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: () => (
@@ -128,10 +133,10 @@ export default function Components(): ReactElement {
{
- setActiveTab(tab);
- setPagination((p) => ({ ...p, pageIndex: 0 }));
+ setActiveTab(tab)
+ setPagination((p) => ({ ...p, pageIndex: 0 }))
}}
>
{tab}
@@ -175,5 +180,5 @@ export default function Components(): ReactElement {
)}
- );
+ )
}
diff --git a/apps/backoffice/src/app/(protected)/gacha-roll/page.tsx b/apps/backoffice/src/routes/_authenticated/gacha-roll.tsx
similarity index 81%
rename from apps/backoffice/src/app/(protected)/gacha-roll/page.tsx
rename to apps/backoffice/src/routes/_authenticated/gacha-roll.tsx
index e3455fc..f281214 100644
--- a/apps/backoffice/src/app/(protected)/gacha-roll/page.tsx
+++ b/apps/backoffice/src/routes/_authenticated/gacha-roll.tsx
@@ -1,15 +1,14 @@
-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 {
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,
@@ -17,26 +16,30 @@ import {
PaginationState,
useReactTable,
RowSelectionState,
-} from '@tanstack/react-table';
-import ModalAddItem from './_components/modal-add-item';
-import ModalUpdateItem from './_components/modal-update-item';
-import ModalDeleteItem from './_components/modal-delete-item';
-import { useQueryState } from '@imphnen-frontend-service/utils';
+} from '@tanstack/react-table'
+import ModalAddItem from './_components/gacha-roll/modal-add-item'
+import ModalUpdateItem from './_components/gacha-roll/modal-update-item'
+import ModalDeleteItem from './_components/gacha-roll/modal-delete-item'
+import { useQueryState } from '@imphnen-frontend-service/utils'
import {
useGachaItemList,
useCreateGachaItem,
useUpdateGachaItem,
useDeleteGachaItem,
TGachaItemDto,
-} from '@imphnen-frontend-service/service';
+} from '@imphnen-frontend-service/service'
-export const Components: FC = (): ReactElement => {
- const [showModalAddItem, setShowModalAddItem] = useState(false);
- const [showModalUpdateItem, setShowModalUpdateItem] = useState(false);
- const [showModalDeleteItem, setShowModalDeleteItem] = useState(false);
- const [selectedItem, setSelectedItem] = useState(null);
- const [search, setSearch] = useState('');
- const pendingFormData = useRef(null);
+export const Route = createFileRoute('/_authenticated/gacha-roll')({
+ component: GachaRollPage,
+})
+
+function GachaRollPage() {
+ const [showModalAddItem, setShowModalAddItem] = useState(false)
+ const [showModalUpdateItem, setShowModalUpdateItem] = useState(false)
+ const [showModalDeleteItem, setShowModalDeleteItem] = useState(false)
+ const [selectedItem, setSelectedItem] = useState(null)
+ const [search, setSearch] = useState('')
+ const pendingFormData = useRef(null)
const {
step: currentStep,
@@ -47,30 +50,30 @@ 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: itemsData, isLoading } = useGachaItemList({
search,
page: pagination.pageIndex + 1,
per_page: pagination.pageSize,
- });
- const createItem = useCreateGachaItem();
- const updateItem = useUpdateGachaItem();
- const deleteItem = useDeleteGachaItem();
+ })
+ const createItem = useCreateGachaItem()
+ const updateItem = useUpdateGachaItem()
+ const deleteItem = useDeleteGachaItem()
- const items: TGachaItemDto[] = itemsData?.data ?? [];
- const totalItems = itemsData?.meta?.total ?? items.length;
+ const items: TGachaItemDto[] = itemsData?.data ?? []
+ const totalItems = itemsData?.meta?.total ?? items.length
const handleAdd = async (): Promise => {
if (pendingFormData.current) {
- const { itemName, quantity, chanceRate } = pendingFormData.current;
+ const { itemName, quantity, chanceRate } = pendingFormData.current
await createItem.mutateAsync({
item_code: (itemName as string).toLowerCase().replace(/\s+/g, '-'),
name: itemName,
@@ -82,14 +85,14 @@ export const Components: FC = (): ReactElement => {
weight: chanceRate ?? 1,
stock: quantity ?? 1,
is_limited: false,
- });
+ })
}
- return true;
- };
+ return true
+ }
const handleUpdate = async (): Promise => {
if (selectedItem && pendingFormData.current) {
- const { itemName, quantity, chanceRate } = pendingFormData.current;
+ const { itemName, quantity, chanceRate } = pendingFormData.current
await updateItem.mutateAsync({
id: selectedItem.id,
data: {
@@ -97,17 +100,17 @@ export const Components: FC = (): ReactElement => {
weight: chanceRate,
stock: quantity,
},
- });
+ })
}
- return true;
- };
+ return true
+ }
const handleDelete = async () => {
if (selectedItem) {
- await deleteItem.mutateAsync(selectedItem.id);
+ await deleteItem.mutateAsync(selectedItem.id)
}
- setShowModalDeleteItem(false);
- };
+ setShowModalDeleteItem(false)
+ }
const columns: ColumnDef[] = [
{
@@ -145,9 +148,9 @@ export const Components: FC = (): ReactElement => {
variant="primary"
size="sm"
onClick={(e) => {
- e.stopPropagation();
- setSelectedItem(row.original);
- setShowModalUpdateItem(true);
+ e.stopPropagation()
+ setSelectedItem(row.original)
+ setShowModalUpdateItem(true)
}}
className="flex items-center gap-2"
>
@@ -157,9 +160,9 @@ export const Components: FC = (): ReactElement => {
variant="danger"
size="sm"
onClick={(e) => {
- e.stopPropagation();
- setSelectedItem(row.original);
- setShowModalDeleteItem(true);
+ e.stopPropagation()
+ setSelectedItem(row.original)
+ setShowModalDeleteItem(true)
}}
className="flex items-center gap-2"
>
@@ -168,7 +171,7 @@ export const Components: FC = (): ReactElement => {
),
},
- ];
+ ]
const table = useReactTable({
data: items,
@@ -184,7 +187,7 @@ export const Components: FC = (): ReactElement => {
onPaginationChange: setPagination,
pageCount: Math.ceil(totalItems / pagination.pageSize),
manualPagination: true,
- });
+ })
return (
@@ -235,7 +238,7 @@ export const Components: FC = (): ReactElement => {
prevStep={prevStep}
resetStep={resetStep}
handleAddItem={handleAdd}
- onDataCapture={(data) => { pendingFormData.current = data; }}
+ onDataCapture={(data) => { pendingFormData.current = data }}
/>
{
resetStep={resetStep}
handleUpdateItem={handleUpdate}
initialValues={selectedItem ? { itemName: selectedItem.name } : undefined}
- onDataCapture={(data) => { pendingFormData.current = data; }}
+ onDataCapture={(data) => { pendingFormData.current = data }}
/>
{
handleDeleteItem={handleDelete}
/>
- );
-};
-
-export default Components;
+ )
+}
diff --git a/apps/backoffice/src/app/(protected)/hackathon-dashboard/page.tsx b/apps/backoffice/src/routes/_authenticated/hackathon-dashboard.tsx
similarity index 77%
rename from apps/backoffice/src/app/(protected)/hackathon-dashboard/page.tsx
rename to apps/backoffice/src/routes/_authenticated/hackathon-dashboard.tsx
index 36567ae..e3893dd 100644
--- a/apps/backoffice/src/app/(protected)/hackathon-dashboard/page.tsx
+++ b/apps/backoffice/src/routes/_authenticated/hackathon-dashboard.tsx
@@ -1,31 +1,36 @@
-import { BackofficeWrapper } from '@imphnen-frontend-service/ui/organisms';
-import { FC, ReactElement } from 'react';
-import { useQuery } from '@tanstack/react-query';
+import { createFileRoute } from '@tanstack/react-router'
+import { BackofficeWrapper } from '@imphnen-frontend-service/ui/organisms'
+import { FC, ReactElement } from 'react'
+import { useQuery } from '@tanstack/react-query'
import {
getAdminUsers,
getAdminTeams,
getAdminSubmissions,
-} from '@imphnen-frontend-service/service';
+} from '@imphnen-frontend-service/service'
-export const HackathonDashboardPage: FC = (): ReactElement => {
+export const Route = createFileRoute('/_authenticated/hackathon-dashboard')({
+ component: HackathonDashboardPage,
+})
+
+function HackathonDashboardPage() {
const { data: usersData } = useQuery({
queryKey: ['admin-users-count'],
queryFn: () => getAdminUsers({ page: 1, per_page: 1 }),
- });
+ })
const { data: teamsData } = useQuery({
queryKey: ['admin-teams-count'],
queryFn: () => getAdminTeams({ page: 1, per_page: 1 }),
- });
+ })
const { data: submissionsData } = useQuery({
queryKey: ['admin-submissions-count'],
queryFn: () => getAdminSubmissions({ page: 1, per_page: 1 }),
- });
+ })
- const totalParticipants = usersData?.meta?.total_data ?? '??';
- const totalTeams = teamsData?.meta?.total_data ?? '??';
- const totalSubmissions = submissionsData?.meta?.total_data ?? '??';
+ const totalParticipants = usersData?.meta?.total_data ?? '??'
+ const totalTeams = teamsData?.meta?.total_data ?? '??'
+ const totalSubmissions = submissionsData?.meta?.total_data ?? '??'
return (
@@ -52,7 +57,5 @@ export const HackathonDashboardPage: FC = (): ReactElement => {
- );
-};
-
-export default HackathonDashboardPage;
+ )
+}
diff --git a/apps/backoffice/src/app/(protected)/hackathon-submissions/page.tsx b/apps/backoffice/src/routes/_authenticated/hackathon-submissions.tsx
similarity index 70%
rename from apps/backoffice/src/app/(protected)/hackathon-submissions/page.tsx
rename to apps/backoffice/src/routes/_authenticated/hackathon-submissions.tsx
index df611be..c06cd8a 100644
--- a/apps/backoffice/src/app/(protected)/hackathon-submissions/page.tsx
+++ b/apps/backoffice/src/routes/_authenticated/hackathon-submissions.tsx
@@ -1,3 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
import {
FC,
ReactElement,
@@ -5,44 +6,52 @@ import {
useEffect,
useMemo,
useCallback,
-} from 'react';
-import SubmissionModal from './_components/submission-modal';
+} from 'react'
+import SubmissionModal from './_components/hackathon-submissions/submission-modal'
import {
BackofficeWrapper,
DataTable,
-} from '@imphnen-frontend-service/ui/organisms';
-import { ColumnDef } from '@tanstack/react-table';
-import { Button } from '@imphnen-frontend-service/ui/atoms';
-import { cn } from '@imphnen-frontend-service/utils';
+} from '@imphnen-frontend-service/ui/organisms'
+import { ColumnDef } from '@tanstack/react-table'
+import { Button } from '@imphnen-frontend-service/ui/atoms'
+import { cn } from '@imphnen-frontend-service/utils'
import {
SearchOutlined,
FilterOutlined,
LoadingOutlined,
EyeOutlined,
-} from '@ant-design/icons';
-import { useQuery } from '@tanstack/react-query';
+} from '@ant-design/icons'
+import { useQuery } from '@tanstack/react-query'
import {
getAdminSubmissions,
TAdminSubmissionItem,
-} from '@imphnen-frontend-service/service';
-import { useSearchParams } from 'react-router-dom';
+} from '@imphnen-frontend-service/service'
+import { useNavigate } from '@tanstack/react-router'
-type SubmissionType = TAdminSubmissionItem;
+type SubmissionType = TAdminSubmissionItem
-export const HackathonSubmissionsPage: FC = (): ReactElement => {
- const [searchParams, setSearchParams] = useSearchParams();
- const currentPage = Math.max(
- 1,
- parseInt(searchParams.get('page') || '1', 10)
- );
- const searchQuery = searchParams.get('search') || '';
- const perPage = parseInt(searchParams.get('per_page') || '10', 10);
- const statusFilter = searchParams.get('status') || 'all';
+export const Route = createFileRoute('/_authenticated/hackathon-submissions')({
+ component: HackathonSubmissionsPage,
+ validateSearch: (search: Record) => ({
+ page: Number(search.page) || 1,
+ search: (search.search as string) || '',
+ per_page: Number(search.per_page) || 10,
+ status: (search.status as string) || 'all',
+ }),
+})
- const [showSubmissionModal, setShowSubmissionModal] = useState(false);
+function HackathonSubmissionsPage() {
+ const searchParams = Route.useSearch()
+ const navigate = useNavigate()
+ const currentPage = Math.max(1, searchParams.page)
+ const searchQuery = searchParams.search || ''
+ const perPage = searchParams.per_page || 10
+ const statusFilter = searchParams.status || 'all'
+
+ const [showSubmissionModal, setShowSubmissionModal] = useState(false)
const [selectedSubmission, setSelectedSubmission] =
- useState(null);
- const [globalFilter, setGlobalFilter] = useState(searchQuery);
+ useState(null)
+ const [globalFilter, setGlobalFilter] = useState(searchQuery)
const {
data: submissionsResponse,
@@ -65,82 +74,86 @@ export const HackathonSubmissionsPage: FC = (): ReactElement => {
}),
staleTime: 30000,
gcTime: 5 * 60 * 1000,
- });
+ })
- const totalData = submissionsResponse?.meta?.total_data || 0;
- const totalPages = submissionsResponse?.meta?.total_page || 1;
+ const totalData = submissionsResponse?.meta?.total_data || 0
+ const totalPages = submissionsResponse?.meta?.total_page || 1
const handlePageChange = useCallback(
(newPage: number) => {
- const params = new URLSearchParams();
- params.set('page', newPage.toString());
- if (perPage !== 10) params.set('per_page', perPage.toString());
- if (searchQuery) params.set('search', searchQuery);
- if (statusFilter !== 'all') params.set('status', statusFilter);
- setSearchParams(params);
- window.scrollTo({ top: 0, behavior: 'smooth' });
+ navigate({
+ search: {
+ page: newPage,
+ per_page: perPage !== 10 ? perPage : undefined,
+ search: searchQuery || undefined,
+ status: statusFilter !== 'all' ? statusFilter : undefined,
+ } as any,
+ })
+ window.scrollTo({ top: 0, behavior: 'smooth' })
},
- [setSearchParams, perPage, searchQuery, statusFilter]
- );
+ [navigate, perPage, searchQuery, statusFilter]
+ )
useEffect(() => {
if (!isLoading && totalPages > 0 && currentPage > totalPages) {
- setSearchParams({ page: totalPages.toString() });
+ navigate({ search: { page: totalPages } as any })
}
- }, [currentPage, totalPages, setSearchParams, isLoading]);
+ }, [currentPage, totalPages, navigate, isLoading])
useEffect(() => {
- setGlobalFilter(searchQuery);
- }, [searchQuery]);
+ setGlobalFilter(searchQuery)
+ }, [searchQuery])
const handleSearch = useCallback(() => {
- const params = new URLSearchParams();
- params.set('page', '1');
- if (perPage !== 10) params.set('per_page', perPage.toString());
- if (globalFilter.trim()) {
- params.set('search', globalFilter.trim());
- }
- if (statusFilter !== 'all') params.set('status', statusFilter);
- setSearchParams(params);
- }, [globalFilter, setSearchParams, perPage, statusFilter]);
+ navigate({
+ search: {
+ page: 1,
+ per_page: perPage !== 10 ? perPage : undefined,
+ search: globalFilter.trim() || undefined,
+ status: statusFilter !== 'all' ? statusFilter : undefined,
+ } as any,
+ })
+ }, [globalFilter, navigate, perPage, statusFilter])
const handleSearchKeyPress = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
- handleSearch();
+ handleSearch()
}
},
[handleSearch]
- );
+ )
const handlePerPageChange = useCallback(
(newPerPage: number) => {
- const params = new URLSearchParams();
- params.set('page', '1');
- params.set('per_page', newPerPage.toString());
- if (searchQuery) params.set('search', searchQuery);
- if (statusFilter !== 'all') params.set('status', statusFilter);
- setSearchParams(params);
+ navigate({
+ search: {
+ page: 1,
+ per_page: newPerPage,
+ search: searchQuery || undefined,
+ status: statusFilter !== 'all' ? statusFilter : undefined,
+ } as any,
+ })
},
- [setSearchParams, searchQuery, statusFilter]
- );
+ [navigate, searchQuery, statusFilter]
+ )
const handleShowSubmissionModal = useCallback(
(submission: SubmissionType) => {
- setSelectedSubmission(submission);
- setShowSubmissionModal(true);
+ setSelectedSubmission(submission)
+ setShowSubmissionModal(true)
},
[]
- );
+ )
const handleCloseSubmissionModal = useCallback(() => {
- setShowSubmissionModal(false);
- setSelectedSubmission(null);
- }, []);
+ setShowSubmissionModal(false)
+ setSelectedSubmission(null)
+ }, [])
const filteredData = useMemo(() => {
- return submissionsResponse?.data || [];
- }, [submissionsResponse]);
+ return submissionsResponse?.data || []
+ }, [submissionsResponse])
const columns: ColumnDef[] = useMemo(
() => [
@@ -168,7 +181,7 @@ export const HackathonSubmissionsPage: FC = (): ReactElement => {
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
- const status = row.original.status;
+ const status = row.original.status
return (
{
>
{status.charAt(0).toUpperCase() + status.slice(1)}
- );
+ )
},
enableSorting: true,
},
@@ -220,7 +233,7 @@ export const HackathonSubmissionsPage: FC = (): ReactElement => {
},
],
[handleShowSubmissionModal]
- );
+ )
return (
@@ -308,7 +321,5 @@ export const HackathonSubmissionsPage: FC = (): ReactElement => {
/>
)}
- );
-};
-
-export default HackathonSubmissionsPage;
+ )
+}
diff --git a/apps/backoffice/src/app/(protected)/hackathon-teams/page.tsx b/apps/backoffice/src/routes/_authenticated/hackathon-teams.tsx
similarity index 76%
rename from apps/backoffice/src/app/(protected)/hackathon-teams/page.tsx
rename to apps/backoffice/src/routes/_authenticated/hackathon-teams.tsx
index 4a115b4..177e129 100644
--- a/apps/backoffice/src/app/(protected)/hackathon-teams/page.tsx
+++ b/apps/backoffice/src/routes/_authenticated/hackathon-teams.tsx
@@ -1,3 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
import {
FC,
ReactElement,
@@ -5,16 +6,16 @@ import {
useEffect,
useMemo,
useCallback,
-} from 'react';
-import ModalTeamDetail from './_components/modal-team-detail-new';
-import { CityFilterSelect } from '../../../components/city-filter-select';
+} from 'react'
+import ModalTeamDetail from './_components/hackathon-teams/modal-team-detail-new'
+import { CityFilterSelect } from '../../components/city-filter-select'
import {
BackofficeWrapper,
DataTable,
-} from '@imphnen-frontend-service/ui/organisms';
-import { ColumnDef } from '@tanstack/react-table';
-import { Button } from '@imphnen-frontend-service/ui/atoms';
-import { cn } from '@imphnen-frontend-service/utils';
+} from '@imphnen-frontend-service/ui/organisms'
+import { ColumnDef } from '@tanstack/react-table'
+import { Button } from '@imphnen-frontend-service/ui/atoms'
+import { cn } from '@imphnen-frontend-service/utils'
import {
EditOutlined,
TeamOutlined,
@@ -22,32 +23,39 @@ import {
FilterOutlined,
PlusOutlined,
LoadingOutlined,
-} from '@ant-design/icons';
-import { useQuery } from '@tanstack/react-query';
+} from '@ant-design/icons'
+import { useQuery } from '@tanstack/react-query'
import {
getAdminTeams,
TAdminTeamItem,
-} from '@imphnen-frontend-service/service';
-import { useSearchParams } from 'react-router-dom';
+} from '@imphnen-frontend-service/service'
+import { useNavigate } from '@tanstack/react-router'
-type TeamType = TAdminTeamItem;
+type TeamType = TAdminTeamItem
-export const HackathonTeamsPage: FC = (): ReactElement => {
- const [searchParams, setSearchParams] = useSearchParams();
- const currentPage = Math.max(
- 1,
- parseInt(searchParams.get('page') || '1', 10)
- );
- const searchQuery = searchParams.get('search') || '';
- const perPage = parseInt(searchParams.get('per_page') || '10', 10);
- const [showDetailModal, setShowDetailModal] = useState(false);
- const [showNewTeamModal, setShowNewTeamModal] = useState(false);
- const [selectedTeam, setSelectedTeam] = useState(null);
- useState(null);
- const [globalFilter, setGlobalFilter] = useState(searchQuery);
+export const Route = createFileRoute('/_authenticated/hackathon-teams')({
+ component: HackathonTeamsPage,
+ validateSearch: (search: Record) => ({
+ page: Number(search.page) || 1,
+ search: (search.search as string) || '',
+ per_page: Number(search.per_page) || 10,
+ }),
+})
- const [visibilityFilter, setVisibilityFilter] = useState('all');
- const [cityFilter, setCityFilter] = useState('all');
+function HackathonTeamsPage() {
+ const searchParams = Route.useSearch()
+ const navigate = useNavigate()
+ const currentPage = Math.max(1, searchParams.page)
+ const searchQuery = searchParams.search || ''
+ const perPage = searchParams.per_page || 10
+ const [showDetailModal, setShowDetailModal] = useState(false)
+ const [showNewTeamModal, setShowNewTeamModal] = useState(false)
+ const [selectedTeam, setSelectedTeam] = useState(null)
+ useState(null)
+ const [globalFilter, setGlobalFilter] = useState(searchQuery)
+
+ const [visibilityFilter, setVisibilityFilter] = useState('all')
+ const [cityFilter, setCityFilter] = useState('all')
const {
data: teamsResponse,
@@ -70,84 +78,88 @@ export const HackathonTeamsPage: FC = (): ReactElement => {
}),
staleTime: 30000,
gcTime: 5 * 60 * 1000,
- });
+ })
- const totalData = teamsResponse?.meta?.total_data || 0;
- const totalPages = teamsResponse?.meta?.total_page || 1;
+ const totalData = teamsResponse?.meta?.total_data || 0
+ const totalPages = teamsResponse?.meta?.total_page || 1
const handlePageChange = useCallback(
(newPage: number) => {
- const params = new URLSearchParams();
- params.set('page', newPage.toString());
- if (perPage !== 10) params.set('per_page', perPage.toString());
- if (searchQuery) params.set('search', searchQuery);
- setSearchParams(params);
- window.scrollTo({ top: 0, behavior: 'smooth' });
+ navigate({
+ search: {
+ page: newPage,
+ per_page: perPage !== 10 ? perPage : undefined,
+ search: searchQuery || undefined,
+ } as any,
+ })
+ window.scrollTo({ top: 0, behavior: 'smooth' })
},
- [setSearchParams, perPage, searchQuery]
- );
+ [navigate, perPage, searchQuery]
+ )
useEffect(() => {
if (!isLoading && totalPages > 0 && currentPage > totalPages) {
- setSearchParams({ page: totalPages.toString() });
+ navigate({ search: { page: totalPages } as any })
}
- }, [currentPage, totalPages, setSearchParams, isLoading]);
+ }, [currentPage, totalPages, navigate, isLoading])
useEffect(() => {
- setGlobalFilter(searchQuery);
- }, [searchQuery]);
+ setGlobalFilter(searchQuery)
+ }, [searchQuery])
const handleSearch = useCallback(() => {
- const params = new URLSearchParams();
- params.set('page', '1');
- if (perPage !== 10) params.set('per_page', perPage.toString());
- if (globalFilter.trim()) {
- params.set('search', globalFilter.trim());
- }
- setSearchParams(params);
- }, [globalFilter, setSearchParams, perPage]);
+ navigate({
+ search: {
+ page: 1,
+ per_page: perPage !== 10 ? perPage : undefined,
+ search: globalFilter.trim() || undefined,
+ } as any,
+ })
+ }, [globalFilter, navigate, perPage])
const handleSearchKeyPress = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
- handleSearch();
+ handleSearch()
}
},
[handleSearch]
- );
+ )
const handlePerPageChange = useCallback(
(newPerPage: number) => {
- const params = new URLSearchParams();
- params.set('page', '1');
- params.set('per_page', newPerPage.toString());
- if (searchQuery) params.set('search', searchQuery);
- setSearchParams(params);
+ navigate({
+ search: {
+ page: 1,
+ per_page: newPerPage,
+ search: searchQuery || undefined,
+ } as any,
+ })
},
- [setSearchParams, searchQuery]
- );
+ [navigate, searchQuery]
+ )
const handleShowDetailModal = useCallback((team: TeamType) => {
- setSelectedTeam(team);
- setShowDetailModal(true);
- }, []);
+ setSelectedTeam(team)
+ setShowDetailModal(true)
+ }, [])
const handleCloseDetailModal = useCallback(() => {
- setShowDetailModal(false);
- setSelectedTeam(null);
- }, []);
+ setShowDetailModal(false)
+ setSelectedTeam(null)
+ }, [])
const handleShowNewTeamModal = useCallback(() => {
- setShowNewTeamModal(true);
- }, []);
+ setShowNewTeamModal(true)
+ }, [])
const handleCloseNewTeamModal = useCallback(() => {
- setShowNewTeamModal(false);
- }, []);
+ setShowNewTeamModal(false)
+ }, [])
const filteredData = useMemo(() => {
- return teamsResponse?.data || [];
- }, [teamsResponse]);
+ return teamsResponse?.data || []
+ }, [teamsResponse])
const columns: ColumnDef[] = useMemo(
() => [
@@ -155,7 +167,7 @@ export const HackathonTeamsPage: FC = (): ReactElement => {
accessorKey: 'name',
header: 'Team',
cell: ({ row }) => {
- const team = row.original;
+ const team = row.original
return (
@@ -178,7 +190,7 @@ export const HackathonTeamsPage: FC = (): ReactElement => {
- );
+ )
},
enableSorting: true,
},
@@ -194,7 +206,7 @@ export const HackathonTeamsPage: FC = (): ReactElement => {
accessorKey: 'visibility',
header: 'Visibility',
cell: ({ row }) => {
- const isPublic = row.original.visibility === 'public';
+ const isPublic = row.original.visibility === 'public'
return (
{
>
{isPublic ? 'Public' : 'Private'}
- );
+ )
},
enableSorting: true,
},
@@ -256,7 +268,7 @@ export const HackathonTeamsPage: FC = (): ReactElement => {
},
],
[handleShowDetailModal]
- );
+ )
return (
@@ -347,9 +359,9 @@ export const HackathonTeamsPage: FC = (): ReactElement => {
variant="secondary"
size="sm"
onClick={() => {
- setVisibilityFilter('all');
- setCityFilter('all');
- setGlobalFilter('');
+ setVisibilityFilter('all')
+ setCityFilter('all')
+ setGlobalFilter('')
}}
className="text-sm text-neutral-600"
>
@@ -401,7 +413,5 @@ export const HackathonTeamsPage: FC = (): ReactElement => {
team={null}
/>
- );
-};
-
-export default HackathonTeamsPage;
+ )
+}
diff --git a/apps/backoffice/src/app/(protected)/hackathon-users/page.tsx b/apps/backoffice/src/routes/_authenticated/hackathon-users.tsx
similarity index 77%
rename from apps/backoffice/src/app/(protected)/hackathon-users/page.tsx
rename to apps/backoffice/src/routes/_authenticated/hackathon-users.tsx
index 551ade0..84b69ef 100644
--- a/apps/backoffice/src/app/(protected)/hackathon-users/page.tsx
+++ b/apps/backoffice/src/routes/_authenticated/hackathon-users.tsx
@@ -1,3 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
import {
FC,
ReactElement,
@@ -5,15 +6,15 @@ import {
useEffect,
useMemo,
useCallback,
-} from 'react';
-import ModalUserDetail from './_components/modal-user-detail';
+} from 'react'
+import ModalUserDetail from './_components/hackathon-users/modal-user-detail'
import {
BackofficeWrapper,
DataTable,
-} from '@imphnen-frontend-service/ui/organisms';
-import { ColumnDef } from '@tanstack/react-table';
-import { Button } from '@imphnen-frontend-service/ui/atoms';
-import { cn } from '@imphnen-frontend-service/utils';
+} from '@imphnen-frontend-service/ui/organisms'
+import { ColumnDef } from '@tanstack/react-table'
+import { Button } from '@imphnen-frontend-service/ui/atoms'
+import { cn } from '@imphnen-frontend-service/utils'
import {
EditOutlined,
UserOutlined,
@@ -21,16 +22,16 @@ import {
FilterOutlined,
PlusOutlined,
LoadingOutlined,
-} from '@ant-design/icons';
-import { CityFilterSelect } from '../../../components/city-filter-select';
-import { useQuery } from '@tanstack/react-query';
+} from '@ant-design/icons'
+import { CityFilterSelect } from '../../components/city-filter-select'
+import { useQuery } from '@tanstack/react-query'
import {
getAdminUsers,
TAdminUserItem,
-} from '@imphnen-frontend-service/service';
-import { useSearchParams } from 'react-router-dom';
+} from '@imphnen-frontend-service/service'
+import { useSearch, useNavigate } from '@tanstack/react-router'
-type UserType = TAdminUserItem;
+type UserType = TAdminUserItem
const skillsOptions = [
'Frontend Developer',
@@ -41,24 +42,31 @@ const skillsOptions = [
'Product Manager',
'Data Scientist',
'Mobile Developer',
-];
+]
-export const HackathonUsersPage: FC = (): ReactElement => {
- const [searchParams, setSearchParams] = useSearchParams();
- const currentPage = Math.max(
- 1,
- parseInt(searchParams.get('page') || '1', 10)
- );
- const searchQuery = searchParams.get('search') || '';
- const perPage = parseInt(searchParams.get('per_page') || '10', 10);
- const [showDetailModal, setShowDetailModal] = useState(false);
- const [showNewUserModal, setShowNewUserModal] = useState(false);
- const [selectedUser, setSelectedUser] = useState(null);
- const [globalFilter, setGlobalFilter] = useState(searchQuery);
+export const Route = createFileRoute('/_authenticated/hackathon-users')({
+ component: HackathonUsersPage,
+ validateSearch: (search: Record) => ({
+ page: Number(search.page) || 1,
+ search: (search.search as string) || '',
+ per_page: Number(search.per_page) || 10,
+ }),
+})
- const [statusFilter, setStatusFilter] = useState('all');
- const [cityFilter, setCityFilter] = useState('all');
- const [skillsFilter, setSkillsFilter] = useState([]);
+function HackathonUsersPage() {
+ const searchParams = Route.useSearch()
+ const navigate = useNavigate()
+ const currentPage = Math.max(1, searchParams.page)
+ const searchQuery = searchParams.search || ''
+ const perPage = searchParams.per_page || 10
+ const [showDetailModal, setShowDetailModal] = useState(false)
+ const [showNewUserModal, setShowNewUserModal] = useState(false)
+ const [selectedUser, setSelectedUser] = useState(null)
+ const [globalFilter, setGlobalFilter] = useState(searchQuery)
+
+ const [statusFilter, setStatusFilter] = useState('all')
+ const [cityFilter, setCityFilter] = useState('all')
+ const [skillsFilter, setSkillsFilter] = useState([])
const {
data: usersResponse,
@@ -81,100 +89,104 @@ export const HackathonUsersPage: FC = (): ReactElement => {
}),
staleTime: 30000,
gcTime: 5 * 60 * 1000,
- });
+ })
- const totalData = usersResponse?.meta?.total_data || 0;
- const totalPages = usersResponse?.meta?.total_page || 1;
+ const totalData = usersResponse?.meta?.total_data || 0
+ const totalPages = usersResponse?.meta?.total_page || 1
const handlePageChange = useCallback(
(newPage: number) => {
- const params = new URLSearchParams();
- params.set('page', newPage.toString());
- if (perPage !== 10) params.set('per_page', perPage.toString());
- if (searchQuery) params.set('search', searchQuery);
- setSearchParams(params);
- window.scrollTo({ top: 0, behavior: 'smooth' });
+ navigate({
+ search: {
+ page: newPage,
+ per_page: perPage !== 10 ? perPage : undefined,
+ search: searchQuery || undefined,
+ } as any,
+ })
+ window.scrollTo({ top: 0, behavior: 'smooth' })
},
- [setSearchParams, perPage, searchQuery]
- );
+ [navigate, perPage, searchQuery]
+ )
useEffect(() => {
if (!isLoading && totalPages > 0 && currentPage > totalPages) {
- setSearchParams({ page: totalPages.toString() });
+ navigate({ search: { page: totalPages } as any })
}
- }, [currentPage, totalPages, setSearchParams, isLoading]);
+ }, [currentPage, totalPages, navigate, isLoading])
useEffect(() => {
- setGlobalFilter(searchQuery);
- }, [searchQuery]);
+ setGlobalFilter(searchQuery)
+ }, [searchQuery])
const handleSearch = useCallback(() => {
- const params = new URLSearchParams();
- params.set('page', '1');
- if (perPage !== 10) params.set('per_page', perPage.toString());
- if (globalFilter.trim()) {
- params.set('search', globalFilter.trim());
- }
- setSearchParams(params);
- }, [globalFilter, setSearchParams, perPage]);
+ navigate({
+ search: {
+ page: 1,
+ per_page: perPage !== 10 ? perPage : undefined,
+ search: globalFilter.trim() || undefined,
+ } as any,
+ })
+ }, [globalFilter, navigate, perPage])
const handleSearchKeyPress = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
- handleSearch();
+ handleSearch()
}
},
[handleSearch]
- );
+ )
const handlePerPageChange = useCallback(
(newPerPage: number) => {
- const params = new URLSearchParams();
- params.set('page', '1');
- params.set('per_page', newPerPage.toString());
- if (searchQuery) params.set('search', searchQuery);
- setSearchParams(params);
+ navigate({
+ search: {
+ page: 1,
+ per_page: newPerPage,
+ search: searchQuery || undefined,
+ } as any,
+ })
},
- [setSearchParams, searchQuery]
- );
+ [navigate, searchQuery]
+ )
const handleShowDetailModal = useCallback((user: UserType) => {
- setSelectedUser(user);
- setShowDetailModal(true);
- }, []);
+ setSelectedUser(user)
+ setShowDetailModal(true)
+ }, [])
const handleCloseDetailModal = useCallback(() => {
- setShowDetailModal(false);
- setSelectedUser(null);
- }, []);
+ setShowDetailModal(false)
+ setSelectedUser(null)
+ }, [])
const handleShowNewUserModal = useCallback(() => {
- setShowNewUserModal(true);
- }, []);
+ setShowNewUserModal(true)
+ }, [])
const handleCloseNewUserModal = useCallback(() => {
- setShowNewUserModal(false);
- }, []);
+ setShowNewUserModal(false)
+ }, [])
const filteredData = useMemo(() => {
- const usersData = usersResponse?.data || [];
+ const usersData = usersResponse?.data || []
return usersData.filter((user: UserType) => {
if (statusFilter !== 'all') {
- const isActive = statusFilter === 'active';
- if (user.is_active !== isActive) return false;
+ const isActive = statusFilter === 'active'
+ if (user.is_active !== isActive) return false
}
if (skillsFilter.length > 0) {
- const userSkills = user.skills || [];
+ const userSkills = user.skills || []
const hasMatchingSkill = skillsFilter.some((skill) =>
userSkills.includes(skill)
- );
- if (!hasMatchingSkill) return false;
+ )
+ if (!hasMatchingSkill) return false
}
- return true;
- });
- }, [usersResponse, statusFilter, skillsFilter]);
+ return true
+ })
+ }, [usersResponse, statusFilter, skillsFilter])
const columns: ColumnDef[] = useMemo(
() => [
@@ -207,7 +219,7 @@ export const HackathonUsersPage: FC = (): ReactElement => {
accessorKey: 'skills',
header: 'Skills',
cell: ({ row }) => {
- const skills = row.original.skills || [];
+ const skills = row.original.skills || []
return (
{skills.length > 0 ? (
@@ -230,7 +242,7 @@ export const HackathonUsersPage: FC = (): ReactElement => {
-
)}
- );
+ )
},
enableSorting: false,
},
@@ -265,11 +277,11 @@ export const HackathonUsersPage: FC = (): ReactElement => {
),
enableSorting: true,
sortingFn: (rowA, rowB) => {
- const aActive = rowA.original.is_active;
- const bActive = rowB.original.is_active;
- if (aActive && !bActive) return -1;
- if (!aActive && bActive) return 1;
- return 0;
+ const aActive = rowA.original.is_active
+ const bActive = rowB.original.is_active
+ if (aActive && !bActive) return -1
+ if (!aActive && bActive) return 1
+ return 0
},
},
{
@@ -310,7 +322,7 @@ export const HackathonUsersPage: FC = (): ReactElement => {
},
],
[handleShowDetailModal]
- );
+ )
return (
@@ -424,10 +436,10 @@ export const HackathonUsersPage: FC = (): ReactElement => {
variant="secondary"
size="sm"
onClick={() => {
- setStatusFilter('all');
- setCityFilter('all');
- setSkillsFilter([]);
- setGlobalFilter('');
+ setStatusFilter('all')
+ setCityFilter('all')
+ setSkillsFilter([])
+ setGlobalFilter('')
}}
className="text-sm text-neutral-600"
>
@@ -479,7 +491,5 @@ export const HackathonUsersPage: FC = (): ReactElement => {
user={null}
/>
- );
-};
-
-export default HackathonUsersPage;
+ )
+}
diff --git a/apps/backoffice/src/app/(protected)/permissions/page.tsx b/apps/backoffice/src/routes/_authenticated/permissions.tsx
similarity index 81%
rename from apps/backoffice/src/app/(protected)/permissions/page.tsx
rename to apps/backoffice/src/routes/_authenticated/permissions.tsx
index d1d6e00..309eeea 100644
--- a/apps/backoffice/src/app/(protected)/permissions/page.tsx
+++ b/apps/backoffice/src/routes/_authenticated/permissions.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 ModalAddPermission from './_components/modal-add-permission';
-import ModalUpdatePermission from './_components/modal-update-permission';
-import ModalDeletePermission from './_components/modal-delete-permission';
-import { useQueryState } from '@imphnen-frontend-service/utils';
+} from '@tanstack/react-table'
+import ModalAddPermission from './_components/permissions/modal-add-permission'
+import ModalUpdatePermission from './_components/permissions/modal-update-permission'
+import ModalDeletePermission from './_components/permissions/modal-delete-permission'
+import { useQueryState } from '@imphnen-frontend-service/utils'
import {
usePermissionList,
useCreatePermission,
useUpdatePermission,
useDeletePermission,
TPermissionItem,
-} 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 [selectedItem, setSelectedItem] = useState(null);
- const [search, setSearch] = useState('');
- const pendingFormData = useRef(null);
+export const Route = createFileRoute('/_authenticated/permissions')({
+ component: PermissionsPage,
+})
+
+function PermissionsPage() {
+ const [showModalAddItem, setShowModalAddItem] = useState(false)
+ const [showModalUpdateItem, setShowModalUpdateItem] = useState(false)
+ const [showModalDeleteItem, setShowModalDeleteItem] = useState(false)
+ const [selectedItem, setSelectedItem] = 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: permissionsData, isLoading } = usePermissionList({
search,
page: pagination.pageIndex + 1,
per_page: pagination.pageSize,
- });
- const createPermission = useCreatePermission();
- const updatePermission = useUpdatePermission();
- const deletePermission = useDeletePermission();
+ })
+ const createPermission = useCreatePermission()
+ const updatePermission = useUpdatePermission()
+ const deletePermission = useDeletePermission()
- const permissions: TPermissionItem[] = permissionsData?.data ?? [];
- const totalItems = permissionsData?.meta?.total ?? permissions.length;
+ const permissions: TPermissionItem[] = permissionsData?.data ?? []
+ const totalItems = permissionsData?.meta?.total ?? permissions.length
const handleAdd = async (): Promise => {
if (pendingFormData.current) {
- await createPermission.mutateAsync(pendingFormData.current);
+ await createPermission.mutateAsync(pendingFormData.current)
}
- return true;
- };
+ return true
+ }
const handleUpdate = async (): Promise => {
if (selectedItem && pendingFormData.current) {
- await updatePermission.mutateAsync({ id: selectedItem.id, data: pendingFormData.current });
+ await updatePermission.mutateAsync({ id: selectedItem.id, data: pendingFormData.current })
}
- return true;
- };
+ return true
+ }
const handleDelete = async (): Promise => {
if (selectedItem) {
- await deletePermission.mutateAsync(selectedItem.id);
+ await deletePermission.mutateAsync(selectedItem.id)
}
- return true;
- };
+ return true
+ }
const columns: ColumnDef[] = [
{
@@ -123,9 +128,9 @@ export const Components: FC = (): ReactElement => {
variant="primary"
size="sm"
onClick={(e) => {
- e.stopPropagation();
- setSelectedItem(row.original);
- setShowModalUpdateItem(true);
+ e.stopPropagation()
+ setSelectedItem(row.original)
+ setShowModalUpdateItem(true)
}}
className="flex items-center gap-2"
>
@@ -135,9 +140,9 @@ export const Components: FC = (): ReactElement => {
variant="danger"
size="sm"
onClick={(e) => {
- e.stopPropagation();
- setSelectedItem(row.original);
- setShowModalDeleteItem(true);
+ e.stopPropagation()
+ setSelectedItem(row.original)
+ setShowModalDeleteItem(true)
}}
className="flex items-center gap-2"
>
@@ -146,7 +151,7 @@ export const Components: FC = (): ReactElement => {
),
},
- ];
+ ]
const table = useReactTable({
data: permissions,
@@ -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}
handleAddItem={handleAdd}
- onDataCapture={(data) => { pendingFormData.current = data; }}
+ onDataCapture={(data) => { pendingFormData.current = data }}
/>
{
resetStep={resetStep}
handleUpdate={handleUpdate}
initialValues={selectedItem ? { name: selectedItem.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)/prizes/page.tsx b/apps/backoffice/src/routes/_authenticated/prizes.tsx
similarity index 85%
rename from apps/backoffice/src/app/(protected)/prizes/page.tsx
rename to apps/backoffice/src/routes/_authenticated/prizes.tsx
index 7c3ebbb..9f18bd5 100644
--- a/apps/backoffice/src/app/(protected)/prizes/page.tsx
+++ b/apps/backoffice/src/routes/_authenticated/prizes.tsx
@@ -1,14 +1,13 @@
-import * as React from 'react';
-
-import { FC, Fragment, ReactElement, useState } from 'react';
+import { createFileRoute } from '@tanstack/react-router'
+import * as React from 'react'
+import { FC, Fragment, ReactElement, useState } from 'react'
import {
FilterOutlined,
SearchOutlined,
AuditOutlined,
-} 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,19 +15,19 @@ import {
PaginationState,
useReactTable,
RowSelectionState,
-} from '@tanstack/react-table';
-import ModalProcessDelivery from './_components/modal-process-item';
+} from '@tanstack/react-table'
+import ModalProcessDelivery from './_components/prizes/modal-process-item'
-type OrderValid = 'valid' | 'invalid' | 'unchecked';
-type Status = 'undelivered' | 'delivered';
+type OrderValid = 'valid' | 'invalid' | 'unchecked'
+type Status = 'undelivered' | 'delivered'
interface Prize {
- id: number;
- name: string;
- orderValid: OrderValid;
- items: string;
- address: string;
- status: Status;
+ id: number
+ name: string
+ orderValid: OrderValid
+ items: string
+ address: string
+ status: Status
}
const items = [
@@ -38,7 +37,7 @@ const items = [
'Sticker Isi 3',
'Sticker Isi 5',
'Gelang Karet',
-];
+]
const mockData: Prize[] = Array.from({ length: 90 }, (_, i) => ({
id: i + 1,
@@ -51,24 +50,28 @@ const mockData: Prize[] = Array.from({ length: 90 }, (_, i) => ({
items: items[i % items.length],
address: 'Jl. Pantai Cibaduyut Indah',
status: (i % 3 === 0 ? 'undelivered' : 'delivered') as Status,
-}));
+}))
-export const Components: FC = (): ReactElement => {
+export const Route = createFileRoute('/_authenticated/prizes')({
+ component: PrizesPage,
+})
+
+function PrizesPage() {
const [showModalProcessDelivery, setShowModalProcessDelivery] =
- useState(false);
+ useState(false)
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 deliveryOptions = [
{ id: 'option1', value: 'undelivered', label: 'Undelivered' },
{ id: 'option1', value: 'delivered', label: 'Delivered' },
- ];
+ ]
const columns: ColumnDef[] = [
{
@@ -102,24 +105,24 @@ export const Components: FC = (): ReactElement => {
header: 'Order Valid?',
accessorKey: 'orderValid',
cell: ({ row }) => {
- const status = row.original.orderValid;
+ const status = row.original.orderValid
const statusColors: Record = {
valid: 'bg-success-200 text-success-500',
invalid: 'bg-danger-200 text-danger-500',
unchecked: 'bg-warning-200 text-warning-900',
- };
+ }
const statusText: Record = {
valid: 'Valid',
invalid: 'Invalid',
unchecked: 'Unchecked',
- };
+ }
return (
{statusText[status]}
- );
+ )
},
},
{
@@ -134,22 +137,22 @@ export const Components: FC = (): ReactElement => {
header: 'Status',
accessorKey: 'status',
cell: ({ row }) => {
- const status = row.original.status;
+ const status = row.original.status
const statusColors: Record = {
delivered: 'bg-success-200 text-success-500',
undelivered: 'bg-danger-200 text-danger-500',
- };
+ }
const statusText: Record = {
delivered: 'Delivered',
undelivered: 'Undelivered',
- };
+ }
return (
{statusText[status]}
- );
+ )
},
},
{
@@ -159,8 +162,8 @@ export const Components: FC = (): ReactElement => {
variant="primary"
size="sm"
onClick={(e) => {
- e.stopPropagation();
- setShowModalProcessDelivery(true);
+ e.stopPropagation()
+ setShowModalProcessDelivery(true)
}}
className="flex items-center gap-2 w-full"
>
@@ -168,7 +171,7 @@ export const Components: FC = (): ReactElement => {
),
},
- ];
+ ]
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 }) => (
{
- e.stopPropagation();
+ e.stopPropagation()
}}
className="flex items-center gap-2"
>
@@ -109,7 +114,7 @@ export default function Components(): React.ReactElement {
variant="danger"
size="sm"
onClick={(e) => {
- e.stopPropagation();
+ e.stopPropagation()
}}
className="flex items-center gap-2"
>
@@ -134,7 +139,7 @@ export default function Components(): React.ReactElement {
onPaginationChange: setPagination,
pageCount: Math.ceil(mockData.length / pagination.pageSize),
manualPagination: false,
- });
+ })
return (
diff --git a/apps/backoffice/src/app/(protected)/roles/page.tsx b/apps/backoffice/src/routes/_authenticated/roles.tsx
similarity index 80%
rename from apps/backoffice/src/app/(protected)/roles/page.tsx
rename to apps/backoffice/src/routes/_authenticated/roles.tsx
index 058f238..c4efd63 100644
--- a/apps/backoffice/src/app/(protected)/roles/page.tsx
+++ b/apps/backoffice/src/routes/_authenticated/roles.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 ModalAddRole from './_components/modal-add-role';
-import ModalUpdateRole from './_components/modal-update-role';
-import ModalDeleteRole from './_components/modal-delete-role';
-import { useQueryState } from '@imphnen-frontend-service/utils';
+} from '@tanstack/react-table'
+import ModalAddRole from './_components/roles/modal-add-role'
+import ModalUpdateRole from './_components/roles/modal-update-role'
+import ModalDeleteRole from './_components/roles/modal-delete-role'
+import { useQueryState } from '@imphnen-frontend-service/utils'
import {
useRoleList,
useCreateRole,
useUpdateRole,
useDeleteRole,
TRolesListItem,
-} 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 [selectedRole, setSelectedRole] = useState(null);
- const [search, setSearch] = useState('');
- const pendingFormData = useRef(null);
+export const Route = createFileRoute('/_authenticated/roles')({
+ component: RolesPage,
+})
+
+function RolesPage() {
+ const [showModalAddItem, setShowModalAddItem] = useState(false)
+ const [showModalUpdateItem, setShowModalUpdateItem] = useState(false)
+ const [showModalDeleteItem, setShowModalDeleteItem] = useState(false)
+ const [selectedRole, setSelectedRole] = 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: rolesData, isLoading } = useRoleList({
search,
page: pagination.pageIndex + 1,
per_page: pagination.pageSize,
- });
- const createRole = useCreateRole();
- const updateRole = useUpdateRole();
- const deleteRole = useDeleteRole();
+ })
+ const createRole = useCreateRole()
+ const updateRole = useUpdateRole()
+ const deleteRole = useDeleteRole()
- const roles: TRolesListItem[] = rolesData?.data ?? [];
- const totalItems = rolesData?.meta?.total ?? roles.length;
+ const roles: TRolesListItem[] = rolesData?.data ?? []
+ const totalItems = rolesData?.meta?.total ?? roles.length
const handleAdd = async (): Promise => {
if (pendingFormData.current) {
- await createRole.mutateAsync(pendingFormData.current);
+ await createRole.mutateAsync(pendingFormData.current)
}
- return true;
- };
+ return true
+ }
const handleUpdate = async (): Promise => {
if (selectedRole && pendingFormData.current) {
- await updateRole.mutateAsync({ id: selectedRole.id, data: pendingFormData.current });
+ await updateRole.mutateAsync({ id: selectedRole.id, data: pendingFormData.current })
}
- return true;
- };
+ return true
+ }
const handleDelete = async (): Promise => {
if (selectedRole) {
- await deleteRole.mutateAsync(selectedRole.id);
+ await deleteRole.mutateAsync(selectedRole.id)
}
- return true;
- };
+ return true
+ }
const columns: ColumnDef[] = [
{
@@ -123,9 +128,9 @@ export const Components: FC = (): ReactElement => {
variant="primary"
size="sm"
onClick={(e) => {
- e.stopPropagation();
- setSelectedRole(row.original);
- setShowModalUpdateItem(true);
+ e.stopPropagation()
+ setSelectedRole(row.original)
+ setShowModalUpdateItem(true)
}}
className="flex items-center gap-2"
>
@@ -135,9 +140,9 @@ export const Components: FC = (): ReactElement => {
variant="danger"
size="sm"
onClick={(e) => {
- e.stopPropagation();
- setSelectedRole(row.original);
- setShowModalDeleteItem(true);
+ e.stopPropagation()
+ setSelectedRole(row.original)
+ setShowModalDeleteItem(true)
}}
className="flex items-center gap-2"
>
@@ -146,7 +151,7 @@ export const Components: FC = (): ReactElement => {
),
},
- ];
+ ]
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: () => (
{
- e.stopPropagation();
- setOpenDetail(true);
+ e.stopPropagation()
+ setOpenDetail(true)
}}
className="flex items-center gap-2 w-max"
>
@@ -121,7 +126,7 @@ export default function Components(): ReactElement {
onPaginationChange: setPagination,
pageCount: Math.ceil(totalItems / pagination.pageSize),
manualPagination: true,
- });
+ })
return (
diff --git a/apps/backoffice/src/app/(protected)/settings-dimentorin/page.tsx b/apps/backoffice/src/routes/_authenticated/settings-dimentorin.tsx
similarity index 52%
rename from apps/backoffice/src/app/(protected)/settings-dimentorin/page.tsx
rename to apps/backoffice/src/routes/_authenticated/settings-dimentorin.tsx
index 7699fe9..af027c3 100644
--- a/apps/backoffice/src/app/(protected)/settings-dimentorin/page.tsx
+++ b/apps/backoffice/src/routes/_authenticated/settings-dimentorin.tsx
@@ -1,22 +1,27 @@
-import { BackofficeWrapper } from "@imphnen-frontend-service/ui/organisms"
-import { cn, For } from "@imphnen-frontend-service/utils"
-import { useState } from "react"
-import { GeneralSettings } from "./_components/general"
-import { UserRolesPermission } from "./_components/user-roles-permission"
-import { NotificationSettings } from "./_components/notification"
-import { SecuritySettings } from "./_components/security"
-import { PaymentSettings } from "./_components/payment"
+import { createFileRoute } from '@tanstack/react-router'
+import { BackofficeWrapper } from '@imphnen-frontend-service/ui/organisms'
+import { cn, For } from '@imphnen-frontend-service/utils'
+import { useState } from 'react'
+import { GeneralSettings } from './_components/settings-dimentorin/general'
+import { UserRolesPermission } from './_components/settings-dimentorin/user-roles-permission'
+import { NotificationSettings } from './_components/settings-dimentorin/notification'
+import { SecuritySettings } from './_components/settings-dimentorin/security'
+import { PaymentSettings } from './_components/settings-dimentorin/payment'
const TABS = {
- general: "General Settings",
- userRolePermissions: "User Roles & Permissions",
- notification: "Notification Settings",
- security: "Security",
- payment: "Payment",
+ general: 'General Settings',
+ userRolePermissions: 'User Roles & Permissions',
+ notification: 'Notification Settings',
+ security: 'Security',
+ payment: 'Payment',
} as const
type Tabs = typeof TABS[keyof typeof TABS]
-export default function Components(): React.ReactElement {
+export const Route = createFileRoute('/_authenticated/settings-dimentorin')({
+ component: SettingsDimentorinPage,
+})
+
+function SettingsDimentorinPage(): React.ReactElement {
const [activeTab, setActiveTab] = useState(TABS.general)
return (
@@ -30,8 +35,8 @@ export default function Components(): React.ReactElement {
setActiveTab(tab)}
>
diff --git a/apps/backoffice/src/app/(protected)/transactions/page.tsx b/apps/backoffice/src/routes/_authenticated/transactions.tsx
similarity index 83%
rename from apps/backoffice/src/app/(protected)/transactions/page.tsx
rename to apps/backoffice/src/routes/_authenticated/transactions.tsx
index 6ea9aa0..d943aee 100644
--- a/apps/backoffice/src/app/(protected)/transactions/page.tsx
+++ b/apps/backoffice/src/routes/_authenticated/transactions.tsx
@@ -1,13 +1,13 @@
-import * as React from 'react';
-import { FC, Fragment, ReactElement, useState } from 'react';
+import { createFileRoute } from '@tanstack/react-router'
+import * as React from 'react'
+import { FC, Fragment, ReactElement, useState } from 'react'
import {
FilterOutlined,
SearchOutlined,
AuditOutlined,
-} 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,
@@ -15,16 +15,16 @@ import {
PaginationState,
useReactTable,
RowSelectionState,
-} from '@tanstack/react-table';
-import ModalValidate from './_components/modal-validate';
+} from '@tanstack/react-table'
+import ModalValidate from './_components/transactions/modal-validate'
-type TransactionStatus = 'valid' | 'invalid' | 'unchecked';
+type TransactionStatus = 'valid' | 'invalid' | 'unchecked'
interface Transaction {
- id: number;
- name: string;
- transactionNumber: string;
- status: TransactionStatus;
+ id: number
+ name: string
+ transactionNumber: string
+ status: TransactionStatus
}
const mockTransactions: Transaction[] = Array.from({ length: 20 }, (_, i) => ({
@@ -36,24 +36,28 @@ const mockTransactions: Transaction[] = Array.from({ length: 20 }, (_, i) => ({
: i % 5 === 0
? 'unchecked'
: 'valid') as TransactionStatus,
-}));
+}))
-export const Components: FC = (): ReactElement => {
- const [showModalValidate, setShowModalValidate] = useState(false);
+export const Route = createFileRoute('/_authenticated/transactions')({
+ component: TransactionsPage,
+})
+
+function TransactionsPage() {
+ const [showModalValidate, setShowModalValidate] = useState(false)
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 validationOptions = [
{ id: 'option1', value: 'unchecked', label: 'Unchecked' },
{ id: 'option2', value: 'valid', label: 'Valid' },
{ id: 'option3', value: 'invalid', label: 'Invalid' },
- ];
+ ]
const columns: ColumnDef[] = [
{
@@ -91,24 +95,24 @@ export const Components: FC = (): ReactElement => {
header: 'Order Valid?',
accessorKey: 'status',
cell: ({ row }) => {
- const status = row.original.status;
+ const status = row.original.status
const statusColors: Record = {
valid: 'bg-success-200 text-success-500',
invalid: 'bg-danger-200 text-danger-500',
unchecked: 'bg-warning-200 text-warning-900',
- };
+ }
const statusText: Record = {
valid: 'Valid',
invalid: 'Invalid',
unchecked: 'Unchecked',
- };
+ }
return (
{statusText[status]}
- );
+ )
},
},
{
@@ -118,8 +122,8 @@ export const Components: FC = (): ReactElement => {
variant="primary"
size="sm"
onClick={(e) => {
- e.stopPropagation();
- setShowModalValidate(true);
+ e.stopPropagation()
+ setShowModalValidate(true)
}}
className="flex items-center gap-2 w-full"
>
@@ -127,7 +131,7 @@ export const Components: FC = (): ReactElement => {
),
},
- ];
+ ]
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}
{ clearSession(); navigate('/auth/login'); }}
+ onClick={() => { clearSession(); navigate({ to: '/auth/login' }) }}
className="text-sm text-red-500 hover:text-red-600 cursor-pointer flex items-center gap-1"
>
@@ -62,5 +61,5 @@ export default function DashboardLayout() {
- );
+ )
}
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!
Summon Password Baru!
- );
-};
-
-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 (
- navigate('/')} className="cursor-pointer text-primary-500 hover:text-primary-600 text-base font-sans flex items-center">
+
Back to Homepage
-
+
@@ -87,7 +91,7 @@ export default function LoginPage() {
Password
- 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 => {
Other
{
/>
{
- );
-};
-
-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}
- );
-};
-
-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:
-
-
-
- Go to{' '}
-
- GitHub Email Settings
-
-
- Uncheck "Keep my email addresses private"
-
- Or go to{' '}
-
- Profile Settings
- {' '}
- and set a public email
-
- Try signing in with GitHub again
-
-
- 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.
-
-
-
-
- Back to Login
-
-
-
-
setEmailSent(false)}
- className="w-full py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors"
- >
- Send another email
-
-
-
-
- );
- }
-
- return (
-
-
-
- navigate('/auth/login')}
- className="cursor-pointer text-primary-500 hover:text-primary-600 dark:text-primary-400 dark:hover:text-primary-300 text-base font-sans flex items-center"
- >
-
- Back to Login
-
-
-
-
-
- Forgot Password?
-
-
- No worries, we'll send you reset instructions
-
-
-
-
-
-
- );
-}
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
-
-
-
-
-
-
- );
-}
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.
-
-
-
-
- Go to Login
-
-
-
-
navigate('/')}
- className="w-full py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors cursor-pointer"
- >
- Back to Homepage
-
-
-
-
- );
- }
-
- 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.
-
-
-
-
-
- Go to Login
-
-
-
-
setRegistrationSuccess(false)}
- className="w-full py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors cursor-pointer"
- >
- Register with different 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 (
-
-
-
- navigate('/')}
- className="cursor-pointer text-primary-500 hover:text-primary-600 dark:text-primary-400 dark:hover:text-primary-300 text-base font-sans flex items-center"
- >
-
- Back to Homepage
-
-
-
-
-
-
- Create Account
-
-
- Join the hackathon community
-
-
-
- {error && (
-
- )}
-
-
-
-
-
-
-
-
- {isGithubLoading ? 'Connecting...' : 'Sign up with GitHub'}
-
-
-
-
- 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)} />
-
-
-
-
-
setSidebarOpen(true)}
- className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
- >
-
-
-
-
-
- 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 (
-
- );
- }
-
- 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
-
-
-
-
-
-
-
- ) : null;
-};
-
-export default ProfilePage;
diff --git a/apps/hackathon/src/app/teams/[teamId]/layout.tsx b/apps/hackathon/src/app/teams/[teamId]/layout.tsx
deleted file mode 100644
index 1ef7f2a..0000000
--- a/apps/hackathon/src/app/teams/[teamId]/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 TeamDetailLayout: FC = (): ReactElement => {
- const [sidebarOpen, setSidebarOpen] = useState(false);
-
- return (
-
-
setSidebarOpen(false)} />
-
-
-
-
-
setSidebarOpen(true)}
- className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
- >
-
-
-
-
-
- Hackathon
-
-
-
-
-
-
-
-
- );
-};
-
-export default TeamDetailLayout;
diff --git a/apps/hackathon/src/app/teams/layout.tsx b/apps/hackathon/src/app/teams/layout.tsx
deleted file mode 100644
index 92950bc..0000000
--- a/apps/hackathon/src/app/teams/layout.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { FC, ReactElement, useState } from 'react';
-import { Outlet } from 'react-router';
-import { Sidebar } from '../../components/sidebar';
-
-const TeamsLayout: FC = (): ReactElement => {
- const [sidebarOpen, setSidebarOpen] = useState(false);
-
- return (
-
-
setSidebarOpen(false)} />
-
-
-
-
-
-
setSidebarOpen(true)}
- className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
- >
-
-
-
-
-
- Hackathon
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default TeamsLayout;
diff --git a/apps/hackathon/src/app/users/[userId]/layout.tsx b/apps/hackathon/src/app/users/[userId]/layout.tsx
deleted file mode 100644
index 049b0b2..0000000
--- a/apps/hackathon/src/app/users/[userId]/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 UserDetailLayout: FC = (): ReactElement => {
- const [sidebarOpen, setSidebarOpen] = useState(false);
-
- return (
-
-
setSidebarOpen(false)} />
-
-
-
-
-
setSidebarOpen(true)}
- className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
- >
-
-
-
-
-
- Hackathon
-
-
-
-
-
-
-
-
- );
-};
-
-export default UserDetailLayout;
diff --git a/apps/hackathon/src/main.tsx b/apps/hackathon/src/main.tsx
index e1f1205..352d760 100644
--- a/apps/hackathon/src/main.tsx
+++ b/apps/hackathon/src/main.tsx
@@ -1,35 +1,26 @@
-import { createRoot } from 'react-dom/client';
-import { StrictMode } from 'react';
-import { createBrowserRouter, RouteObject, RouterProvider } from 'react-router';
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import { RouterProvider, createRouter } from '@tanstack/react-router'
import {
- add404PageToRoutesChildren,
- addErrorElementToRoutes,
- convertPagesToRoute,
ModalLoginProvider,
QueryProvider,
-} from '@imphnen-frontend-service/utils';
-import { Toaster } from 'sonner';
-import { ThemeProvider } from './components/theme-provider';
-import './index.css';
+} from '@imphnen-frontend-service/utils'
+import { Toaster } from 'sonner'
+import { ThemeProvider } from './components/theme-provider'
+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,
- },
-]);
+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,4 +33,4 @@ createRoot(rootElement).render(
-);
+)
diff --git a/apps/hackathon/src/middleware.ts b/apps/hackathon/src/middleware.ts
deleted file mode 100644
index b075a92..0000000
--- a/apps/hackathon/src/middleware.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import { SessionUser } from '@imphnen-frontend-service/service';
-import { hackathonApi, SessionToken } from '@imphnen-frontend-service/service';
-import { LoaderFunctionArgs, redirect } from 'react-router';
-
-const mappingPublicRoutes = [
- '/',
-];
-
-const mappingOnboardingRoutes = [
- '/onboarding/user',
-];
-
-const mappingRoutePermissions = [
- {
- path: '/dashboard',
- permissions: [],
- },
- {
- path: '/teams/browse',
- permissions: [],
- },
- {
- path: '/teams/create',
- permissions: [],
- },
-];
-
-const mappingPublicPrefixRoutes = [
- '/hackathons',
-];
-
-const onboardingCache = new Map();
-const CACHE_DURATION = 5000;
-
-export const middleware = async ({ request }: LoaderFunctionArgs) => {
- const url = new URL(request.url);
- const pathname = url.pathname;
-
- const tokenData = SessionToken.get();
- const user = SessionUser.get();
- const isAuthenticated = !!tokenData?.token?.access_token;
-
- if (mappingPublicPrefixRoutes.some((prefix) => pathname.startsWith(prefix))) {
- return null;
- }
-
- if (mappingPublicRoutes.includes(pathname)) {
- return null;
- }
-
- if (pathname === '/auth/callback') {
- return null;
- }
-
- if (pathname.startsWith('/auth')) {
- if (isAuthenticated) return redirect('/dashboard');
- return null;
- }
-
- if (!isAuthenticated) {
- return redirect('/auth/login');
- }
-
- if (!mappingOnboardingRoutes.includes(pathname)) {
- try {
- const userId = user?.id;
- if (!userId) {
- return redirect('/auth/login');
- }
-
- const now = Date.now();
-
- const cached = onboardingCache.get(userId);
- let hasLocation = false;
-
- if (cached && (now - cached.timestamp) < CACHE_DURATION) {
- hasLocation = cached.hasLocation;
- } else {
- if (user?.location) {
- hasLocation = true;
- } else {
- try {
- const response = await hackathonApi.get('/users/me');
- hasLocation = !!response.data?.data?.location;
- } catch {
- hasLocation = !!user?.location;
- }
- }
-
- onboardingCache.set(userId, { hasLocation, timestamp: now });
- }
-
- if (!hasLocation) {
- return redirect('/onboarding/user');
- }
- } catch (error) {
- console.error('[Middleware] Unexpected error checking onboarding:', error);
- return null;
- }
- }
-
- const userPermissions =
- user?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
-
- const matchedRoute = mappingRoutePermissions.find(
- (route) => route.path === pathname
- );
-
- if (matchedRoute) {
- const hasPermission =
- !matchedRoute.permissions ||
- matchedRoute.permissions.some((perm) => userPermissions.includes(perm));
-
- if (!hasPermission) {
- return redirect('/dashboard');
- }
- }
-
- return null;
-};
diff --git a/apps/hackathon/src/routeTree.gen.ts b/apps/hackathon/src/routeTree.gen.ts
new file mode 100644
index 0000000..42aaf68
--- /dev/null
+++ b/apps/hackathon/src/routeTree.gen.ts
@@ -0,0 +1,3 @@
+// This file is auto-generated by TanStack Router
+
+export const routeTree = {} as any
diff --git a/apps/hackathon/src/routes/__root.tsx b/apps/hackathon/src/routes/__root.tsx
new file mode 100644
index 0000000..f463b79
--- /dev/null
+++ b/apps/hackathon/src/routes/__root.tsx
@@ -0,0 +1,5 @@
+import { createRootRoute, Outlet } from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+ component: () => ,
+})
diff --git a/apps/hackathon/src/routes/_authenticated.tsx b/apps/hackathon/src/routes/_authenticated.tsx
new file mode 100644
index 0000000..7e0eaf9
--- /dev/null
+++ b/apps/hackathon/src/routes/_authenticated.tsx
@@ -0,0 +1,92 @@
+import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
+import { SessionToken, SessionUser, hackathonApi } from '@imphnen-frontend-service/service'
+import { useState } from 'react'
+import { Sidebar } from '../components/sidebar'
+
+const onboardingCache = new Map()
+const CACHE_DURATION = 5000
+
+export const Route = createFileRoute('/_authenticated')({
+ beforeLoad: async ({ location }) => {
+ const session = SessionToken.get()
+ if (!session?.token?.access_token) {
+ throw redirect({ to: '/auth/login' })
+ }
+
+ const user = SessionUser.get()
+ const pathname = location.pathname
+
+ if (!pathname.startsWith('/onboarding')) {
+ const userId = user?.id
+ if (!userId) {
+ throw redirect({ to: '/auth/login' })
+ }
+
+ const now = Date.now()
+ const cached = onboardingCache.get(userId)
+ let hasLocation = false
+
+ if (cached && now - cached.timestamp < CACHE_DURATION) {
+ hasLocation = cached.hasLocation
+ } else {
+ if (user?.location) {
+ hasLocation = true
+ } else {
+ try {
+ const response = await hackathonApi.get('/users/me')
+ hasLocation = !!response.data?.data?.location
+ } catch {
+ hasLocation = !!user?.location
+ }
+ }
+
+ onboardingCache.set(userId, { hasLocation, timestamp: now })
+ }
+
+ if (!hasLocation) {
+ throw redirect({ to: '/onboarding/user' })
+ }
+ }
+ },
+ component: AuthenticatedLayout,
+})
+
+function AuthenticatedLayout() {
+ const [sidebarOpen, setSidebarOpen] = useState(false)
+
+ return (
+
+
setSidebarOpen(false)} />
+
+
+
+
setSidebarOpen(true)}
+ className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
+ >
+
+
+
+
+
+ Hackathon
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/hackathon/src/routes/_authenticated/_components/profile-modal.tsx b/apps/hackathon/src/routes/_authenticated/_components/profile-modal.tsx
new file mode 100644
index 0000000..e64475d
--- /dev/null
+++ b/apps/hackathon/src/routes/_authenticated/_components/profile-modal.tsx
@@ -0,0 +1,243 @@
+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 ? (
+
+ ) : (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ Click the camera icon to change your photo Format: JPG, PNG. Max 2MB
+
+
+
+
+
+
+ City
+ (
+
+ )} />
+
+
+
+
Role / Skills
+
(
+
+
+ {ROLE_OPTIONS.map((role) => (
+
+ { const newValue = e.target.checked ? [...(field.value || []), role] : (field.value || []).filter((v) => v !== role); field.onChange(newValue); }} className="rounded border-gray-300 dark:border-neutral-600 text-blue-600 focus:ring-blue-500 dark:bg-gray-800" />
+ {role}
+
+ ))}
+
+
+ )} />
+
+
+
+
Bio (Optional)
+
(
+
+
+ {fieldState.error &&
{fieldState.error.message}
}
+
+ )} />
+
+
+
+
Cancel
+
+ {isLoading ? (
+
+
+
+
+
+ Saving...
+
+ ) : 'Save'}
+
+
+
+
+
+
+ ) : null;
+};
+
+export default ProfilePage;
diff --git a/apps/hackathon/src/app/dashboard/page.tsx b/apps/hackathon/src/routes/_authenticated/dashboard.tsx
similarity index 98%
rename from apps/hackathon/src/app/dashboard/page.tsx
rename to apps/hackathon/src/routes/_authenticated/dashboard.tsx
index 592809a..b1bdc3e 100644
--- a/apps/hackathon/src/app/dashboard/page.tsx
+++ b/apps/hackathon/src/routes/_authenticated/dashboard.tsx
@@ -1,18 +1,21 @@
import { FC, ReactElement, useEffect, useMemo, useState } from 'react';
-import { Link, useNavigate } from 'react-router';
+import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
import {
useMyTeams,
useMyInvitations,
useRespondToInvitation,
useAuthStore,
- useWinners,
-} from '@imphnen-frontend-service/service';
+ useWinners } from '@imphnen-frontend-service/service';
import { toast } from 'sonner';
import { Button } from '@imphnen-frontend-service/ui/atoms';
import { Icon } from '@iconify/react';
-import ProfilePage from '../profile/page';
+import ProfilePage from './_components/profile-modal';
import { encodeWinnerCertificateId } from '../../utils/certificate';
+export const Route = createFileRoute('/_authenticated/dashboard')({
+ component: DashboardPage,
+})
+
const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z');
const SUBMISSION_DEADLINE = new Date('2025-12-07T16:59:00Z');
@@ -159,7 +162,7 @@ const DashboardPage: FC = (): ReactElement => {
const team = myTeams[0] as { id?: string } | null | undefined;
if (!team?.id) return;
const certId = await encodeWinnerCertificateId(team.id);
- navigate(`/certificate/winner/${encodeURIComponent(certId)}`);
+ navigate({ to: `/certificate/winner/${encodeURIComponent(certId)}` });
}}
className="shrink-0 px-6 py-2 bg-amber-600 hover:bg-amber-700 dark:bg-amber-600 dark:hover:bg-amber-700 text-white font-medium rounded-lg transition-colors cursor-pointer"
>
@@ -475,4 +478,3 @@ const DashboardPage: FC = (): ReactElement => {
);
};
-export default DashboardPage;
diff --git a/apps/hackathon/src/app/onboarding/user/page.tsx b/apps/hackathon/src/routes/_authenticated/onboarding/user.tsx
similarity index 97%
rename from apps/hackathon/src/app/onboarding/user/page.tsx
rename to apps/hackathon/src/routes/_authenticated/onboarding/user.tsx
index 79e19cc..50d0bcf 100644
--- a/apps/hackathon/src/app/onboarding/user/page.tsx
+++ b/apps/hackathon/src/routes/_authenticated/onboarding/user.tsx
@@ -1,7 +1,7 @@
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 { useNavigate } from 'react-router';
+import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { useForm, Controller } from 'react-hook-form';
import {
userOnboardingSchema,
@@ -9,14 +9,17 @@ import {
useUpdateUserMe,
useUploadAvatar,
useUserMe,
- useAuthStore,
-} from '@imphnen-frontend-service/service';
+ 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';
+export const Route = createFileRoute('/_authenticated/onboarding/user')({
+ component: UserOnboardingPage,
+})
+
const ROLE_OPTIONS = [
'Frontend Developer',
'Backend Developer',
@@ -267,4 +270,3 @@ const UserOnboardingPage: FC = (): ReactElement => {
);
};
-export default UserOnboardingPage;
diff --git a/apps/hackathon/src/app/teams/[teamId]/page.tsx b/apps/hackathon/src/routes/_authenticated/teams/$teamId.tsx
similarity index 98%
rename from apps/hackathon/src/app/teams/[teamId]/page.tsx
rename to apps/hackathon/src/routes/_authenticated/teams/$teamId.tsx
index 264a1ea..b18b250 100644
--- a/apps/hackathon/src/app/teams/[teamId]/page.tsx
+++ b/apps/hackathon/src/routes/_authenticated/teams/$teamId.tsx
@@ -1,6 +1,6 @@
import { FC, ReactElement, useState, useEffect } from 'react';
import { Button } from '@imphnen-frontend-service/ui/atoms';
-import { Link, useParams, useNavigate } from 'react-router';
+import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
import {
useTeamById,
useTeamMembers,
@@ -10,11 +10,13 @@ import {
useLeaveTeam,
useDeleteTeam,
ETeamMemberRole,
- useAuthStore,
-} from '@imphnen-frontend-service/service';
+ useAuthStore } from '@imphnen-frontend-service/service';
import { toast } from 'sonner';
import { Icon } from '@iconify/react';
+export const Route = createFileRoute('/_authenticated/teams/$teamId')({
+ component: TeamDashboardPage })
+
const MAX_TEAM_MEMBERS = 5;
const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z');
@@ -50,7 +52,7 @@ const ImageWithLoader: FC<{
};
const TeamDashboardPage: FC = (): ReactElement => {
- const { teamId } = useParams<{ teamId: string }>();
+ const { teamId } = Route.useParams();
const navigate = useNavigate();
const { session } = useAuthStore();
@@ -153,7 +155,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
try {
await leaveTeam(teamId);
toast.success('You have left the team');
- navigate('/dashboard');
+ navigate({ to: '/dashboard' });
} catch (error) {
console.error('Failed to leave team:', error);
toast.error('Failed to leave team');
@@ -166,7 +168,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
try {
await deleteTeam(teamId);
toast.success('Team deleted successfully');
- navigate('/dashboard');
+ navigate({ to: '/dashboard' });
} catch (error: any) {
console.error('Failed to delete team:', error);
toast.error(error?.message || 'Failed to delete team');
@@ -242,7 +244,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
Team not found
- navigate('/dashboard')}>
+ navigate({ to: '/dashboard' })}>
Back to Dashboard
@@ -525,7 +527,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
{team.leader && (
navigate(`/users/${team.leader.id}`)}
+ onClick={() => navigate({ to: `/users/${team.leader.id}` })}
className="w-full flex items-center space-x-3 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg p-2 transition-colors text-left cursor-pointer"
>
{team.leader.avatar ? (
@@ -561,7 +563,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
{members.map((member: any) => (
navigate(`/users/${member.user.id}`)}
+ onClick={() => navigate({ to: `/users/${member.user.id}` })}
className="w-full flex items-center space-x-3 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg p-2 transition-colors text-left cursor-pointer"
>
{member.user?.avatar ? (
@@ -876,4 +878,3 @@ const TeamDashboardPage: FC = (): ReactElement => {
);
};
-export default TeamDashboardPage;
diff --git a/apps/hackathon/src/app/teams/[teamId]/chat/page.tsx b/apps/hackathon/src/routes/_authenticated/teams/$teamId/chat.tsx
similarity index 97%
rename from apps/hackathon/src/app/teams/[teamId]/chat/page.tsx
rename to apps/hackathon/src/routes/_authenticated/teams/$teamId/chat.tsx
index ffe6b24..76da2e1 100644
--- a/apps/hackathon/src/app/teams/[teamId]/chat/page.tsx
+++ b/apps/hackathon/src/routes/_authenticated/teams/$teamId/chat.tsx
@@ -1,18 +1,20 @@
import { FC, ReactElement, useState, useRef, useEffect, useMemo } from 'react';
import { Button } from '@imphnen-frontend-service/ui/atoms';
-import { useNavigate, useParams } from 'react-router';
+import { createFileRoute, useNavigate } from '@tanstack/react-router';
import {
useTeamById,
useTeamMessages,
useSendMessage,
useDeleteMessage,
- useAuthStore,
-} from '@imphnen-frontend-service/service';
+ useAuthStore } from '@imphnen-frontend-service/service';
import { toast } from 'sonner';
import { Icon } from '@iconify/react';
+export const Route = createFileRoute('/_authenticated/teams/$teamId/chat')({
+ component: TeamChatPage })
+
const TeamChatPage: FC = (): ReactElement => {
- const { teamId } = useParams<{ teamId: string }>();
+ const { teamId } = Route.useParams();
const navigate = useNavigate();
const { session } = useAuthStore();
const [message, setMessage] = useState('');
@@ -117,7 +119,7 @@ const TeamChatPage: FC = (): ReactElement => {
navigate(`/teams/${teamId}`)}
+ onClick={() => navigate({ to: `/teams/${teamId}` })}
>
Back to Team
@@ -304,4 +306,3 @@ const TeamChatPage: FC = (): ReactElement => {
);
};
-export default TeamChatPage;
diff --git a/apps/hackathon/src/app/teams/[teamId]/edit/page.tsx b/apps/hackathon/src/routes/_authenticated/teams/$teamId/edit.tsx
similarity index 96%
rename from apps/hackathon/src/app/teams/[teamId]/edit/page.tsx
rename to apps/hackathon/src/routes/_authenticated/teams/$teamId/edit.tsx
index ae08037..bd62509 100644
--- a/apps/hackathon/src/app/teams/[teamId]/edit/page.tsx
+++ b/apps/hackathon/src/routes/_authenticated/teams/$teamId/edit.tsx
@@ -1,7 +1,7 @@
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 { useNavigate, useParams } from 'react-router';
+import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { useForm, Controller } from 'react-hook-form';
import {
teamUpdateSchema,
@@ -10,18 +10,20 @@ import {
useTeamById,
ETeamVisibility,
useUploadFile,
- useAuthStore,
-} from '@imphnen-frontend-service/service';
+ useAuthStore } from '@imphnen-frontend-service/service';
import { zodResolver } from '@hookform/resolvers/zod';
import { CitySelect } from '../../../../components/city-select';
import { Icon } from '@iconify/react';
import { toast } from 'sonner';
+export const Route = createFileRoute('/_authenticated/teams/$teamId/edit')({
+ component: EditTeamPage })
+
const MAX_FILE_SIZE = 2 * 1024 * 1024;
const EditTeamPage: FC = (): ReactElement => {
- const { teamId } = useParams<{ teamId: string }>();
+ const { teamId } = Route.useParams();
const navigate = useNavigate();
const { session } = useAuthStore();
const [logoFile, setLogoFile] = useState(null);
@@ -43,8 +45,7 @@ const EditTeamPage: FC = (): ReactElement => {
const form = useForm({
resolver: zodResolver(teamUpdateSchema),
- mode: 'all',
- });
+ mode: 'all' });
useEffect(() => {
if (team) {
@@ -54,8 +55,7 @@ const EditTeamPage: FC = (): ReactElement => {
city: team.city,
visibility: team.visibility,
logo: team.logo,
- banner: team.banner,
- });
+ banner: team.banner });
if (team.logo) setLogoPreview(team.logo);
if (team.banner) setBannerPreview(team.banner);
}
@@ -78,7 +78,7 @@ const EditTeamPage: FC = (): ReactElement => {
Only the team leader can edit team information
- navigate(`/teams/${teamId}`)}>
+ navigate({ to: `/teams/${teamId}` })}>
Back to Team
@@ -137,10 +137,9 @@ const EditTeamPage: FC = (): ReactElement => {
await updateTeam({
...data,
logo: logoUrl,
- banner: bannerUrl,
- });
+ banner: bannerUrl });
- navigate(`/teams/${teamId}`);
+ navigate({ to: `/teams/${teamId}` });
} catch (error) {
console.error('Failed to update team:', error);
}
@@ -366,7 +365,7 @@ const EditTeamPage: FC = (): ReactElement => {
type="button"
variant="secondary"
className="flex-1"
- onClick={() => navigate(`/teams/${teamId}`)}
+ onClick={() => navigate({ to: `/teams/${teamId}` })}
>
Cancel
@@ -385,4 +384,3 @@ const EditTeamPage: FC = (): ReactElement => {
);
};
-export default EditTeamPage;
diff --git a/apps/hackathon/src/app/teams/[teamId]/members/page.tsx b/apps/hackathon/src/routes/_authenticated/teams/$teamId/members.tsx
similarity index 97%
rename from apps/hackathon/src/app/teams/[teamId]/members/page.tsx
rename to apps/hackathon/src/routes/_authenticated/teams/$teamId/members.tsx
index 301a491..b2b71cb 100644
--- a/apps/hackathon/src/app/teams/[teamId]/members/page.tsx
+++ b/apps/hackathon/src/routes/_authenticated/teams/$teamId/members.tsx
@@ -1,6 +1,6 @@
import { FC, ReactElement, useState } from 'react';
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
-import { useNavigate, useParams } from 'react-router';
+import { createFileRoute, useNavigate } from '@tanstack/react-router';
import {
useTeamById,
useTeamMembers,
@@ -11,14 +11,16 @@ import {
ETeamMemberStatus,
inviteMemberSchema,
TInviteMemberForm,
- useAuthStore,
-} from '@imphnen-frontend-service/service';
+ useAuthStore } from '@imphnen-frontend-service/service';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from 'sonner';
+export const Route = createFileRoute('/_authenticated/teams/$teamId/members')({
+ component: ManageMembersPage })
+
const ManageMembersPage: FC = (): ReactElement => {
- const { teamId } = useParams<{ teamId: string }>();
+ const { teamId } = Route.useParams();
const navigate = useNavigate();
const { session } = useAuthStore();
const [showInviteModal, setShowInviteModal] = useState(false);
@@ -51,8 +53,7 @@ const ManageMembersPage: FC = (): ReactElement => {
const form = useForm({
resolver: zodResolver(inviteMemberSchema),
- mode: 'all',
- });
+ mode: 'all' });
if (isLoadingTeam) {
return (
@@ -71,7 +72,7 @@ const ManageMembersPage: FC = (): ReactElement => {
Only the team leader can manage members
- navigate(`/teams/${teamId}`)}>
+ navigate({ to: `/teams/${teamId}` })}>
Back to Team
@@ -142,7 +143,7 @@ const ManageMembersPage: FC = (): ReactElement => {
navigate(`/teams/${teamId}`)}
+ onClick={() => navigate({ to: `/teams/${teamId}` })}
>
Back to Team
@@ -378,4 +379,3 @@ const ManageMembersPage: FC = (): ReactElement => {
);
};
-export default ManageMembersPage;
diff --git a/apps/hackathon/src/app/teams/[teamId]/submission/page.tsx b/apps/hackathon/src/routes/_authenticated/teams/$teamId/submission.tsx
similarity index 96%
rename from apps/hackathon/src/app/teams/[teamId]/submission/page.tsx
rename to apps/hackathon/src/routes/_authenticated/teams/$teamId/submission.tsx
index f652060..6c38228 100644
--- a/apps/hackathon/src/app/teams/[teamId]/submission/page.tsx
+++ b/apps/hackathon/src/routes/_authenticated/teams/$teamId/submission.tsx
@@ -1,11 +1,14 @@
import { FC, ReactElement } from 'react';
import { Button } from '@imphnen-frontend-service/ui/atoms';
-import { useNavigate, useParams } from 'react-router';
+import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { useTeamById, useTeamSubmission, useAuthStore } from '@imphnen-frontend-service/service';
import { encodeCertificateId } from '../../../../utils/certificate';
+export const Route = createFileRoute('/_authenticated/teams/$teamId/submission')({
+ component: SubmissionViewPage })
+
const SubmissionViewPage: FC = (): ReactElement => {
- const { teamId } = useParams<{ teamId: string }>();
+ const { teamId } = Route.useParams();
const navigate = useNavigate();
const { session } = useAuthStore();
@@ -29,7 +32,7 @@ const SubmissionViewPage: FC = (): ReactElement => {
📄
No Submission Yet
Your team hasn't submitted a project
- navigate(`/teams/${teamId}`)}>Back to Team
+ navigate({ to: `/teams/${teamId}` })}>Back to Team
);
}
@@ -40,8 +43,7 @@ const SubmissionViewPage: FC = (): ReactElement => {
month: 'long',
year: 'numeric',
hour: '2-digit',
- minute: '2-digit',
- })
+ minute: '2-digit' })
: 'Not submitted';
return (
@@ -53,7 +55,7 @@ const SubmissionViewPage: FC = (): ReactElement => {
Project Submission
{team?.name}
- navigate(`/teams/${teamId}`)}>
+ navigate({ to: `/teams/${teamId}` })}>
Back to Team
@@ -124,7 +126,7 @@ const SubmissionViewPage: FC = (): ReactElement => {
submission.id,
session?.user?.id || ''
);
- navigate(`/certificate/${encodeURIComponent(certId)}`);
+ navigate({ to: `/certificate/${encodeURIComponent(certId)}` });
}}
className="shrink-0 px-6 py-2 bg-amber-600 hover:bg-amber-700 dark:bg-amber-600 dark:hover:bg-amber-700 text-white font-medium rounded-lg transition-colors"
>
@@ -252,4 +254,3 @@ const SubmissionViewPage: FC = (): ReactElement => {
);
};
-export default SubmissionViewPage;
diff --git a/apps/hackathon/src/app/teams/[teamId]/submit/page.tsx b/apps/hackathon/src/routes/_authenticated/teams/$teamId/submit.tsx
similarity index 96%
rename from apps/hackathon/src/app/teams/[teamId]/submit/page.tsx
rename to apps/hackathon/src/routes/_authenticated/teams/$teamId/submit.tsx
index f54ef95..5eb3d2d 100644
--- a/apps/hackathon/src/app/teams/[teamId]/submit/page.tsx
+++ b/apps/hackathon/src/routes/_authenticated/teams/$teamId/submit.tsx
@@ -1,7 +1,7 @@
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 { useNavigate, useParams } from 'react-router';
+import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { useForm, Controller } from 'react-hook-form';
import {
projectSubmissionSchema,
@@ -10,19 +10,21 @@ import {
useTeamById,
useTeamSubmission,
useUploadSubmission,
- useAuthStore,
-} from '@imphnen-frontend-service/service';
+ useAuthStore } from '@imphnen-frontend-service/service';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from 'sonner';
import { Icon } from '@iconify/react';
+export const Route = createFileRoute('/_authenticated/teams/$teamId/submit')({
+ component: SubmitProjectPage })
+
const MIN_TEAM_MEMBERS = 2;
const MAX_FILE_SIZE = 2 * 1024 * 1024;
const SUBMISSION_DEADLINE = new Date('2025-12-07T16:59:00Z');
const SubmitProjectPage: FC = (): ReactElement => {
- const { teamId } = useParams<{ teamId: string }>();
+ const { teamId } = Route.useParams();
const navigate = useNavigate();
const { session } = useAuthStore();
const [showConfirmModal, setShowConfirmModal] = useState(false);
@@ -78,8 +80,7 @@ const SubmitProjectPage: FC = (): ReactElement => {
const form = useForm({
resolver: zodResolver(projectSubmissionSchema),
- mode: 'all',
- });
+ mode: 'all' });
if (!isLeader) {
return (
@@ -90,7 +91,7 @@ const SubmitProjectPage: FC = (): ReactElement => {
Only the team leader can submit projects
- navigate(`/teams/${teamId}`)}>
+ navigate({ to: `/teams/${teamId}` })}>
Back to Team
@@ -108,12 +109,12 @@ const SubmitProjectPage: FC = (): ReactElement => {
Your team has already submitted a project
-
navigate(`/teams/${teamId}/submission`)}>
+ navigate({ to: `/teams/${teamId}/submission` })}>
View Submission
navigate(`/teams/${teamId}`)}
+ onClick={() => navigate({ to: `/teams/${teamId}` })}
>
Back to Team
@@ -147,14 +148,14 @@ const SubmitProjectPage: FC = (): ReactElement => {
navigate(`/teams/${teamId}`)}
+ onClick={() => navigate({ to: `/teams/${teamId}` })}
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 transition-colors cursor-pointer"
>
Back to Team
navigate('/dashboard')}
+ onClick={() => navigate({ to: '/dashboard' })}
className="w-full py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors cursor-pointer"
>
Back to Dashboard
@@ -197,9 +198,8 @@ const SubmitProjectPage: FC = (): ReactElement => {
try {
await submitProject({
...data,
- screenshots,
- });
- navigate(`/teams/${teamId}/submission`);
+ screenshots });
+ navigate({ to: `/teams/${teamId}/submission` });
} catch (error) {
console.error('Failed to submit project:', error);
}
@@ -416,7 +416,7 @@ const SubmitProjectPage: FC = (): ReactElement => {
type="button"
variant="secondary"
className="flex-1"
- onClick={() => navigate(`/teams/${teamId}`)}
+ onClick={() => navigate({ to: `/teams/${teamId}` })}
>
Cancel
@@ -500,4 +500,3 @@ const SubmitProjectPage: FC = (): ReactElement => {
);
};
-export default SubmitProjectPage;
diff --git a/apps/hackathon/src/app/teams/browse/page.tsx b/apps/hackathon/src/routes/_authenticated/teams/browse.tsx
similarity index 96%
rename from apps/hackathon/src/app/teams/browse/page.tsx
rename to apps/hackathon/src/routes/_authenticated/teams/browse.tsx
index cb19400..db3e2f6 100644
--- a/apps/hackathon/src/app/teams/browse/page.tsx
+++ b/apps/hackathon/src/routes/_authenticated/teams/browse.tsx
@@ -1,19 +1,22 @@
import { FC, ReactElement, useState, useEffect, useCallback } from 'react';
import { Button } from '@imphnen-frontend-service/ui/atoms';
-import { Link, useNavigate, useSearchParams } from 'react-router';
+import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
import {
useTeams,
useJoinTeam,
useMyTeams,
ETeamVisibility,
joinTeamSchema,
- TJoinTeamForm,
-} from '@imphnen-frontend-service/service';
+ TJoinTeamForm } from '@imphnen-frontend-service/service';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { CitySelect } from '../../../components/city-select';
import { Icon } from '@iconify/react';
+export const Route = createFileRoute('/_authenticated/teams/browse')({
+ component: BrowseTeamsPage,
+})
+
const DEFAULT_PER_PAGE = 12;
const PER_PAGE_OPTIONS = [6, 12, 24, 48];
@@ -61,7 +64,16 @@ const TeamCardSkeleton: FC = () => (
const BrowseTeamsPage: FC = (): ReactElement => {
const navigate = useNavigate();
- const [searchParams, setSearchParams] = useSearchParams();
+ const searchParams = new URLSearchParams(globalThis.location.search);
+ const setSearchParams = (newParams: URLSearchParams, opts?: { replace?: boolean }) => {
+ const url = new URL(globalThis.location.href);
+ url.search = newParams.toString();
+ if (opts?.replace) {
+ globalThis.history.replaceState(null, '', url.toString());
+ } else {
+ globalThis.history.pushState(null, '', url.toString());
+ }
+ };
const isTeamFeaturesClosed = new Date() >= TEAM_FEATURES_DEADLINE;
@@ -435,7 +447,7 @@ const BrowseTeamsPage: FC = (): ReactElement => {
navigate(`/teams/${team.id}`)}
+ onClick={() => navigate({ to: `/teams/${team.id}` })}
>
Your Team
@@ -455,7 +467,7 @@ const BrowseTeamsPage: FC = (): ReactElement => {
navigate(`/teams/${team.id}`)}
+ onClick={() => navigate({ to: `/teams/${team.id}` })}
>
View Team
@@ -616,4 +628,3 @@ const BrowseTeamsPage: FC = (): ReactElement => {
);
};
-export default BrowseTeamsPage;
diff --git a/apps/hackathon/src/app/teams/create/page.tsx b/apps/hackathon/src/routes/_authenticated/teams/create.tsx
similarity index 97%
rename from apps/hackathon/src/app/teams/create/page.tsx
rename to apps/hackathon/src/routes/_authenticated/teams/create.tsx
index bc449e2..aeae340 100644
--- a/apps/hackathon/src/app/teams/create/page.tsx
+++ b/apps/hackathon/src/routes/_authenticated/teams/create.tsx
@@ -1,7 +1,7 @@
import { FC, ReactElement, useState } from 'react';
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
import { Button, Textarea } from '@imphnen-frontend-service/ui/atoms';
-import { useNavigate } from 'react-router';
+import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { useForm, Controller } from 'react-hook-form';
import { teamCreateSchema, TTeamCreateForm, useCreateTeam, ETeamVisibility, useUploadFile } from '@imphnen-frontend-service/service';
import { zodResolver } from '@hookform/resolvers/zod';
@@ -10,6 +10,10 @@ import { Icon } from '@iconify/react';
import { CitySelect } from '../../../components/city-select';
+export const Route = createFileRoute('/_authenticated/teams/create')({
+ component: CreateTeamPage,
+})
+
const MAX_FILE_SIZE = 2 * 1024 * 1024;
const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z');
@@ -93,7 +97,7 @@ const CreateTeamPage: FC = (): ReactElement => {
});
toast.success('Team created successfully!');
- navigate(`/teams/${result.data.id}`);
+ navigate({ to: `/teams/${result.data.id}` });
} catch (error: any) {
console.error('Failed to create team:', error);
@@ -133,14 +137,14 @@ const CreateTeamPage: FC = (): ReactElement => {
navigate('/dashboard')}
+ onClick={() => navigate({ to: '/dashboard' })}
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 transition-colors cursor-pointer"
>
Back to Dashboard
navigate('/teams/browse')}
+ onClick={() => navigate({ to: '/teams/browse' })}
className="w-full py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors cursor-pointer"
>
Browse Teams
@@ -352,7 +356,7 @@ const CreateTeamPage: FC = (): ReactElement => {
type="button"
variant="secondary"
className="flex-1"
- onClick={() => navigate('/dashboard')}
+ onClick={() => navigate({ to: '/dashboard' })}
>
Cancel
@@ -371,4 +375,3 @@ const CreateTeamPage: FC = (): ReactElement => {
);
};
-export default CreateTeamPage;
diff --git a/apps/hackathon/src/app/users/[userId]/page.tsx b/apps/hackathon/src/routes/_authenticated/users/$userId.tsx
similarity index 96%
rename from apps/hackathon/src/app/users/[userId]/page.tsx
rename to apps/hackathon/src/routes/_authenticated/users/$userId.tsx
index 6db6f5b..b41a381 100644
--- a/apps/hackathon/src/app/users/[userId]/page.tsx
+++ b/apps/hackathon/src/routes/_authenticated/users/$userId.tsx
@@ -1,14 +1,16 @@
import { FC, ReactElement } from 'react';
import { Button } from '@imphnen-frontend-service/ui/atoms';
-import { useParams, useNavigate, Link } from 'react-router';
+import { createFileRoute, useNavigate, Link } from '@tanstack/react-router';
import {
useUserDetailsById,
- useTeamsByUserId,
-} from '@imphnen-frontend-service/service';
+ useTeamsByUserId } from '@imphnen-frontend-service/service';
import { Icon } from '@iconify/react';
+export const Route = createFileRoute('/_authenticated/users/$userId')({
+ component: UserProfilePage })
+
const UserProfilePage: FC = (): ReactElement => {
- const { userId } = useParams<{ userId: string }>();
+ const { userId } = Route.useParams();
const navigate = useNavigate();
const { data: userData, isLoading, error } = useUserDetailsById(userId || '');
const { data: teamsData } = useTeamsByUserId(userId || '');
@@ -35,7 +37,7 @@ const UserProfilePage: FC = (): ReactElement => {
{error && (
{String(error)}
)}
- navigate('/dashboard')}>
+ navigate({ to: '/dashboard' })}>
Back to Dashboard
@@ -234,4 +236,3 @@ const UserProfilePage: FC = (): ReactElement => {
);
};
-export default UserProfilePage;
diff --git a/apps/hackathon/src/routes/_public.tsx b/apps/hackathon/src/routes/_public.tsx
new file mode 100644
index 0000000..5aaf130
--- /dev/null
+++ b/apps/hackathon/src/routes/_public.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute, Outlet } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/_public')({
+ component: PublicLayout,
+})
+
+function PublicLayout() {
+ return
+}
diff --git a/apps/hackathon/src/routes/_public/auth/callback.tsx b/apps/hackathon/src/routes/_public/auth/callback.tsx
new file mode 100644
index 0000000..e8f1bdc
--- /dev/null
+++ b/apps/hackathon/src/routes/_public/auth/callback.tsx
@@ -0,0 +1,123 @@
+import { createFileRoute, useNavigate } from '@tanstack/react-router'
+import { FC, ReactElement, useEffect, useState, useRef } from 'react'
+import { useGitHubCallback } from '@imphnen-frontend-service/service'
+import { toast } from 'sonner'
+
+export const Route = createFileRoute('/_public/auth/callback')({
+ component: CallbackPage,
+})
+
+function CallbackPage(): 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')
+
+ if (accessToken) {
+ setIsProcessing(false)
+
+ if (type === 'recovery' || type === 'magiclink') {
+ toast.success('Email verified! Please set your new password.')
+ navigate({ to: '/auth/reset-password', search: { access_token: accessToken } })
+ return
+ }
+
+ if (type === 'signup' || type === 'email_confirmation') {
+ toast.success('Email verified successfully! Please log in to continue.')
+ navigate({ to: '/auth/login' })
+ return
+ }
+
+ toast.success('Email verified! Please set your new password.')
+ navigate({ to: '/auth/reset-password', search: { 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({ to: '/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:
+
+ Go to GitHub Email Settings
+ Uncheck "Keep my email addresses private"
+ Or go to Profile Settings and set a public email
+ Try signing in with GitHub again
+
+
Alternatively, you can sign up using email and password instead.
+
+ )}
+
Redirecting to login page in 3 seconds...
+
+
+ )
+ }
+
+ return (
+
+
+
+
Completing login...
+
Please wait
+
+
+ )
+}
diff --git a/apps/hackathon/src/routes/_public/auth/forgot-password.tsx b/apps/hackathon/src/routes/_public/auth/forgot-password.tsx
new file mode 100644
index 0000000..2e43079
--- /dev/null
+++ b/apps/hackathon/src/routes/_public/auth/forgot-password.tsx
@@ -0,0 +1,74 @@
+import { createFileRoute, useNavigate, Link } from '@tanstack/react-router'
+import { useState } from 'react'
+import { useForgotPassword } from '@imphnen-frontend-service/service'
+import { toast } from 'sonner'
+import { Icon } from '@iconify/react'
+import ThemeToggle from '../../../components/theme-toggle'
+
+export const Route = createFileRoute('/_public/auth/forgot-password')({
+ component: ForgotPasswordPage,
+})
+
+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.
+
Back to Login
+
setEmailSent(false)} className="w-full py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">Send another email
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ navigate({ to: '/auth/login' })} className="cursor-pointer text-primary-500 hover:text-primary-600 dark:text-primary-400 dark:hover:text-primary-300 text-base font-sans flex items-center">
+ Back to Login
+
+
+
+
+
Forgot Password?
+
No worries, we'll send you reset instructions
+
+
+
+ Email Address
+ 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 />
+
+
+ {forgotPasswordMutation.isPending ? 'Sending...' : 'Send Reset Link'}
+
+
+
+
+ )
+}
diff --git a/apps/hackathon/src/app/auth/login/page.tsx b/apps/hackathon/src/routes/_public/auth/login.tsx
similarity index 74%
rename from apps/hackathon/src/app/auth/login/page.tsx
rename to apps/hackathon/src/routes/_public/auth/login.tsx
index 4b2a6af..e60c991 100644
--- a/apps/hackathon/src/app/auth/login/page.tsx
+++ b/apps/hackathon/src/routes/_public/auth/login.tsx
@@ -1,71 +1,83 @@
-import { useState, useEffect } from 'react';
+import { createFileRoute, redirect } 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 { 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 { ThemeToggle } from '../../../components/theme-toggle';
+ SessionToken,
+} from '@imphnen-frontend-service/service'
+import { GithubOutlined } from '@ant-design/icons'
+import { useNavigate, Link } 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'
+import { ThemeToggle } from '../../../components/theme-toggle'
-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')({
+ beforeLoad: () => {
+ const session = SessionToken.get()
+ if (session?.token?.access_token) {
+ throw redirect({ to: '/dashboard' })
+ }
+ },
+ 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/reset-password', search: { access_token: accessToken } })
}
- }, [navigate]);
+ }, [navigate])
const onSubmit = handleSubmit(async (data) => {
- setError(null);
+ setError(null)
try {
- const result = await loginMutation.mutateAsync(data);
- toast.success('Login successful!');
- navigate(result.user.location ? '/dashboard' : '/onboarding/user');
+ const result = await loginMutation.mutateAsync(data)
+ toast.success('Login successful!')
+ navigate({ to: result.user.location ? '/dashboard' : '/onboarding/user' })
} 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 (
- navigate('/')} className="cursor-pointer text-primary-500 hover:text-primary-600 text-base font-sans flex items-center">
+ navigate({ to: '/' })} className="cursor-pointer text-primary-500 hover:text-primary-600 text-base font-sans flex items-center">
Back to Homepage
@@ -136,5 +148,5 @@ export default function LoginPage() {
- );
+ )
}
diff --git a/apps/hackathon/src/routes/_public/auth/reset-password.tsx b/apps/hackathon/src/routes/_public/auth/reset-password.tsx
new file mode 100644
index 0000000..7da834f
--- /dev/null
+++ b/apps/hackathon/src/routes/_public/auth/reset-password.tsx
@@ -0,0 +1,87 @@
+import { createFileRoute, useNavigate } from '@tanstack/react-router'
+import { useState, useEffect } from 'react'
+import { useResetPassword, useAuthStore } from '@imphnen-frontend-service/service'
+import { toast } from 'sonner'
+import { Icon } from '@iconify/react'
+
+export const Route = createFileRoute('/_public/auth/reset-password')({
+ component: ResetPasswordPage,
+})
+
+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({ to: '/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({ to: '/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
+
+
+
+
New Password
+
+ 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} />
+ setShowPassword(!showPassword)} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">
+
+
+
+
+
+
Confirm New Password
+
+ 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} />
+ setShowConfirmPassword(!showConfirmPassword)} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">
+
+
+
+
+
+ {resetPasswordMutation.isPending ? 'Updating...' : 'Update Password'}
+
+
+
+
+ )
+}
diff --git a/apps/hackathon/src/routes/_public/auth/signup.tsx b/apps/hackathon/src/routes/_public/auth/signup.tsx
new file mode 100644
index 0000000..16c6855
--- /dev/null
+++ b/apps/hackathon/src/routes/_public/auth/signup.tsx
@@ -0,0 +1,227 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+import { useState } from 'react'
+import { useGitHubAuth, useSignup, SessionToken } from '@imphnen-frontend-service/service'
+import { GithubOutlined } from '@ant-design/icons'
+import { useNavigate, Link } from '@tanstack/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'
+
+export const Route = createFileRoute('/_public/auth/signup')({
+ beforeLoad: () => {
+ const session = SessionToken.get()
+ if (session?.token?.access_token) {
+ throw redirect({ to: '/dashboard' })
+ }
+ },
+ component: SignupPage,
+})
+
+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')
+
+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.
+
+
Go to Login
+
+
navigate({ to: '/' })} className="w-full py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors cursor-pointer">Back to Homepage
+
+
+
+ )
+ }
+
+ 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.
+
+
+
Go to Login
+
+
setRegistrationSuccess(false)} className="w-full py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors cursor-pointer">Register with different 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 (
+
+
+
+ navigate({ to: '/' })} className="cursor-pointer text-primary-500 hover:text-primary-600 dark:text-primary-400 dark:hover:text-primary-300 text-base font-sans flex items-center">
+
+ Back to Homepage
+
+
+
+
+
Create Account
+
Join the hackathon community
+
+ {error && (
+
+ )}
+
+
+
Full Name
+
+ {errors.fullname &&
{errors.fullname.message}
}
+
+
+
Email
+
+ {errors.email &&
{errors.email.message}
}
+
+
+
Password
+
+
+ setShowPassword(!showPassword)} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">
+
+
+
+ {errors.password &&
{errors.password.message}
}
+
+
+
Confirm Password
+
+
+ setShowConfirmPassword(!showConfirmPassword)} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">
+
+
+
+ {errors.confirmPassword &&
{errors.confirmPassword.message}
}
+
+
+ {signupMutation.isPending ? 'Creating account...' : 'Create Account'}
+
+
+
+
+
+ {isGithubLoading ? 'Connecting...' : 'Sign up with GitHub'}
+
+
+ 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/certificate/[certId]/page.tsx b/apps/hackathon/src/routes/_public/certificate/$certId.tsx
similarity index 93%
rename from apps/hackathon/src/app/certificate/[certId]/page.tsx
rename to apps/hackathon/src/routes/_public/certificate/$certId.tsx
index 5c87436..f1e973b 100644
--- a/apps/hackathon/src/app/certificate/[certId]/page.tsx
+++ b/apps/hackathon/src/routes/_public/certificate/$certId.tsx
@@ -1,14 +1,16 @@
import { FC, ReactElement, useState, useEffect, useRef } from 'react';
-import { useParams, useNavigate } from 'react-router';
+import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { Button } from '@imphnen-frontend-service/ui/atoms';
import { decodeCertificateId } from '../../../utils/certificate';
import {
useCertificatePublicData,
- useAuthStore,
-} from '@imphnen-frontend-service/service';
+ useAuthStore } from '@imphnen-frontend-service/service';
import QRCode from 'qrcode';
import html2canvas from 'html2canvas';
+export const Route = createFileRoute('/_public/certificate/$certId')({
+ component: CertificatePage })
+
interface DecodedCert {
teamId: string;
submissionId: string;
@@ -16,7 +18,7 @@ interface DecodedCert {
}
const CertificatePage: FC = (): ReactElement => {
- const { certId } = useParams<{ certId: string }>();
+ const { certId } = Route.useParams();
const navigate = useNavigate();
const { session } = useAuthStore();
const [decodedInfo, setDecodedInfo] = useState(null);
@@ -53,9 +55,7 @@ const CertificatePage: FC = (): ReactElement => {
margin: 1,
color: {
dark: '#000000',
- light: '#ffffff',
- },
- })
+ light: '#ffffff' } })
.then(setQrCodeUrl)
.catch((err) => console.error('QR Code generation failed:', err));
}
@@ -117,8 +117,7 @@ const CertificatePage: FC = (): ReactElement => {
height: (1000 * 2480) / 3508,
allowTaint: true,
imageTimeout: 0,
- removeContainer: true,
- });
+ removeContainer: true });
const imageUrl = canvas.toDataURL('image/png', 1.0);
setCertificateImage(imageUrl);
@@ -183,7 +182,7 @@ const CertificatePage: FC = (): ReactElement => {
{error || 'The certificate ID is invalid or malformed.'}
- navigate('/')}>Back to Home
+ navigate({ to: '/' })}>Back to Home
);
}
@@ -211,7 +210,7 @@ const CertificatePage: FC = (): ReactElement => {
The user associated with this certificate could not be found.
- navigate('/')}>Back to Home
+ navigate({ to: '/' })}>Back to Home
);
}
@@ -277,7 +276,7 @@ const CertificatePage: FC = (): ReactElement => {
{team && isTeamMember && (
navigate(`/teams/${team.id}/submission`)}
+ onClick={() => navigate({ to: `/teams/${team.id}/submission` })}
>
Back to Submission
@@ -303,16 +302,14 @@ const CertificatePage: FC = (): ReactElement => {
backgroundSize: 'cover',
backgroundPosition: 'center',
width: '1000px',
- height: `${(1000 * 2480) / 3508}px`,
- }}
+ height: `${(1000 * 2480) / 3508}px` }}
>
{
fontSize: '32px',
lineHeight: '1.2',
wordBreak: 'break-word',
- margin: 0,
- }}
+ margin: 0 }}
>
{team?.name}
@@ -336,8 +332,7 @@ const CertificatePage: FC = (): ReactElement => {
position: 'absolute',
top: '45%',
left: '3.5%',
- width: '55%',
- }}
+ width: '55%' }}
>
{
fontSize: '40px',
lineHeight: '1.2',
wordBreak: 'break-word',
- margin: 0,
- }}
+ margin: 0 }}
>
{certificateName || 'N/A'}
@@ -365,8 +359,7 @@ const CertificatePage: FC = (): ReactElement => {
height: '190px',
display: 'flex',
alignItems: 'center',
- justifyContent: 'center',
- }}
+ justifyContent: 'center' }}
>
{qrCodeUrl && (
{
{team && (
navigate(`/teams/${team.id}/submission`)}
+ onClick={() => navigate({ to: `/teams/${team.id}/submission` })}
variant="secondary"
className="col-span-2 flex items-center gap-2 xl:col-span-1"
>
@@ -446,4 +439,3 @@ const CertificatePage: FC = (): ReactElement => {
);
};
-export default CertificatePage;
diff --git a/apps/hackathon/src/app/certificate/winner/[certId]/page.tsx b/apps/hackathon/src/routes/_public/certificate/winner/$certId.tsx
similarity index 92%
rename from apps/hackathon/src/app/certificate/winner/[certId]/page.tsx
rename to apps/hackathon/src/routes/_public/certificate/winner/$certId.tsx
index c4e2a2f..bbb395c 100644
--- a/apps/hackathon/src/app/certificate/winner/[certId]/page.tsx
+++ b/apps/hackathon/src/routes/_public/certificate/winner/$certId.tsx
@@ -1,5 +1,5 @@
import { FC, ReactElement, useEffect, useMemo, useRef, useState } from 'react';
-import { useNavigate, useParams } from 'react-router';
+import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { Button } from '@imphnen-frontend-service/ui/atoms';
import { Icon } from '@iconify/react';
import { decodeWinnerCertificateId } from '../../../../utils/certificate';
@@ -8,11 +8,13 @@ import {
useMyTeams,
useTeamById,
useTeamSubmission,
- useWinners,
-} from '@imphnen-frontend-service/service';
+ useWinners } from '@imphnen-frontend-service/service';
import QRCode from 'qrcode';
import html2canvas from 'html2canvas';
+export const Route = createFileRoute('/_public/certificate/winner/$certId')({
+ component: CertificateWinnerPage })
+
type WinnerEntry = {
team_id: string;
rank: number;
@@ -48,15 +50,14 @@ const LAYOUT_SCALE = CERT_WIDTH / LAYOUT_BASE_WIDTH;
const s = (px: number) => Math.round(px * LAYOUT_SCALE);
const CertificateWinnerPage: FC = (): ReactElement => {
- const { certId } = useParams<{ certId: string }>();
+ const { certId } = Route.useParams();
const navigate = useNavigate();
const { session } = useAuthStore();
const { data: myTeamsData } = useMyTeams();
const {
data: winnersResponse,
isLoading: isLoadingWinners,
- isError: isWinnersError,
- } = useWinners();
+ isError: isWinnersError } = useWinners();
const [decodedTeamId, setDecodedTeamId] = useState('');
const [error, setError] = useState(null);
@@ -127,9 +128,7 @@ const CertificateWinnerPage: FC = (): ReactElement => {
margin: 1,
color: {
dark: '#000000',
- light: '#ffffff',
- },
- })
+ light: '#ffffff' } })
.then(setQrCodeUrl)
.catch((err) => console.error('QR Code generation failed:', err));
}, [certId]);
@@ -156,8 +155,7 @@ const CertificateWinnerPage: FC = (): ReactElement => {
height: CERT_HEIGHT,
allowTaint: true,
imageTimeout: 0,
- removeContainer: true,
- });
+ removeContainer: true });
const imageUrl = canvas.toDataURL('image/png', 1.0);
setCertificateImage(imageUrl);
@@ -228,7 +226,7 @@ const CertificateWinnerPage: FC = (): ReactElement => {
{error || 'The certificate ID is invalid or malformed.'}
- navigate('/')}>Back to Home
+ navigate({ to: '/' })}>Back to Home
);
}
@@ -268,7 +266,7 @@ const CertificateWinnerPage: FC = (): ReactElement => {
Please try again later.
- navigate('/')}>Back to Home
+ navigate({ to: '/' })}>Back to Home
);
}
@@ -282,7 +280,7 @@ const CertificateWinnerPage: FC = (): ReactElement => {
This team is not listed as a hackathon winner.
- navigate('/')}>Back to Home
+ navigate({ to: '/' })}>Back to Home
);
}
@@ -296,7 +294,7 @@ const CertificateWinnerPage: FC = (): ReactElement => {
The team associated with this certificate could not be loaded.
- navigate('/')}>Back to Home
+ navigate({ to: '/' })}>Back to Home
);
}
@@ -317,7 +315,7 @@ const CertificateWinnerPage: FC = (): ReactElement => {
{isTeamMember && (
navigate('/dashboard')}
+ onClick={() => navigate({ to: '/dashboard' })}
>
Back to Dashboard
@@ -344,8 +342,7 @@ const CertificateWinnerPage: FC = (): ReactElement => {
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
width: `${CERT_WIDTH}px`,
- height: `${CERT_HEIGHT}px`,
- }}
+ height: `${CERT_HEIGHT}px` }}
>