From 4f4e94389eb5739f4cdc12ff3e1fe27ef105b091 Mon Sep 17 00:00:00 2001 From: Ardeman Date: Mon, 3 Mar 2025 16:33:30 +0800 Subject: [PATCH] feat: add admin login page and authentication logic with form handling --- app/configs/pages.ts | 6 + app/pages/admin-login/index.tsx | 107 +++++++++++++++++ app/routes/_admin.lg-admin._auth.login.tsx | 108 +----------------- .../_admin.lg-admin._dashboard._index.tsx | 4 +- .../_admin.lg-admin._dashboard.admins.tsx | 4 +- ...min.lg-admin._dashboard.advertisements.tsx | 4 +- .../_admin.lg-admin._dashboard.contents.tsx | 4 +- .../_admin.lg-admin._dashboard.settings.tsx | 4 +- .../_admin.lg-admin._dashboard.site-data.tsx | 4 +- ...dmin.lg-admin._dashboard.subscriptions.tsx | 4 +- .../_admin.lg-admin._dashboard.users.tsx | 4 +- app/routes/_admin.lg-admin.tsx | 10 +- app/routes/_layout._index.tsx | 4 +- app/routes/_layout.category.$name.tsx | 4 +- app/routes/_layout.detail.$slug.tsx | 4 +- app/routes/actions.admin.login.ts | 72 ++++++++++++ 16 files changed, 207 insertions(+), 140 deletions(-) create mode 100644 app/configs/pages.ts create mode 100644 app/pages/admin-login/index.tsx create mode 100644 app/routes/actions.admin.login.ts diff --git a/app/configs/pages.ts b/app/configs/pages.ts new file mode 100644 index 0000000..42b3ff4 --- /dev/null +++ b/app/configs/pages.ts @@ -0,0 +1,6 @@ +export const AUTH_PAGES = [ + '/lg-admin/login', + '/lg-admin/forgot-password', + '/lg-admin/reset-password', + '/lg-admin/register', +] diff --git a/app/pages/admin-login/index.tsx b/app/pages/admin-login/index.tsx new file mode 100644 index 0000000..2698423 --- /dev/null +++ b/app/pages/admin-login/index.tsx @@ -0,0 +1,107 @@ +import { useState } from 'react' +import { Link } from 'react-router' + +import { EyeIcon } from '~/components/icons/eye' +import { Button } from '~/components/ui/button' +import { APP } from '~/configs/meta' + +export const AdminLoginPage = () => { + const [showPassword, setShowPassword] = useState(false) + + return ( +
+
+
+ + {APP.title} + +
+

+ Selamat Datang, silakan masukkan akun Anda untuk melanjutkan! +

+
+
+ {/* Input Email / No Telepon */} +
+ + +
+ + {/* Input Password */} +
+ + + +
+ + {/* Lupa Kata Sandi */} +
+ Lupa Kata Sandi? + + Reset Kata Sandi + +
+ + {/* Tombol Masuk */} + +
+
+
+ {/* Link Daftar */} +
+ Belum punya akun?{' '} + +
+
+ ) +} diff --git a/app/routes/_admin.lg-admin._auth.login.tsx b/app/routes/_admin.lg-admin._auth.login.tsx index a99db97..0113752 100644 --- a/app/routes/_admin.lg-admin._auth.login.tsx +++ b/app/routes/_admin.lg-admin._auth.login.tsx @@ -1,108 +1,4 @@ -import { useState } from 'react' -import { Link } from 'react-router' +import { AdminLoginPage } from '~/pages/admin-login' -import { EyeIcon } from '~/components/icons/eye' -import { Button } from '~/components/ui/button' -import { APP } from '~/configs/meta' - -const AuthLayout = () => { - const [showPassword, setShowPassword] = useState(false) - - return ( -
-
-
- - {APP.title} - -
-

- Selamat Datang, silakan masukkan akun Anda untuk melanjutkan! -

-
-
- {/* Input Email / No Telepon */} -
- - -
- - {/* Input Password */} -
- - - -
- - {/* Lupa Kata Sandi */} -
- Lupa Kata Sandi? - - Reset Kata Sandi - -
- - {/* Tombol Masuk */} - -
-
-
- {/* Link Daftar */} -
- Belum punya akun?{' '} - -
-
- ) -} +const AuthLayout = () => export default AuthLayout diff --git a/app/routes/_admin.lg-admin._dashboard._index.tsx b/app/routes/_admin.lg-admin._dashboard._index.tsx index 7fc1b2d..23875ef 100644 --- a/app/routes/_admin.lg-admin._dashboard._index.tsx +++ b/app/routes/_admin.lg-admin._dashboard._index.tsx @@ -1,6 +1,4 @@ import { DashboardPage } from '~/pages/dashboard' -const DashboardIndexLayout = () => { - return -} +const DashboardIndexLayout = () => export default DashboardIndexLayout diff --git a/app/routes/_admin.lg-admin._dashboard.admins.tsx b/app/routes/_admin.lg-admin._dashboard.admins.tsx index 9173750..312cc47 100644 --- a/app/routes/_admin.lg-admin._dashboard.admins.tsx +++ b/app/routes/_admin.lg-admin._dashboard.admins.tsx @@ -1,4 +1,2 @@ -const DashboardAdminsLayout = () => { - return
Admins Page
-} +const DashboardAdminsLayout = () =>
Admins Page
export default DashboardAdminsLayout diff --git a/app/routes/_admin.lg-admin._dashboard.advertisements.tsx b/app/routes/_admin.lg-admin._dashboard.advertisements.tsx index d452a18..5bd076d 100644 --- a/app/routes/_admin.lg-admin._dashboard.advertisements.tsx +++ b/app/routes/_admin.lg-admin._dashboard.advertisements.tsx @@ -1,6 +1,4 @@ import { AdvertisementsPage } from '~/pages/dashboard-advertisements' -const DashboardAdvertisementsLayout = () => { - return -} +const DashboardAdvertisementsLayout = () => export default DashboardAdvertisementsLayout diff --git a/app/routes/_admin.lg-admin._dashboard.contents.tsx b/app/routes/_admin.lg-admin._dashboard.contents.tsx index 081af43..f0d4595 100644 --- a/app/routes/_admin.lg-admin._dashboard.contents.tsx +++ b/app/routes/_admin.lg-admin._dashboard.contents.tsx @@ -1,6 +1,4 @@ import { ContentsPage } from '~/pages/dashboard-contents' -const DashboardContentsLayout = () => { - return -} +const DashboardContentsLayout = () => export default DashboardContentsLayout diff --git a/app/routes/_admin.lg-admin._dashboard.settings.tsx b/app/routes/_admin.lg-admin._dashboard.settings.tsx index 52fbb11..4e2632a 100644 --- a/app/routes/_admin.lg-admin._dashboard.settings.tsx +++ b/app/routes/_admin.lg-admin._dashboard.settings.tsx @@ -1,4 +1,2 @@ -const DashboardSettingsLayout = () => { - return
Settings Page
-} +const DashboardSettingsLayout = () =>
Settings Page
export default DashboardSettingsLayout diff --git a/app/routes/_admin.lg-admin._dashboard.site-data.tsx b/app/routes/_admin.lg-admin._dashboard.site-data.tsx index 78a0838..3c0702f 100644 --- a/app/routes/_admin.lg-admin._dashboard.site-data.tsx +++ b/app/routes/_admin.lg-admin._dashboard.site-data.tsx @@ -1,4 +1,2 @@ -const DashboardSiteDataLayout = () => { - return
Site Data Page
-} +const DashboardSiteDataLayout = () =>
Site Data Page
export default DashboardSiteDataLayout diff --git a/app/routes/_admin.lg-admin._dashboard.subscriptions.tsx b/app/routes/_admin.lg-admin._dashboard.subscriptions.tsx index fdff80d..de9e320 100644 --- a/app/routes/_admin.lg-admin._dashboard.subscriptions.tsx +++ b/app/routes/_admin.lg-admin._dashboard.subscriptions.tsx @@ -1,6 +1,4 @@ import { SubscriptionsPage } from '~/pages/dashboard-subscriptions' -const DashboardSubscriptionsLayout = () => { - return -} +const DashboardSubscriptionsLayout = () => export default DashboardSubscriptionsLayout diff --git a/app/routes/_admin.lg-admin._dashboard.users.tsx b/app/routes/_admin.lg-admin._dashboard.users.tsx index e3edc82..8c3838b 100644 --- a/app/routes/_admin.lg-admin._dashboard.users.tsx +++ b/app/routes/_admin.lg-admin._dashboard.users.tsx @@ -1,6 +1,4 @@ import { UsersPage } from '~/pages/dashboard-users' -const DashboardUsersLayout = () => { - return -} +const DashboardUsersLayout = () => export default DashboardUsersLayout diff --git a/app/routes/_admin.lg-admin.tsx b/app/routes/_admin.lg-admin.tsx index 6e75f52..5eb7a6f 100644 --- a/app/routes/_admin.lg-admin.tsx +++ b/app/routes/_admin.lg-admin.tsx @@ -1,6 +1,7 @@ -import { Outlet } from 'react-router' +import { Outlet, redirect } from 'react-router' import { getStaff } from '~/apis/admin/get-staff' +import { AUTH_PAGES } from '~/configs/pages' import { AdminProvider } from '~/contexts/admin' import { AdminDefaultLayout } from '~/layouts/admin/default' import { handleCookie } from '~/libs/cookies' @@ -9,7 +10,14 @@ import type { Route } from './+types/_admin.lg-admin' export const loader = async ({ request }: Route.LoaderArgs) => { const { adminToken } = await handleCookie(request) + const { pathname } = new URL(request.url) + const isAuthPage = AUTH_PAGES.includes(pathname) let adminData + + if (!isAuthPage && !adminToken) { + throw redirect('/lg-admin/login') + } + if (adminToken) { const { data } = await getStaff({ accessToken: adminToken, diff --git a/app/routes/_layout._index.tsx b/app/routes/_layout._index.tsx index 0038b88..ffa7627 100644 --- a/app/routes/_layout._index.tsx +++ b/app/routes/_layout._index.tsx @@ -1,7 +1,5 @@ import { NewsPage } from '~/pages/news' -const NewsIndexLayout = () => { - return -} +const NewsIndexLayout = () => export default NewsIndexLayout diff --git a/app/routes/_layout.category.$name.tsx b/app/routes/_layout.category.$name.tsx index a0fdb3b..ac046f3 100644 --- a/app/routes/_layout.category.$name.tsx +++ b/app/routes/_layout.category.$name.tsx @@ -1,7 +1,5 @@ import { NewsCategoriesPage } from '~/pages/news-categories' -const NewsCategoriesLayout = () => { - return -} +const NewsCategoriesLayout = () => export default NewsCategoriesLayout diff --git a/app/routes/_layout.detail.$slug.tsx b/app/routes/_layout.detail.$slug.tsx index 93d567b..8bbc788 100644 --- a/app/routes/_layout.detail.$slug.tsx +++ b/app/routes/_layout.detail.$slug.tsx @@ -1,7 +1,5 @@ import { NewsDetailPage } from '~/pages/news-detail' -const NewsDetailLayout = () => { - return -} +const NewsDetailLayout = () => export default NewsDetailLayout diff --git a/app/routes/actions.admin.login.ts b/app/routes/actions.admin.login.ts new file mode 100644 index 0000000..6923511 --- /dev/null +++ b/app/routes/actions.admin.login.ts @@ -0,0 +1,72 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { data } from 'react-router' +import { getValidatedFormData } from 'remix-hook-form' +import { XiorError } from 'xior' + +import { getUser } from '~/apis/news/get-user' +import { userLoginRequest } from '~/apis/news/login-user' +import { loginSchema, type TLoginSchema } from '~/layouts/news/form-login' +import { generateTokenCookie } from '~/utils/token' + +import type { Route } from './+types/actions.login' + +export const action = async ({ request }: Route.ActionArgs) => { + try { + const { + errors, + data: payload, + receivedValues: defaultValues, + } = await getValidatedFormData( + request, + zodResolver(loginSchema), + false, + ) + + if (errors) { + return data({ success: false, errors, defaultValues }, { status: 400 }) + } + + const { data: loginData } = await userLoginRequest(payload) + const { token } = loginData + const { data: userData } = await getUser({ + accessToken: token, + }) + const tokenCookie = generateTokenCookie({ + token, + }) + + const headers = new Headers() + headers.append('Set-Cookie', await tokenCookie) + + return data( + { + success: true, + user: userData, + }, + { + headers, + status: 200, + statusText: 'OK', + }, + ) + } catch (error) { + if (error instanceof XiorError) { + return data( + { + success: false, + message: error?.response?.data?.error?.message || error.message, + }, + { + status: error?.response?.status || 500, + }, + ) + } + return data( + { + success: false, + message: 'Internal server error', + }, + { status: 500 }, + ) + } +}