From d3f0dac69f14c56ddc705844e3712819f096c70d Mon Sep 17 00:00:00 2001 From: "fredy.siswanto" Date: Mon, 10 Mar 2025 19:34:51 +0700 Subject: [PATCH] feat: add create and update subscription plan APIs and corresponding forms --- app/apis/admin/create-subscribe-plan.ts | 28 +++++ app/apis/admin/update-subscribe-plan.ts | 29 +++++ app/pages/form-subscriptions-plan/index.tsx | 109 ++++++++++++++++++ ...admin._dashboard.subscribe-plan.create.tsx | 4 + ...n._dashboard.subscribe-plan.update.$id.tsx | 20 ++++ .../actions.admin.subscribe-plan.create.ts | 67 +++++++++++ .../actions.admin.subscribe-plan.update.ts | 65 +++++++++++ 7 files changed, 322 insertions(+) create mode 100644 app/apis/admin/create-subscribe-plan.ts create mode 100644 app/apis/admin/update-subscribe-plan.ts create mode 100644 app/pages/form-subscriptions-plan/index.tsx create mode 100644 app/routes/_admin.lg-admin._dashboard.subscribe-plan.create.tsx create mode 100644 app/routes/_admin.lg-admin._dashboard.subscribe-plan.update.$id.tsx create mode 100644 app/routes/actions.admin.subscribe-plan.create.ts create mode 100644 app/routes/actions.admin.subscribe-plan.update.ts diff --git a/app/apis/admin/create-subscribe-plan.ts b/app/apis/admin/create-subscribe-plan.ts new file mode 100644 index 0000000..8035263 --- /dev/null +++ b/app/apis/admin/create-subscribe-plan.ts @@ -0,0 +1,28 @@ +import { z } from 'zod' + +import { HttpServer, type THttpServer } from '~/libs/http-server' +import type { TSubscribePlanSchema } from '~/pages/form-subscriptions-plan' + +const subscribePlanResponseSchema = z.object({ + data: z.object({ + Message: z.string(), + }), +}) + +type TParameters = { + payload: TSubscribePlanSchema +} & THttpServer + +export const createSubscribePlanRequest = async (parameters: TParameters) => { + const { payload, ...restParameters } = parameters + try { + const { data } = await HttpServer(restParameters).post( + '/api/subscribe-plan/create', + payload, + ) + return subscribePlanResponseSchema.parse(data) + } catch (error) { + // eslint-disable-next-line unicorn/no-useless-promise-resolve-reject + return Promise.reject(error) + } +} diff --git a/app/apis/admin/update-subscribe-plan.ts b/app/apis/admin/update-subscribe-plan.ts new file mode 100644 index 0000000..4676e28 --- /dev/null +++ b/app/apis/admin/update-subscribe-plan.ts @@ -0,0 +1,29 @@ +import { z } from 'zod' + +import { HttpServer, type THttpServer } from '~/libs/http-server' +import type { TSubscribePlanSchema } from '~/pages/form-subscriptions-plan' + +const subscribePlanResponseSchema = z.object({ + data: z.object({ + Message: z.string(), + }), +}) + +type TParameters = { + payload: TSubscribePlanSchema +} & THttpServer + +export const updateSubscribePlanRequest = async (parameters: TParameters) => { + const { payload, ...restParameters } = parameters + const { id, ...restPayload } = payload + try { + const { data } = await HttpServer(restParameters).put( + `/api/subscribe-plan/${id}/update`, + restPayload, + ) + return subscribePlanResponseSchema.parse(data) + } catch (error) { + // eslint-disable-next-line unicorn/no-useless-promise-resolve-reject + return Promise.reject(error) + } +} diff --git a/app/pages/form-subscriptions-plan/index.tsx b/app/pages/form-subscriptions-plan/index.tsx new file mode 100644 index 0000000..0310583 --- /dev/null +++ b/app/pages/form-subscriptions-plan/index.tsx @@ -0,0 +1,109 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { useEffect, useState } from 'react' +import { useFetcher, useNavigate } 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 { TitleDashboard } from '~/components/ui/title-dashboard' +import { urlFriendlyCode } from '~/utils/formatter' + +export const createSubscribePlanSchema = z.object({ + id: z.string().optional(), + name: z.string().min(3, 'Nama minimal 3 karakter'), + code: z.string(), +}) +export type TSubscribePlanSchema = z.infer +type TProperties = { + subscribePlanData?: TSubscribePlanSchema +} + +export const FormSubscribePlanPage = (properties: TProperties) => { + const { subscribePlanData } = properties || {} + const fetcher = useFetcher() + const navigate = useNavigate() + const formMethods = useRemixForm({ + mode: 'onSubmit', + fetcher, + resolver: zodResolver(createSubscribePlanSchema), + values: { + id: subscribePlanData?.id || undefined, + code: subscribePlanData?.code || '', + name: subscribePlanData?.name || '', + }, + }) + const [error, setError] = useState() + const [disabled, setDisabled] = useState(false) + + const { handleSubmit, watch, setValue } = formMethods + const watchName = watch('name') + + useEffect(() => { + if (!fetcher.data?.success) { + setError(fetcher.data?.message) + setDisabled(false) + return + } + navigate('/lg-admin/subscribe-plan') + setDisabled(true) + setError(undefined) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fetcher]) + + useEffect(() => { + setValue('code', urlFriendlyCode(watchName)) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [watchName]) + + return ( +
+ +
+ + + {error && ( +
{error}
+ )} +
+ + + +
+
+
+
+
+ ) +} diff --git a/app/routes/_admin.lg-admin._dashboard.subscribe-plan.create.tsx b/app/routes/_admin.lg-admin._dashboard.subscribe-plan.create.tsx new file mode 100644 index 0000000..30fc4bd --- /dev/null +++ b/app/routes/_admin.lg-admin._dashboard.subscribe-plan.create.tsx @@ -0,0 +1,4 @@ +import { FormSubscribePlanPage } from '~/pages/form-subscriptions-plan' + +const DashboardSubscribePlanCreateLayout = () => +export default DashboardSubscribePlanCreateLayout diff --git a/app/routes/_admin.lg-admin._dashboard.subscribe-plan.update.$id.tsx b/app/routes/_admin.lg-admin._dashboard.subscribe-plan.update.$id.tsx new file mode 100644 index 0000000..ffb981c --- /dev/null +++ b/app/routes/_admin.lg-admin._dashboard.subscribe-plan.update.$id.tsx @@ -0,0 +1,20 @@ +import { getSubscriptions } from '~/apis/common/get-subscriptions' +import { FormSubscribePlanPage } from '~/pages/form-subscriptions-plan' + +import type { Route } from './+types/_admin.lg-admin._dashboard.subscribe-plan.update.$id' + +export const loader = async ({ params }: Route.LoaderArgs) => { + const { data: subscribePlansData } = await getSubscriptions() + const subscribePlanData = subscribePlansData.find( + (subscribePlan) => subscribePlan.id === params.id, + ) + return { subscribePlanData } +} + +const DashboardSubscribePlanUpdateLayout = ({ + loaderData, +}: Route.ComponentProps) => { + const { subscribePlanData } = loaderData || {} + return +} +export default DashboardSubscribePlanUpdateLayout diff --git a/app/routes/actions.admin.subscribe-plan.create.ts b/app/routes/actions.admin.subscribe-plan.create.ts new file mode 100644 index 0000000..2ac4716 --- /dev/null +++ b/app/routes/actions.admin.subscribe-plan.create.ts @@ -0,0 +1,67 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { data } from 'react-router' +import { getValidatedFormData } from 'remix-hook-form' +import { XiorError } from 'xior' + +import { createSubscribePlanRequest } from '~/apis/admin/create-subscribe-plan' +import { handleCookie } from '~/libs/cookies' +import { + createSubscribePlanSchema, + type TSubscribePlanSchema, +} from '~/pages/form-subscriptions-plan' + +import type { Route } from './+types/actions.register' + +export const action = async ({ request }: Route.ActionArgs) => { + const { staffToken } = await handleCookie(request) + try { + const { + errors, + data: payload, + receivedValues: defaultValues, + } = await getValidatedFormData( + request, + zodResolver(createSubscribePlanSchema), + false, + ) + + if (errors) { + return data({ success: false, errors, defaultValues }, { status: 400 }) + } + + const { data: tagsData } = await createSubscribePlanRequest({ + accessToken: staffToken, + payload, + }) + + return data( + { + success: true, + tagsData, + }, + { + 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 }, + ) + } +} diff --git a/app/routes/actions.admin.subscribe-plan.update.ts b/app/routes/actions.admin.subscribe-plan.update.ts new file mode 100644 index 0000000..4146c8f --- /dev/null +++ b/app/routes/actions.admin.subscribe-plan.update.ts @@ -0,0 +1,65 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { data } from 'react-router' +import { getValidatedFormData } from 'remix-hook-form' +import { XiorError } from 'xior' + +import { updateSubscribePlanRequest } from '~/apis/admin/update-subscribe-plan' +import { handleCookie } from '~/libs/cookies' +import type { TSubscribePlanSchema } from '~/pages/form-subscriptions-plan' +import { createTagSchema } from '~/pages/form-tag' + +import type { Route } from './+types/actions.register' + +export const action = async ({ request }: Route.ActionArgs) => { + const { staffToken } = await handleCookie(request) + try { + const { + errors, + data: payload, + receivedValues: defaultValues, + } = await getValidatedFormData( + request, + zodResolver(createTagSchema), + false, + ) + + if (errors) { + return data({ success: false, errors, defaultValues }, { status: 400 }) + } + + const { data: tagData } = await updateSubscribePlanRequest({ + accessToken: staffToken, + payload, + }) + + return data( + { + success: true, + tagData, + }, + { + 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 }, + ) + } +}