diff --git a/app/apis/admin/create-tags.ts b/app/apis/admin/create-tags.ts new file mode 100644 index 0000000..e6bb3f7 --- /dev/null +++ b/app/apis/admin/create-tags.ts @@ -0,0 +1,34 @@ +import { z } from 'zod' + +import { HttpServer } from '~/libs/http-server' +import type { TTagSchema } from '~/pages/dashboard-tags-create' + +const tagsResponseSchema = z.object({ + data: z.object({ + Message: z.string(), + }), +}) + +type TParameters = { + accessToken: string + payload: TTagSchema +} + +export const createTagsRequest = async (parameters: TParameters) => { + const { accessToken, payload } = parameters + try { + const { ...restPayload } = payload + const transformedPayload = { + ...restPayload, + } + + const { data } = await HttpServer({ accessToken }).post( + '/api/tag/create', + transformedPayload, + ) + return tagsResponseSchema.parse(data) + } catch (error) { + // eslint-disable-next-line unicorn/no-useless-promise-resolve-reject + return Promise.reject(error) + } +} diff --git a/app/pages/dashboard-category-create/index.tsx b/app/pages/dashboard-category-create/index.tsx index 920b463..86721fc 100644 --- a/app/pages/dashboard-category-create/index.tsx +++ b/app/pages/dashboard-category-create/index.tsx @@ -1,12 +1,13 @@ import { zodResolver } from '@hookform/resolvers/zod' import { useEffect, useState, type ChangeEvent } from 'react' -import { useFetcher } from 'react-router' +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 createCategorySchema = z.object({ code: z.string().min(3, 'Kode minimal 3 karakter'), @@ -16,6 +17,7 @@ export type TCategorySchema = z.infer export const CreateCategoryPage = () => { const fetcher = useFetcher() + const navigate = useNavigate() const formMethods = useRemixForm({ mode: 'onSubmit', fetcher, @@ -34,17 +36,16 @@ export const CreateCategoryPage = () => { setDisabled(false) return } + navigate('/lg-admin/categories') + setDisabled(true) + setError(undefined) // eslint-disable-next-line react-hooks/exhaustive-deps }, [fetcher]) const handelCodeAuto = (name: ChangeEvent) => { const inputName = name.target.value setName(inputName) - const urlFriendlyCode = inputName - .toLowerCase() - .replaceAll(/\s+/g, '-') - .replaceAll(/[^\w-]+/g, '') - setCode(urlFriendlyCode) + setCode(urlFriendlyCode(inputName)) } return ( @@ -55,7 +56,7 @@ export const CreateCategoryPage = () => { {error && ( diff --git a/app/pages/dashboard-tags-create/index.tsx b/app/pages/dashboard-tags-create/index.tsx new file mode 100644 index 0000000..8d8dba4 --- /dev/null +++ b/app/pages/dashboard-tags-create/index.tsx @@ -0,0 +1,102 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { useEffect, useState, type ChangeEvent } 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 createTagsSchema = z.object({ + code: z.string().min(3, 'Kode minimal 3 karakter'), + name: z.string().min(3, 'Nama minimal 3 karakter'), +}) +export type TTagSchema = z.infer + +export const CreateTagsPage = () => { + const fetcher = useFetcher() + const navigate = useNavigate() + const formMethods = useRemixForm({ + mode: 'onSubmit', + fetcher, + resolver: zodResolver(createTagsSchema), + }) + const [error, setError] = useState() + const [disabled, setDisabled] = useState(false) + const [code, setCode] = useState('') + const [name, setName] = useState('') + + const { handleSubmit } = formMethods + + useEffect(() => { + if (!fetcher.data?.success) { + setError(fetcher.data?.message) + setDisabled(false) + return + } + navigate('/lg-admin/tags') + setDisabled(true) + setError(undefined) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fetcher]) + + const handelCodeAuto = (name: ChangeEvent) => { + const inputName = name.target.value + setName(inputName) + setCode(urlFriendlyCode(name.target.value)) + } + + return ( +
+ +
+ + + {error && ( +
{error}
+ )} +
+ + + +
+
+
+
+
+ ) +} diff --git a/app/routes/_admin.lg-admin._dashboard.tags.create.tsx b/app/routes/_admin.lg-admin._dashboard.tags.create.tsx new file mode 100644 index 0000000..8e3448a --- /dev/null +++ b/app/routes/_admin.lg-admin._dashboard.tags.create.tsx @@ -0,0 +1,4 @@ +import { CreateTagsPage } from '~/pages/dashboard-tags-create' + +const DashboardTagsCreateLayout = () => +export default DashboardTagsCreateLayout diff --git a/app/routes/actions.admin.tags.create.ts b/app/routes/actions.admin.tags.create.ts new file mode 100644 index 0000000..d6ad20f --- /dev/null +++ b/app/routes/actions.admin.tags.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 { createTagsRequest } from '~/apis/admin/create-tags' +import { handleCookie } from '~/libs/cookies' +import { + createTagsSchema, + type TTagSchema, +} from '~/pages/dashboard-tags-create' + +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(createTagsSchema), + false, + ) + + if (errors) { + return data({ success: false, errors, defaultValues }, { status: 400 }) + } + + const { data: tagsData } = await createTagsRequest({ + 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/utils/formatter.ts b/app/utils/formatter.ts index db00613..2463f49 100644 --- a/app/utils/formatter.ts +++ b/app/utils/formatter.ts @@ -9,3 +9,11 @@ export const formatDate = (isoDate: string): string => { year: 'numeric', }).format(date) } + +export const urlFriendlyCode = (input: string) => { + return input + .trim() + .toLowerCase() + .replaceAll(/\s+/g, '-') + .replaceAll(/[^\w-]+/g, '') +}