From c7195b742865e206f26ede229c4b0514f6ccd359 Mon Sep 17 00:00:00 2001 From: Ardeman Date: Sat, 15 Mar 2025 15:24:01 +0800 Subject: [PATCH] feat: refactor user authentication and subscription dialogs for improved structure and consistency --- app/apis/news/login-user.ts | 2 +- app/apis/news/register-user.ts | 2 +- app/layouts/news/default.tsx | 59 +------ app/layouts/news/dialog-forgot-password.tsx | 49 ++++++ app/layouts/news/dialog-login.tsx | 134 ++++++++++++++ app/layouts/news/dialog-register.tsx | 163 ++++++++++++++++++ ...ibe-plan.tsx => dialog-subscribe-plan.tsx} | 76 ++++---- app/layouts/news/form-forgot-password.tsx | 32 ---- app/layouts/news/form-login.tsx | 122 ------------- app/layouts/news/form-register.tsx | 148 ---------------- app/routes/actions.login.ts | 2 +- app/routes/actions.register.ts | 2 +- app/routes/actions.subscribe.ts | 2 +- 13 files changed, 404 insertions(+), 389 deletions(-) create mode 100644 app/layouts/news/dialog-forgot-password.tsx create mode 100644 app/layouts/news/dialog-login.tsx create mode 100644 app/layouts/news/dialog-register.tsx rename app/layouts/news/{form-subscribe-plan.tsx => dialog-subscribe-plan.tsx} (51%) delete mode 100644 app/layouts/news/form-forgot-password.tsx delete mode 100644 app/layouts/news/form-login.tsx delete mode 100644 app/layouts/news/form-register.tsx diff --git a/app/apis/news/login-user.ts b/app/apis/news/login-user.ts index d536481..9f7aff3 100644 --- a/app/apis/news/login-user.ts +++ b/app/apis/news/login-user.ts @@ -1,6 +1,6 @@ import { z } from 'zod' -import { type TLoginSchema } from '~/layouts/news/form-login' +import { type TLoginSchema } from '~/layouts/news/dialog-login' import { HttpServer } from '~/libs/http-server' export const loginResponseSchema = z.object({ diff --git a/app/apis/news/register-user.ts b/app/apis/news/register-user.ts index 1732b2b..9d49eae 100644 --- a/app/apis/news/register-user.ts +++ b/app/apis/news/register-user.ts @@ -1,4 +1,4 @@ -import type { TRegisterSchema } from '~/layouts/news/form-register' +import type { TRegisterSchema } from '~/layouts/news/dialog-register' import { HttpServer } from '~/libs/http-server' import { loginResponseSchema } from './login-user' diff --git a/app/layouts/news/default.tsx b/app/layouts/news/default.tsx index 1c50111..d1d5fb3 100644 --- a/app/layouts/news/default.tsx +++ b/app/layouts/news/default.tsx @@ -1,34 +1,22 @@ import { type PropsWithChildren } from 'react' import { Toaster } from 'react-hot-toast' -import { DialogNews } from '~/components/dialog/news' import { DialogSuccess } from '~/components/dialog/success' import { useNewsContext } from '~/contexts/news' import { Banner } from '~/layouts/news/banner' -import { FormForgotPassword } from '~/layouts/news/form-forgot-password' -import { FormLogin } from '~/layouts/news/form-login' -import { FormRegister } from '~/layouts/news/form-register' +import { DialogForgotPassword } from '~/layouts/news/dialog-forgot-password' +import { DialogLogin } from '~/layouts/news/dialog-login' +import { DialogRegister } from './dialog-register' +import { DialogSubscribePlan } from './dialog-subscribe-plan' import { FooterLinks } from './footer-links' import { FooterNewsletter } from './footer-newsletter' -import { FormSubscribePlan } from './form-subscribe-plan' import { HeaderMenu } from './header-menu' import { HeaderTop } from './header-top' export const NewsDefaultLayout = (properties: PropsWithChildren) => { const { children } = properties - const { - isLoginOpen, - setIsLoginOpen, - isRegisterOpen, - setIsRegisterOpen, - isForgetOpen, - setIsForgetOpen, - isSuccessOpen, - setIsSuccessOpen, - isSubscribeOpen, - setIsSubscribeOpen, - } = useNewsContext() + const { isSuccessOpen, setIsSuccessOpen } = useNewsContext() return (
@@ -46,39 +34,10 @@ export const NewsDefaultLayout = (properties: PropsWithChildren) => { - - setIsLoginOpen(false)} - description="Selamat Datang, silakan daftarkan akun Anda untuk melanjutkan!" - > - - - - setIsRegisterOpen(false)} - description="Selamat Datang, silakan isi keterangan akun Anda untuk melanjutkan!" - > - - - - setIsForgetOpen(false)} - description="Selamat Datang, silakan isi keterangan akun Anda untuk melanjutkan!" - > - - - - setIsSubscribeOpen(false)} - description="Selamat Datang, silakan Pilih Subscribe Plan Anda untuk melanjutkan!" - > - - - + + + + { diff --git a/app/layouts/news/dialog-forgot-password.tsx b/app/layouts/news/dialog-forgot-password.tsx new file mode 100644 index 0000000..5a84cf7 --- /dev/null +++ b/app/layouts/news/dialog-forgot-password.tsx @@ -0,0 +1,49 @@ +import { useFetcher } from 'react-router' + +import { DialogNews } from '~/components/dialog/news' +import { Button } from '~/components/ui/button' +import { useNewsContext } from '~/contexts/news' + +export const DialogForgotPassword = () => { + const { isForgetOpen, setIsForgetOpen } = useNewsContext() + const fetcher = useFetcher() + + return ( + { + if (fetcher.state === 'idle') { + setIsForgetOpen(false) + } + }} + description="Selamat Datang, silakan isi keterangan akun Anda untuk melanjutkan!" + > +
+
+
+ {/* Input Email / No Telepon */} +
+ + +
+ + {/* Tombol Masuk */} + +
+
+
+
+ ) +} diff --git a/app/layouts/news/dialog-login.tsx b/app/layouts/news/dialog-login.tsx new file mode 100644 index 0000000..2837677 --- /dev/null +++ b/app/layouts/news/dialog-login.tsx @@ -0,0 +1,134 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { useEffect, useState } from 'react' +import { useFetcher } from 'react-router' +import { RemixFormProvider, useRemixForm } from 'remix-hook-form' +import { z } from 'zod' + +import { DialogNews } from '~/components/dialog/news' +import { Button } from '~/components/ui/button' +import { Input } from '~/components/ui/input' +import { useNewsContext } from '~/contexts/news' + +export const loginSchema = z.object({ + email: z.string().email('Email tidak valid'), + password: z.string().min(6, 'Kata sandi minimal 6 karakter'), +}) + +export type TLoginSchema = z.infer + +export const DialogLogin = () => { + const { + setIsRegisterOpen, + setIsLoginOpen, + setIsForgetOpen, + setIsSubscribeOpen, + isLoginOpen, + } = useNewsContext() + const fetcher = useFetcher() + const [error, setError] = useState() + + const formMethods = useRemixForm({ + mode: 'onSubmit', + fetcher, + resolver: zodResolver(loginSchema), + }) + + const { handleSubmit } = formMethods + + useEffect(() => { + if (!fetcher.data?.success) { + setError(fetcher.data?.message) + return + } + + setError(undefined) + setIsLoginOpen(false) + + if (fetcher.data?.user.subscribe_plan_code === 'basic') { + setIsSubscribeOpen(true) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fetcher]) + + return ( + { + if (fetcher.state === 'idle') { + setIsLoginOpen(false) + } + }} + description="Selamat Datang, silakan daftarkan akun Anda untuk melanjutkan!" + > +
+
+ + + + + + + {error && ( +
{error}
+ )} + +
+ Lupa Kata Sandi? + +
+ + +
+
+ + {/* Link Daftar */} +
+ Belum punya akun?{' '} + +
+
+
+
+ ) +} diff --git a/app/layouts/news/dialog-register.tsx b/app/layouts/news/dialog-register.tsx new file mode 100644 index 0000000..5a6ec6b --- /dev/null +++ b/app/layouts/news/dialog-register.tsx @@ -0,0 +1,163 @@ +import { DevTool } from '@hookform/devtools' +import { zodResolver } from '@hookform/resolvers/zod' +import { useEffect, useState } from 'react' +import { useFetcher, useRouteLoaderData } from 'react-router' +import { RemixFormProvider, useRemixForm } from 'remix-hook-form' +import { z } from 'zod' + +import { DialogNews } from '~/components/dialog/news' +import { Button } from '~/components/ui/button' +import { Combobox } from '~/components/ui/combobox' +import { Input } from '~/components/ui/input' +import { useNewsContext } from '~/contexts/news' +import type { loader } from '~/routes/_news' + +export const registerSchema = z + .object({ + email: z.string().email('Email tidak valid'), + password: z.string().min(6, 'Kata sandi minimal 6 karakter'), + rePassword: z.string().min(6, 'Kata sandi minimal 6 karakter'), + phone: z.string().min(10, 'No telepon tidak valid'), + subscribe_plan: z + .object({ + id: z.string(), + code: z.string(), + name: z.string(), + }) + .optional() + .nullable() + .refine((data) => !!data, { + message: 'Please select a Subscribe Plan', + }), + }) + .refine((field) => field.password === field.rePassword, { + message: 'Kata sandi tidak sama', + path: ['rePassword'], + }) + +export type TRegisterSchema = z.infer + +export const DialogRegister = () => { + const { + setIsLoginOpen, + setIsRegisterOpen, + setIsSuccessOpen, + isRegisterOpen, + } = useNewsContext() + const [error, setError] = useState() + const fetcher = useFetcher() + const loaderData = useRouteLoaderData('routes/_news') + const { subscribePlanData: subscribePlan } = loaderData || {} + + const formMethods = useRemixForm({ + mode: 'onSubmit', + fetcher, + resolver: zodResolver(registerSchema), + }) + + const { handleSubmit, control } = formMethods + + useEffect(() => { + if (!fetcher.data?.success) { + setError(fetcher.data?.message) + return + } + + setError(undefined) + setIsRegisterOpen(false) + setIsSuccessOpen('register') + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fetcher]) + + return ( + { + if (fetcher.state === 'idle') { + setIsRegisterOpen(false) + } + }} + description="Selamat Datang, silakan isi keterangan akun Anda untuk melanjutkan!" + > +
+
+ + + + + + + + + + + + + {error && ( +
{error}
+ )} + + +
+
+ + {/* Link Login */} +
+ Sudah punya akun?{' '} + +
+
+ +
+
+ ) +} diff --git a/app/layouts/news/form-subscribe-plan.tsx b/app/layouts/news/dialog-subscribe-plan.tsx similarity index 51% rename from app/layouts/news/form-subscribe-plan.tsx rename to app/layouts/news/dialog-subscribe-plan.tsx index d9c4b6d..c49b003 100644 --- a/app/layouts/news/form-subscribe-plan.tsx +++ b/app/layouts/news/dialog-subscribe-plan.tsx @@ -4,6 +4,7 @@ import { useFetcher, useRouteLoaderData } from 'react-router' import { RemixFormProvider, useRemixForm } from 'remix-hook-form' import { z } from 'zod' +import { DialogNews } from '~/components/dialog/news' import { Button } from '~/components/ui/button' import { Combobox } from '~/components/ui/combobox' import { useNewsContext } from '~/contexts/news' @@ -25,8 +26,9 @@ export const subscribeSchema = z.object({ export type TSubscribeSchema = z.infer -export const FormSubscribePlan = () => { - const { setIsSubscribeOpen, setIsSuccessOpen } = useNewsContext() +export const DialogSubscribePlan = () => { + const { setIsSubscribeOpen, setIsSuccessOpen, isSubscribeOpen } = + useNewsContext() const fetcher = useFetcher() const [error, setError] = useState() const loaderData = useRouteLoaderData('routes/_news') @@ -53,36 +55,46 @@ export const FormSubscribePlan = () => { }, [fetcher]) return ( -
- - - - - {error && ( -
{error}
- )} - - -
-
-
+ + + {error && ( +
{error}
+ )} + + + + + + ) } diff --git a/app/layouts/news/form-forgot-password.tsx b/app/layouts/news/form-forgot-password.tsx deleted file mode 100644 index 20bfd97..0000000 --- a/app/layouts/news/form-forgot-password.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Button } from '~/components/ui/button' - -export const FormForgotPassword = () => { - return ( -
-
-
- {/* Input Email / No Telepon */} -
- - -
- - {/* Tombol Masuk */} - -
-
-
- ) -} diff --git a/app/layouts/news/form-login.tsx b/app/layouts/news/form-login.tsx deleted file mode 100644 index 131d249..0000000 --- a/app/layouts/news/form-login.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod' -import { useEffect, useState } from 'react' -import { useFetcher } from 'react-router' -import { RemixFormProvider, useRemixForm } from 'remix-hook-form' -import { z } from 'zod' - -import { Button } from '~/components/ui/button' -import { Input } from '~/components/ui/input' -import { useNewsContext } from '~/contexts/news' - -export const loginSchema = z.object({ - email: z.string().email('Email tidak valid'), - password: z.string().min(6, 'Kata sandi minimal 6 karakter'), -}) - -export type TLoginSchema = z.infer - -export const FormLogin = () => { - const { - setIsRegisterOpen, - setIsLoginOpen, - setIsForgetOpen, - setIsSubscribeOpen, - } = useNewsContext() - const fetcher = useFetcher() - const [error, setError] = useState() - - const formMethods = useRemixForm({ - mode: 'onSubmit', - fetcher, - resolver: zodResolver(loginSchema), - }) - - const { handleSubmit } = formMethods - - useEffect(() => { - if (!fetcher.data?.success) { - setError(fetcher.data?.message) - return - } - - setError(undefined) - setIsLoginOpen(false) - - if (fetcher.data?.user.subscribe_plan_code === 'basic') { - setIsSubscribeOpen(true) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fetcher]) - - return ( -
-
- - - - - - - {error && ( -
{error}
- )} - -
- Lupa Kata Sandi? - -
- - -
-
- - {/* Link Daftar */} -
- Belum punya akun?{' '} - -
-
-
- ) -} diff --git a/app/layouts/news/form-register.tsx b/app/layouts/news/form-register.tsx deleted file mode 100644 index 48b0ea1..0000000 --- a/app/layouts/news/form-register.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { DevTool } from '@hookform/devtools' -import { zodResolver } from '@hookform/resolvers/zod' -import { useEffect, useState } from 'react' -import { useFetcher, useRouteLoaderData } from 'react-router' -import { RemixFormProvider, useRemixForm } from 'remix-hook-form' -import { z } from 'zod' - -import { Button } from '~/components/ui/button' -import { Combobox } from '~/components/ui/combobox' -import { Input } from '~/components/ui/input' -import { useNewsContext } from '~/contexts/news' -import type { loader } from '~/routes/_news' - -export const registerSchema = z - .object({ - email: z.string().email('Email tidak valid'), - password: z.string().min(6, 'Kata sandi minimal 6 karakter'), - rePassword: z.string().min(6, 'Kata sandi minimal 6 karakter'), - phone: z.string().min(10, 'No telepon tidak valid'), - subscribe_plan: z - .object({ - id: z.string(), - code: z.string(), - name: z.string(), - }) - .optional() - .nullable() - .refine((data) => !!data, { - message: 'Please select a Subscribe Plan', - }), - }) - .refine((field) => field.password === field.rePassword, { - message: 'Kata sandi tidak sama', - path: ['rePassword'], - }) - -export type TRegisterSchema = z.infer - -export const FormRegister = () => { - const { setIsLoginOpen, setIsRegisterOpen, setIsSuccessOpen } = - useNewsContext() - const [error, setError] = useState() - const fetcher = useFetcher() - const loaderData = useRouteLoaderData('routes/_news') - const { subscribePlanData: subscribePlan } = loaderData || {} - - const formMethods = useRemixForm({ - mode: 'onSubmit', - fetcher, - resolver: zodResolver(registerSchema), - }) - - const { handleSubmit, control } = formMethods - - useEffect(() => { - if (!fetcher.data?.success) { - setError(fetcher.data?.message) - return - } - - setError(undefined) - setIsRegisterOpen(false) - setIsSuccessOpen('register') - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fetcher]) - - return ( -
-
- - - - - - - - - - - - - {error && ( -
{error}
- )} - - -
-
- - {/* Link Login */} -
- Sudah punya akun?{' '} - -
-
- -
- ) -} diff --git a/app/routes/actions.login.ts b/app/routes/actions.login.ts index 4bb209a..f4566c9 100644 --- a/app/routes/actions.login.ts +++ b/app/routes/actions.login.ts @@ -5,7 +5,7 @@ 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 { loginSchema, type TLoginSchema } from '~/layouts/news/dialog-login' import { generateUserTokenCookie } from '~/utils/token' import type { Route } from './+types/actions.login' diff --git a/app/routes/actions.register.ts b/app/routes/actions.register.ts index 48ac35d..dcc1823 100644 --- a/app/routes/actions.register.ts +++ b/app/routes/actions.register.ts @@ -8,7 +8,7 @@ import { userRegisterRequest } from '~/apis/news/register-user' import { registerSchema, type TRegisterSchema, -} from '~/layouts/news/form-register' +} from '~/layouts/news/dialog-register' import { generateUserTokenCookie } from '~/utils/token' import type { Route } from './+types/actions.register' diff --git a/app/routes/actions.subscribe.ts b/app/routes/actions.subscribe.ts index 355c1e4..370e1d6 100644 --- a/app/routes/actions.subscribe.ts +++ b/app/routes/actions.subscribe.ts @@ -8,7 +8,7 @@ import { getUser } from '~/apis/news/get-user' import { subscribeSchema, type TSubscribeSchema, -} from '~/layouts/news/form-subscribe-plan' +} from '~/layouts/news/dialog-subscribe-plan' import { handleCookie } from '~/libs/cookies' import type { Route } from './+types/actions.subscribe'