From 26026572012283dcbff3e931c7cdf248fb031d22 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Wed, 2 Apr 2025 22:42:02 +0700 Subject: [PATCH 01/37] feat(backoffice): send login request - buat .env untuk menyimpan API dan memberikan .env.example juga untuk contohnya. Tambah .gitignore juga - tambah fungsi login pada Backoffice --- .env.example | 1 + .gitignore | 4 ++- apps/backoffice/src/app/page.tsx | 55 +++++++++++++++++++++--------- libs/service/src/api/auth/index.ts | 2 +- 4 files changed, 44 insertions(+), 18 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..49d9f6e --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +VITE_API_URL=https://api.example.com diff --git a/.gitignore b/.gitignore index 77912e8..0ddc0e9 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,6 @@ Thumbs.db vite.config.*.timestamp* vitest.config.*.timestamp* -storybook-static \ No newline at end of file +storybook-static + +.env diff --git a/apps/backoffice/src/app/page.tsx b/apps/backoffice/src/app/page.tsx index b92bf58..7ba7fee 100644 --- a/apps/backoffice/src/app/page.tsx +++ b/apps/backoffice/src/app/page.tsx @@ -1,10 +1,27 @@ -import { FC, ReactElement } from 'react'; +import { FC, ReactElement, useState } from 'react'; import { useNavigate } from 'react-router'; import { Button } from '@imphnen-frontend-service/ui/atoms'; import { InputForm } from '@imphnen-frontend-service/ui/molecules'; +import { postLogin } from '@imphnen-frontend-service/service'; export const Components: FC = (): ReactElement => { const navigate = useNavigate(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + try { + const payload = { email, password }; + console.log(payload); // for debugging + const response = await postLogin(payload); + + console.log('Login successful:', response); // for debugging + navigate('/dashboard'); + } catch (error) { + console.log('Login error:', error); // for debugging + } + }; return (
@@ -13,21 +30,27 @@ export const Components: FC = (): ReactElement => {

Welcome to IMPHNEN Backoffice

- - - +
+ setEmail(e.target.value)} + /> + setPassword(e.target.value)} + /> + +
); diff --git a/libs/service/src/api/auth/index.ts b/libs/service/src/api/auth/index.ts index e925dd1..ded271b 100644 --- a/libs/service/src/api/auth/index.ts +++ b/libs/service/src/api/auth/index.ts @@ -12,7 +12,7 @@ export const postLogin = async ( ): Promise => { const { data } = await api({ method: 'POST', - url: '/auth/login', + url: '/v1/auth/login', data: payload, }); return data; From a9cc728ade1f9a037b1d9b01fa247873b8523158 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Thu, 3 Apr 2025 06:10:07 +0700 Subject: [PATCH 02/37] feat(backoffice): implement login and logout - Tambah fitur login dan logout pada Backoffice - Ubah port development server dan atasi CORS - Update type login response - Update component Input agar toggle password berfungsi dengan benar --- apps/backoffice/src/app/page.tsx | 5 +++++ apps/backoffice/vite.config.ts | 4 ++-- libs/service/src/types/auth/index.ts | 18 ++++++++++++++++++ libs/ui/src/atoms/input/input.tsx | 4 +++- .../backoffice-sidebar/backoffice-sidebar.tsx | 11 ++++++++--- 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/apps/backoffice/src/app/page.tsx b/apps/backoffice/src/app/page.tsx index 7ba7fee..dc8a0c8 100644 --- a/apps/backoffice/src/app/page.tsx +++ b/apps/backoffice/src/app/page.tsx @@ -16,6 +16,10 @@ export const Components: FC = (): ReactElement => { console.log(payload); // for debugging const response = await postLogin(payload); + const { access_token, refresh_token } = response.data.token; + sessionStorage.setItem('access_token', access_token); + localStorage.setItem('refresh_token', refresh_token); + console.log('Login successful:', response); // for debugging navigate('/dashboard'); } catch (error) { @@ -39,6 +43,7 @@ export const Components: FC = (): ReactElement => { className="w-full" value={email} onChange={(e) => setEmail(e.target.value)} + autoFocus /> ({ root: __dirname, cacheDir: '../../node_modules/.vite/apps/backoffice', server: { - port: 4200, + port: 3000, host: 'localhost', }, preview: { - port: 4300, + port: 3001, host: 'localhost', }, plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], diff --git a/libs/service/src/types/auth/index.ts b/libs/service/src/types/auth/index.ts index bf094ec..03fc5a7 100644 --- a/libs/service/src/types/auth/index.ts +++ b/libs/service/src/types/auth/index.ts @@ -10,9 +10,27 @@ export type TLoginResponse = { refresh_token: string; }; user: { + role: { + id: string; + name: string; + permission: [ + { + id: string; + name: string; + created_at: string; + updated_at: string; + } + ] + created_at: string; + updated_at: string; + }; fullname: string; email: string; + avatar: string; + phone_number: string; is_active: boolean; + gender: string; + birthdate: string; }; }; }; diff --git a/libs/ui/src/atoms/input/input.tsx b/libs/ui/src/atoms/input/input.tsx index ae0e849..e780059 100644 --- a/libs/ui/src/atoms/input/input.tsx +++ b/libs/ui/src/atoms/input/input.tsx @@ -40,7 +40,8 @@ export const Input: FC = ({ }): ReactElement => { const [showPassword, setShowPassword] = useState(false); // State for password visibility - const togglePasswordVisibility = () => { + const togglePasswordVisibility = (e: React.FormEvent) => { + e.preventDefault(); if (!disabled) setShowPassword((prev) => !prev); }; @@ -63,6 +64,7 @@ export const Input: FC = ({ {type === 'password' && (
-
+
Merch 2
@@ -163,7 +146,7 @@ export const Components: FC = (): ReactElement => {
- {/* Roulette Page */} +
{
-
+
Here Take Your Prize
@@ -223,7 +203,7 @@ export const Components: FC = (): ReactElement => { Spin Now
- {/* TODO: Change using real button trigger */} +
- {/* Diffuser & Cloud */}
- {/* Login Modal */} setShowModalLogin(false)} @@ -247,7 +225,6 @@ export const Components: FC = (): ReactElement => { key="login" /> - {/* Register Modal */} { @@ -256,7 +233,6 @@ export const Components: FC = (): ReactElement => { key="register" /> - {/* Forgot Password Modal */} { From a6fea630a44feddb8b3ee231e7c9a53f715c6c73 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Thu, 3 Apr 2025 12:21:10 +0700 Subject: [PATCH 04/37] feat: login example --- .env | 1 + .../form/modal-form-forgot-password.tsx | 19 +- .../app/_components/form/modal-form-login.tsx | 74 +- .../_components/form/modal-form-register.tsx | 16 +- .../src/app/_components/item/gacha-item.tsx | 29 + apps/gacha/src/app/_hooks/use-login.ts | 26 + apps/gacha/src/app/page.tsx | 29 +- libs/service/src/api/auth/index.ts | 2 +- libs/service/src/api/index.ts | 8 + libs/service/src/hooks/auth/index.ts | 7 + libs/service/src/index.ts | 1 + libs/service/src/schemas/auth/index.ts | 9 + libs/service/src/schemas/index.ts | 1 + libs/service/src/types/auth/index.ts | 8 +- libs/service/src/types/index.ts | 2 + libs/service/src/types/permissions/index.ts | 6 + libs/service/src/types/roles/index.ts | 9 + libs/service/src/types/users/index.ts | 20 +- libs/ui/src/molecules/index.ts | 2 +- libs/ui/src/molecules/input-field/index.ts | 1 + .../input-field.spec.tsx} | 6 +- .../input-field.stories.tsx} | 6 +- .../input-field.tsx} | 12 +- libs/ui/src/molecules/input-form/index.ts | 1 - .../controlled-input-field.tsx | 21 + .../src/organisms/controlled-field/index.ts | 1 + libs/ui/src/organisms/index.ts | 11 +- libs/ui/src/organisms/navbar/navbar.spec.tsx | 4 +- libs/ui/src/organisms/navbar/navbar.tsx | 53 +- libs/utils/src/axios/api.ts | 7 - libs/utils/src/axios/index.ts | 1 - libs/utils/src/hooks/index.ts | 2 + .../utils}/src/hooks/use-query-state.ts | 0 libs/utils/src/hooks/use-session.ts | 22 + libs/utils/src/index.ts | 3 +- libs/utils/src/local-storage/index.ts | 60 + package-lock.json | 1207 ++++++++--------- package.json | 10 +- 38 files changed, 942 insertions(+), 755 deletions(-) create mode 100644 .env create mode 100644 apps/gacha/src/app/_components/item/gacha-item.tsx create mode 100644 apps/gacha/src/app/_hooks/use-login.ts create mode 100644 libs/service/src/schemas/auth/index.ts create mode 100644 libs/service/src/schemas/index.ts create mode 100644 libs/service/src/types/permissions/index.ts create mode 100644 libs/service/src/types/roles/index.ts create mode 100644 libs/ui/src/molecules/input-field/index.ts rename libs/ui/src/molecules/{input-form/input-form.spec.tsx => input-field/input-field.spec.tsx} (75%) rename libs/ui/src/molecules/{input-form/input-form.stories.tsx => input-field/input-field.stories.tsx} (96%) rename libs/ui/src/molecules/{input-form/input-form.tsx => input-field/input-field.tsx} (90%) delete mode 100644 libs/ui/src/molecules/input-form/index.ts create mode 100644 libs/ui/src/organisms/controlled-field/controlled-input-field.tsx create mode 100644 libs/ui/src/organisms/controlled-field/index.ts delete mode 100644 libs/utils/src/axios/api.ts delete mode 100644 libs/utils/src/axios/index.ts create mode 100644 libs/utils/src/hooks/index.ts rename {apps/gacha => libs/utils}/src/hooks/use-query-state.ts (100%) create mode 100644 libs/utils/src/hooks/use-session.ts create mode 100644 libs/utils/src/local-storage/index.ts diff --git a/.env b/.env new file mode 100644 index 0000000..8b453ae --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_API_URL=https://api.imphnen.dev/v1 \ No newline at end of file diff --git a/apps/gacha/src/app/_components/form/modal-form-forgot-password.tsx b/apps/gacha/src/app/_components/form/modal-form-forgot-password.tsx index 4f42d91..9d0c14e 100644 --- a/apps/gacha/src/app/_components/form/modal-form-forgot-password.tsx +++ b/apps/gacha/src/app/_components/form/modal-form-forgot-password.tsx @@ -1,11 +1,11 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; import { - InputForm, + InputField, Modal, Stepper, } from '@imphnen-frontend-service/ui/molecules'; -import { useQueryState } from '../../../hooks/use-query-state'; - +import { useQueryState } from '@imphnen-frontend-service/utils'; +import { Fragment } from 'react/jsx-runtime'; interface IModalFormForgotPasswordProps { isOpen: boolean; onClose: () => void; @@ -62,7 +62,7 @@ interface IStepOneProps { const StepOne = ({ nextStep, onClose }: IStepOneProps) => ( <> - ( - <> - {/* TODO: Change component using OTP Input */} - + ( Reset Password
- + ); interface IStepThreeProps { @@ -118,14 +117,14 @@ interface IStepThreeProps { const StepThree = ({ onClose, resetStep }: IStepThreeProps) => ( <> - - { + const { form, onSubmit } = useLogin(); + return ( - - - -

- +

+ - - -
-

Belum punya akun?

- - Daftar - -
+
+

Belum punya akun?

+ + Daftar + +
+
); diff --git a/apps/gacha/src/app/_components/form/modal-form-register.tsx b/apps/gacha/src/app/_components/form/modal-form-register.tsx index 3873c1d..2a9c385 100644 --- a/apps/gacha/src/app/_components/form/modal-form-register.tsx +++ b/apps/gacha/src/app/_components/form/modal-form-register.tsx @@ -1,6 +1,6 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules'; -import { useQueryState } from '../../../hooks/use-query-state'; +import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules'; +import { useQueryState } from '@imphnen-frontend-service/utils'; interface IModalFormRegisterProps { isOpen: boolean; @@ -54,28 +54,28 @@ interface IStepOneProps { const StepOne = ({ nextStep, onClose }: IStepOneProps) => ( <> - - - - ( <> - - = ({ + src, + label, + className, +}): ReactElement => { + return ( +
+
+ Banner +
+

{label}

+
+ ); +}; diff --git a/apps/gacha/src/app/_hooks/use-login.ts b/apps/gacha/src/app/_hooks/use-login.ts new file mode 100644 index 0000000..272b2ba --- /dev/null +++ b/apps/gacha/src/app/_hooks/use-login.ts @@ -0,0 +1,26 @@ +import { useForm } from 'react-hook-form'; +import { + authLoginSchema, + TLoginRequest, + usePostLogin, +} from '@imphnen-frontend-service/service'; +import { zodResolver } from '@hookform/resolvers/zod'; + +export const useLogin = () => { + const postLogin = usePostLogin(); + const form = useForm({ + resolver: zodResolver(authLoginSchema), + mode: 'all', + }); + + const onSubmit = form.handleSubmit((data) => { + postLogin.mutate(data, { + onSuccess: () => console.log('Success Login'), + }); + }); + + return { + form, + onSubmit, + }; +}; diff --git a/apps/gacha/src/app/page.tsx b/apps/gacha/src/app/page.tsx index 83f7a7f..c693317 100644 --- a/apps/gacha/src/app/page.tsx +++ b/apps/gacha/src/app/page.tsx @@ -1,37 +1,10 @@ import { ArrowDownOutlined } from '@ant-design/icons'; import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { cn } from '@imphnen-frontend-service/utils'; 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'; - -interface GachaItemProps { - src: string; - label: string; - className?: string; -} - -const GachaItem: FC = ({ - src, - label, - className, -}): ReactElement => { - return ( -
-
- Banner -
-

{label}

-
- ); -}; +import { GachaItem } from './_components/item/gacha-item'; export const Components: FC = (): ReactElement => { const [showModalForgotPassword, setShowModalForgotPassword] = useState(false); diff --git a/libs/service/src/api/auth/index.ts b/libs/service/src/api/auth/index.ts index e925dd1..0241638 100644 --- a/libs/service/src/api/auth/index.ts +++ b/libs/service/src/api/auth/index.ts @@ -1,4 +1,4 @@ -import { api } from '@imphnen-frontend-service/utils'; +import { api } from '../'; import { TLoginRequest, TLoginResponse, diff --git a/libs/service/src/api/index.ts b/libs/service/src/api/index.ts index 76d0cf2..7745a7c 100644 --- a/libs/service/src/api/index.ts +++ b/libs/service/src/api/index.ts @@ -1,3 +1,11 @@ +import axios, { AxiosRequestConfig } from 'axios'; + export * from './auth'; export * from './gacha'; export * from './users'; + +const config: AxiosRequestConfig = { + baseURL: import.meta.env.VITE_API_URL, +}; + +export const api = axios.create(config); diff --git a/libs/service/src/hooks/auth/index.ts b/libs/service/src/hooks/auth/index.ts index dda436e..d4fd99e 100644 --- a/libs/service/src/hooks/auth/index.ts +++ b/libs/service/src/hooks/auth/index.ts @@ -6,6 +6,8 @@ import { TRegisterRequest, TVerifyEmailRequest, } from '../../types/auth'; +import { SessionToken, SessionUser } from '@imphnen-frontend-service/utils'; + import { TResponseError, TResponseMessage } from '../../types/common'; export const usePostLogin = (): UseMutationResult< @@ -17,6 +19,11 @@ export const usePostLogin = (): UseMutationResult< return useMutation({ mutationKey: ['post-login'], mutationFn: async (payload) => await postLogin(payload), + onSuccess: (res) => { + SessionUser.set(res.data.user); + SessionToken.set(res.data.token); + window.location.reload(); + }, }); }; diff --git a/libs/service/src/index.ts b/libs/service/src/index.ts index 6c2f44a..5314ca1 100644 --- a/libs/service/src/index.ts +++ b/libs/service/src/index.ts @@ -1,3 +1,4 @@ export * from './api'; export * from './hooks'; export * from './types'; +export * from './schemas'; diff --git a/libs/service/src/schemas/auth/index.ts b/libs/service/src/schemas/auth/index.ts new file mode 100644 index 0000000..4da256e --- /dev/null +++ b/libs/service/src/schemas/auth/index.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; + +export const authLoginSchema = z.object({ + email: z + .string() + .min(1, 'Email cannot be empty') + .email('Email must be valid'), + password: z.string().min(1, 'Password cannot be empty'), +}); diff --git a/libs/service/src/schemas/index.ts b/libs/service/src/schemas/index.ts new file mode 100644 index 0000000..269586e --- /dev/null +++ b/libs/service/src/schemas/index.ts @@ -0,0 +1 @@ +export * from './auth'; diff --git a/libs/service/src/types/auth/index.ts b/libs/service/src/types/auth/index.ts index bf094ec..031773c 100644 --- a/libs/service/src/types/auth/index.ts +++ b/libs/service/src/types/auth/index.ts @@ -1,3 +1,5 @@ +import { TUserItem } from '../users'; + export type TLoginRequest = { email: string; password: string; @@ -9,11 +11,7 @@ export type TLoginResponse = { access_token: string; refresh_token: string; }; - user: { - fullname: string; - email: string; - is_active: boolean; - }; + user: TUserItem; }; }; diff --git a/libs/service/src/types/index.ts b/libs/service/src/types/index.ts index 76d0cf2..5256155 100644 --- a/libs/service/src/types/index.ts +++ b/libs/service/src/types/index.ts @@ -1,3 +1,5 @@ export * from './auth'; export * from './gacha'; export * from './users'; +export * from './roles'; +export * from './permissions'; diff --git a/libs/service/src/types/permissions/index.ts b/libs/service/src/types/permissions/index.ts new file mode 100644 index 0000000..66e4d7f --- /dev/null +++ b/libs/service/src/types/permissions/index.ts @@ -0,0 +1,6 @@ +export type TPermissionItem = { + id: string; + name: string; + created_at: string; + updated_at: string; +}; diff --git a/libs/service/src/types/roles/index.ts b/libs/service/src/types/roles/index.ts new file mode 100644 index 0000000..9a51667 --- /dev/null +++ b/libs/service/src/types/roles/index.ts @@ -0,0 +1,9 @@ +import { TPermissionItem } from '../permissions'; + +export type TRoleItem = { + id: string; + name: string; + created_at: string; + updated_at: string; + permissions: TPermissionItem[]; +}; diff --git a/libs/service/src/types/users/index.ts b/libs/service/src/types/users/index.ts index cb0ff5c..8b6abae 100644 --- a/libs/service/src/types/users/index.ts +++ b/libs/service/src/types/users/index.ts @@ -1 +1,19 @@ -export {}; +import { TRoleItem } from '../roles'; + +export type TUserItem = { + id: string; + avatar: string; + birthdate: string; + email: string; + fullname: string; + gender: string; + identity_number: string; + is_active: boolean; + is_profile_completed: boolean; + phone_number: string; + referral_code: string; + referred_by: string; + religion: string; + student_type: string; + role: TRoleItem; +}; diff --git a/libs/ui/src/molecules/index.ts b/libs/ui/src/molecules/index.ts index b822815..1bc94a6 100644 --- a/libs/ui/src/molecules/index.ts +++ b/libs/ui/src/molecules/index.ts @@ -1,6 +1,6 @@ export * from './forgot-step'; export * from './otp-form'; -export * from './input-form'; +export * from './input-field'; export * from './pagination'; export * from './modal/modal'; export * from './stepper'; diff --git a/libs/ui/src/molecules/input-field/index.ts b/libs/ui/src/molecules/input-field/index.ts new file mode 100644 index 0000000..6ab3377 --- /dev/null +++ b/libs/ui/src/molecules/input-field/index.ts @@ -0,0 +1 @@ +export * from './input-field'; diff --git a/libs/ui/src/molecules/input-form/input-form.spec.tsx b/libs/ui/src/molecules/input-field/input-field.spec.tsx similarity index 75% rename from libs/ui/src/molecules/input-form/input-form.spec.tsx rename to libs/ui/src/molecules/input-field/input-field.spec.tsx index f0dc650..7ed60dc 100644 --- a/libs/ui/src/molecules/input-form/input-form.spec.tsx +++ b/libs/ui/src/molecules/input-field/input-field.spec.tsx @@ -1,9 +1,9 @@ import { render, screen } from '@testing-library/react'; -import InputForm from './input-form'; +import { InputField } from './input-field'; describe('InputForm Component', () => { it('renders correctly with disabled prop', () => { - render(); + render(); const input = screen.getByLabelText('Test Label'); expect(input).toBeDisabled(); @@ -11,7 +11,7 @@ describe('InputForm Component', () => { }); it('renders correctly without disabled prop', () => { - render(); + render(); const input = screen.getByLabelText('Test Label'); expect(input).not.toBeDisabled(); diff --git a/libs/ui/src/molecules/input-form/input-form.stories.tsx b/libs/ui/src/molecules/input-field/input-field.stories.tsx similarity index 96% rename from libs/ui/src/molecules/input-form/input-form.stories.tsx rename to libs/ui/src/molecules/input-field/input-field.stories.tsx index eae33dc..25c925b 100644 --- a/libs/ui/src/molecules/input-form/input-form.stories.tsx +++ b/libs/ui/src/molecules/input-field/input-field.stories.tsx @@ -1,9 +1,9 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { InputForm } from './input-form'; +import { InputField } from './input-field'; const meta = { title: 'Molecules/Input Form', - component: InputForm, + component: InputField, parameters: { layout: 'centered', docs: { @@ -27,7 +27,7 @@ Cek dan inspect element pada story With HtmlFor untuk melihat hasilnya. }, }, tags: ['autodocs'], -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; diff --git a/libs/ui/src/molecules/input-form/input-form.tsx b/libs/ui/src/molecules/input-field/input-field.tsx similarity index 90% rename from libs/ui/src/molecules/input-form/input-form.tsx rename to libs/ui/src/molecules/input-field/input-field.tsx index 7e5c8d3..0db7b16 100644 --- a/libs/ui/src/molecules/input-form/input-form.tsx +++ b/libs/ui/src/molecules/input-field/input-field.tsx @@ -7,10 +7,9 @@ import { import { Input } from '../../atoms'; import { cn } from '@imphnen-frontend-service/utils'; -type TInputType = 'text' | 'email' | 'password' | 'file'; -type TInputSize = 'sm' | 'md' | 'lg'; - -type TInputFormProps = Omit< +export type TInputType = 'text' | 'email' | 'password' | 'file'; +export type TInputSize = 'sm' | 'md' | 'lg'; +export type TInputFieldProps = Omit< DetailedHTMLProps, HTMLInputElement>, 'size' | 'type' > & { @@ -19,7 +18,6 @@ type TInputFormProps = Omit< size?: TInputSize; error?: string; disabled?: boolean; - helperText?: string; htmlFor?: string; }; @@ -39,7 +37,7 @@ const sizeClasses: Record = { }, }; -export const InputForm: FC = ({ +export const InputField: FC = ({ label, placeholder, type = 'text', @@ -88,5 +86,3 @@ export const InputForm: FC = ({ ); }; - -export default InputForm; diff --git a/libs/ui/src/molecules/input-form/index.ts b/libs/ui/src/molecules/input-form/index.ts deleted file mode 100644 index 14dcfcb..0000000 --- a/libs/ui/src/molecules/input-form/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './input-form'; diff --git a/libs/ui/src/organisms/controlled-field/controlled-input-field.tsx b/libs/ui/src/organisms/controlled-field/controlled-input-field.tsx new file mode 100644 index 0000000..08f8552 --- /dev/null +++ b/libs/ui/src/organisms/controlled-field/controlled-input-field.tsx @@ -0,0 +1,21 @@ +import { + InputField, + TInputFieldProps, +} from '@imphnen-frontend-service/ui/molecules'; +import { + FieldValues, + useController, + UseControllerProps, +} from 'react-hook-form'; + +export type TControlledInputFieldProps = + UseControllerProps & TInputFieldProps; + +export const ControlledInputField = ( + props: TControlledInputFieldProps +) => { + const { field, fieldState } = useController(props); + return ( + + ); +}; diff --git a/libs/ui/src/organisms/controlled-field/index.ts b/libs/ui/src/organisms/controlled-field/index.ts new file mode 100644 index 0000000..cb8eb90 --- /dev/null +++ b/libs/ui/src/organisms/controlled-field/index.ts @@ -0,0 +1 @@ +export * from './controlled-input-field'; diff --git a/libs/ui/src/organisms/index.ts b/libs/ui/src/organisms/index.ts index b8af5de..20f6043 100644 --- a/libs/ui/src/organisms/index.ts +++ b/libs/ui/src/organisms/index.ts @@ -1,6 +1,7 @@ export * from './navbar'; -export * from "./modals-gacha"; -export * from "./auth-banner"; -export * from './backoffice-sidebar' -export * from './datatable' -export * from './filter' +export * from './modals-gacha'; +export * from './auth-banner'; +export * from './backoffice-sidebar'; +export * from './datatable'; +export * from './filter'; +export * from './controlled-field'; diff --git a/libs/ui/src/organisms/navbar/navbar.spec.tsx b/libs/ui/src/organisms/navbar/navbar.spec.tsx index 99385ea..8cb4462 100644 --- a/libs/ui/src/organisms/navbar/navbar.spec.tsx +++ b/libs/ui/src/organisms/navbar/navbar.spec.tsx @@ -163,7 +163,7 @@ describe('Navbar', () => { } }); - it('has correct ARIA role for navigation', () => { + it('has correct ARIA role for nav', () => { const { container }: RenderResult = render( @@ -171,6 +171,6 @@ describe('Navbar', () => { ); const header: HTMLElement | null = container.querySelector('header'); - expect(header).toHaveAttribute('role', 'navigation'); + expect(header).toHaveAttribute('role', 'nav'); }); }); diff --git a/libs/ui/src/organisms/navbar/navbar.tsx b/libs/ui/src/organisms/navbar/navbar.tsx index f431833..e675d3c 100644 --- a/libs/ui/src/organisms/navbar/navbar.tsx +++ b/libs/ui/src/organisms/navbar/navbar.tsx @@ -1,16 +1,18 @@ import { MenuOutlined } from '@ant-design/icons'; -import { Button } from '@imphnen-frontend-service/ui/atoms'; import { FC, ReactElement, useState } from 'react'; import { Link } from 'react-router-dom'; +import { Button } from '../../atoms/button'; +import { useSession } from '@imphnen-frontend-service/utils'; export const Navbar: FC = (): ReactElement => { - const [isDropdownOpen, setDropdownOpen] = useState(false); + const { session, signOut, isAuthenticated } = useSession(); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); return (
@@ -40,20 +42,27 @@ export const Navbar: FC = (): ReactElement => { Merch Gacha -
  • - -
  • + {!isAuthenticated ? ( +
  • + +
  • + ) : ( +
  • + {session.user?.fullname} +
    + Logout +
    +
  • + )} @@ -77,14 +86,18 @@ export const Navbar: FC = (): ReactElement => { Merch Gacha -
  • - - Login - -
  • + {!isAuthenticated ? ( +
  • + + Login + +
  • + ) : ( +
  • {session.user?.fullname}
  • + )}
    )} diff --git a/libs/utils/src/axios/api.ts b/libs/utils/src/axios/api.ts deleted file mode 100644 index f04cfc4..0000000 --- a/libs/utils/src/axios/api.ts +++ /dev/null @@ -1,7 +0,0 @@ -import axios, { AxiosRequestConfig } from 'axios'; - -const config: AxiosRequestConfig = { - baseURL: import.meta.env.VITE_API_URL, -}; - -export const api = axios.create(config); diff --git a/libs/utils/src/axios/index.ts b/libs/utils/src/axios/index.ts deleted file mode 100644 index b1c13e7..0000000 --- a/libs/utils/src/axios/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './api'; diff --git a/libs/utils/src/hooks/index.ts b/libs/utils/src/hooks/index.ts new file mode 100644 index 0000000..2279469 --- /dev/null +++ b/libs/utils/src/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './use-query-state'; +export * from './use-session'; diff --git a/apps/gacha/src/hooks/use-query-state.ts b/libs/utils/src/hooks/use-query-state.ts similarity index 100% rename from apps/gacha/src/hooks/use-query-state.ts rename to libs/utils/src/hooks/use-query-state.ts diff --git a/libs/utils/src/hooks/use-session.ts b/libs/utils/src/hooks/use-session.ts new file mode 100644 index 0000000..880ec1f --- /dev/null +++ b/libs/utils/src/hooks/use-session.ts @@ -0,0 +1,22 @@ +import { SessionToken, SessionUser } from '../local-storage'; + +export const useSession = () => { + const session = { + user: SessionUser.get(), + token: SessionToken.get(), + }; + + const isAuthenticated = !!session.token?.access_token; + + const signOut = () => { + SessionUser.remove(); + SessionToken.remove(); + window.location.reload(); + }; + + return { + isAuthenticated, + session, + signOut, + }; +}; diff --git a/libs/utils/src/index.ts b/libs/utils/src/index.ts index 651d023..5884fb4 100644 --- a/libs/utils/src/index.ts +++ b/libs/utils/src/index.ts @@ -1,4 +1,5 @@ export * from './react-query'; export * from './react-router'; export * from './tailwind-merge'; -export * from './axios'; +export * from './hooks'; +export * from './local-storage'; diff --git a/libs/utils/src/local-storage/index.ts b/libs/utils/src/local-storage/index.ts new file mode 100644 index 0000000..9cd2959 --- /dev/null +++ b/libs/utils/src/local-storage/index.ts @@ -0,0 +1,60 @@ +export type TPermissionItem = { + id: string; + name: string; + created_at: string; + updated_at: string; +}; + +type TRoleItem = { + id: string; + name: string; + created_at: string; + updated_at: string; + permissions: TPermissionItem[]; +}; + +type TUserItem = { + id: string; + avatar: string; + birthdate: string; + email: string; + fullname: string; + gender: string; + identity_number: string; + is_active: boolean; + is_profile_completed: boolean; + phone_number: string; + referral_code: string; + referred_by: string; + religion: string; + student_type: string; + role: TRoleItem; +}; + +export const SessionUser = { + set: (val: TUserItem) => localStorage.setItem('users', JSON.stringify(val)), + get: (): TUserItem | undefined => { + const users = localStorage.getItem('users'); + return users ? JSON.parse(users) : undefined; + }, + remove: () => localStorage.removeItem('users'), +}; + +export const SessionToken = { + set: (val: { access_token: string; refresh_token: string }) => { + localStorage.setItem('access_token', val.access_token); + localStorage.setItem('refresh_token', val.refresh_token); + }, + get: (): + | { access_token?: string | null; refresh_token?: string | null } + | undefined => { + return { + access_token: localStorage.getItem('access_token'), + refresh_token: localStorage.getItem('refresh_token'), + }; + }, + remove: () => { + localStorage.removeItem('access_token'); + localStorage.removeItem('refresh_token'); + }, +}; diff --git a/package-lock.json b/package-lock.json index c4d353c..9269853 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,14 +10,19 @@ "license": "MIT", "dependencies": { "@ant-design/icons": "^5.6.1", + "@hookform/resolvers": "^5.0.1", "@tanstack/react-query": "^5.67.3", + "@tanstack/react-store": "^0.7.0", "@tanstack/react-table": "^8.21.2", "axios": "^1.8.3", "clsx": "^2.1.1", + "js-cookie": "^3.0.5", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-hook-form": "^7.55.0", "react-router-dom": "^7.3.0", - "tailwind-merge": "^3.0.2" + "tailwind-merge": "^3.0.2", + "zod": "^3.24.2" }, "devDependencies": { "@babel/core": "^7.14.5", @@ -39,7 +44,7 @@ "@storybook/test-runner": "^0.19.0", "@storybook/testing-library": "^0.2.2", "@swc-node/register": "~1.9.1", - "@swc/cli": "~0.3.12", + "@swc/cli": "^0.6.0", "@swc/core": "~1.5.7", "@swc/helpers": "~0.5.11", "@tailwindcss/postcss": "^4.0.13", @@ -47,6 +52,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.1.0", "@testing-library/user-event": "^14.6.1", + "@types/js-cookie": "^3.0.6", "@types/node": "18.16.9", "@types/react": "19.0.0", "@types/react-dom": "19.0.0", @@ -2923,6 +2929,18 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@hookform/resolvers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz", + "integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -4216,26 +4234,6 @@ "@module-federation/sdk": "0.9.1" } }, - "node_modules/@mole-inc/bin-wrapper": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@mole-inc/bin-wrapper/-/bin-wrapper-8.0.1.tgz", - "integrity": "sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bin-check": "^4.1.0", - "bin-version-check": "^5.0.0", - "content-disposition": "^0.5.4", - "ext-name": "^5.0.0", - "file-type": "^17.1.6", - "filenamify": "^5.0.2", - "got": "^11.8.5", - "os-filter-obj": "^2.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/@napi-rs/nice": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", @@ -6916,6 +6914,13 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -6948,13 +6953,13 @@ "license": "MIT" }, "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sindresorhus/is?sponsor=1" @@ -6980,6 +6985,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@storybook/addon-actions": { "version": "8.6.6", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.6.6.tgz", @@ -8234,18 +8245,18 @@ } }, "node_modules/@swc/cli": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.3.14.tgz", - "integrity": "sha512-0vGqD6FSW67PaZUZABkA+ADKsX7OUY/PwNEz1SbQdCvVk/e4Z36Gwh7mFVBQH9RIsMonTyhV1RHkwkGnEfR3zQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.6.0.tgz", + "integrity": "sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==", "dev": true, "license": "MIT", "dependencies": { - "@mole-inc/bin-wrapper": "^8.0.1", "@swc/counter": "^0.1.3", + "@xhmikosr/bin-wrapper": "^13.0.5", "commander": "^8.3.0", "fast-glob": "^3.2.5", "minimatch": "^9.0.3", - "piscina": "^4.3.0", + "piscina": "^4.3.1", "semver": "^7.3.8", "slash": "3.0.0", "source-map": "^0.7.3" @@ -8260,7 +8271,7 @@ }, "peerDependencies": { "@swc/core": "^1.2.66", - "chokidar": "^3.5.1" + "chokidar": "^4.0.1" }, "peerDependenciesMeta": { "chokidar": { @@ -8543,16 +8554,16 @@ } }, "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "license": "MIT", "dependencies": { - "defer-to-connect": "^2.0.0" + "defer-to-connect": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=14.16" } }, "node_modules/@tailwindcss/node": { @@ -8818,6 +8829,24 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-store": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.7.0.tgz", + "integrity": "sha512-S/Rq17HaGOk+tQHV/yrePMnG1xbsKZIl/VsNWnNXt4XW+tTY8dTlvpJH2ZQ3GRALsusG5K6Q3unAGJ2pd9W/Ng==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.7.0", + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@tanstack/react-table": { "version": "8.21.2", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.2.tgz", @@ -8838,6 +8867,16 @@ "react-dom": ">=16.8" } }, + "node_modules/@tanstack/store": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.0.tgz", + "integrity": "sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/table-core": { "version": "8.21.2", "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz", @@ -9114,19 +9153,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -9283,6 +9309,13 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -9297,16 +9330,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", @@ -9355,16 +9378,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -10853,6 +10866,210 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@xhmikosr/archive-type": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/archive-type/-/archive-type-7.0.0.tgz", + "integrity": "sha512-sIm84ZneCOJuiy3PpWR5bxkx3HaNt1pqaN+vncUBZIlPZCq8ASZH+hBVdu5H8znR7qYC6sKwx+ie2Q7qztJTxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^19.0.0" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + } + }, + "node_modules/@xhmikosr/bin-check": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-check/-/bin-check-7.0.3.tgz", + "integrity": "sha512-4UnCLCs8DB+itHJVkqFp9Zjg+w/205/J2j2wNBsCEAm/BuBmtua2hhUOdAMQE47b1c7P9Xmddj0p+X1XVsfHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "isexe": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/bin-wrapper": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-wrapper/-/bin-wrapper-13.0.5.tgz", + "integrity": "sha512-DT2SAuHDeOw0G5bs7wZbQTbf4hd8pJ14tO0i4cWhRkIJfgRdKmMfkDilpaJ8uZyPA0NVRwasCNAmMJcWA67osw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/bin-check": "^7.0.3", + "@xhmikosr/downloader": "^15.0.1", + "@xhmikosr/os-filter-obj": "^3.0.0", + "bin-version-check": "^5.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress/-/decompress-10.0.1.tgz", + "integrity": "sha512-6uHnEEt5jv9ro0CDzqWlFgPycdE+H+kbJnwyxgZregIMLQ7unQSCNVsYG255FoqU8cP46DyggI7F7LohzEl8Ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "@xhmikosr/decompress-tarbz2": "^8.0.1", + "@xhmikosr/decompress-targz": "^8.0.1", + "@xhmikosr/decompress-unzip": "^7.0.0", + "graceful-fs": "^4.2.11", + "make-dir": "^4.0.0", + "strip-dirs": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-tar": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tar/-/decompress-tar-8.0.1.tgz", + "integrity": "sha512-dpEgs0cQKJ2xpIaGSO0hrzz3Kt8TQHYdizHsgDtLorWajuHJqxzot9Hbi0huRxJuAGG2qiHSQkwyvHHQtlE+fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^19.0.0", + "is-stream": "^2.0.1", + "tar-stream": "^3.1.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-tar/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/@xhmikosr/decompress-tarbz2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tarbz2/-/decompress-tarbz2-8.0.2.tgz", + "integrity": "sha512-p5A2r/AVynTQSsF34Pig6olt9CvRj6J5ikIhzUd3b57pUXyFDGtmBstcw+xXza0QFUh93zJsmY3zGeNDlR2AQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "file-type": "^19.6.0", + "is-stream": "^2.0.1", + "seek-bzip": "^2.0.0", + "unbzip2-stream": "^1.4.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-targz": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-targz/-/decompress-targz-8.0.1.tgz", + "integrity": "sha512-mvy5AIDIZjQ2IagMI/wvauEiSNHhu/g65qpdM4EVoYHUJBAmkQWqcPJa8Xzi1aKVTmOA5xLJeDk7dqSjlHq8Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "file-type": "^19.0.0", + "is-stream": "^2.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-unzip": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-unzip/-/decompress-unzip-7.0.0.tgz", + "integrity": "sha512-GQMpzIpWTsNr6UZbISawsGI0hJ4KA/mz5nFq+cEoPs12UybAqZWKbyIaZZyLbJebKl5FkLpsGBkrplJdjvUoSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^19.0.0", + "get-stream": "^6.0.1", + "yauzl": "^3.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/downloader": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@xhmikosr/downloader/-/downloader-15.0.1.tgz", + "integrity": "sha512-fiuFHf3Dt6pkX8HQrVBsK0uXtkgkVlhrZEh8b7VgoDqFf+zrgFBPyrwCqE/3nDwn3hLeNz+BsrS7q3mu13Lp1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/archive-type": "^7.0.0", + "@xhmikosr/decompress": "^10.0.1", + "content-disposition": "^0.5.4", + "defaults": "^3.0.0", + "ext-name": "^5.0.0", + "file-type": "^19.0.0", + "filenamify": "^6.0.0", + "get-stream": "^6.0.1", + "got": "^13.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/defaults": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-3.0.0.tgz", + "integrity": "sha512-RsqXDEAALjfRTro+IFNKpcPCt0/Cy2FqHSIlnomiJp9YGadpQnrtbRpSgN2+np21qHcIKiva4fiOQGjS9/qR/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@xhmikosr/os-filter-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/os-filter-obj/-/os-filter-obj-3.0.0.tgz", + "integrity": "sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^3.0.0" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + } + }, + "node_modules/@xhmikosr/os-filter-obj/node_modules/arch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-3.0.0.tgz", + "integrity": "sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -12034,20 +12251,6 @@ "node": "*" } }, - "node_modules/bin-check": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", - "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^0.7.0", - "executable": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/bin-version": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", @@ -12083,56 +12286,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bin-version/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/bin-version/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bin-version/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -12461,6 +12614,16 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -12510,48 +12673,32 @@ } }, "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.6.0" + "node": ">=14.16" } }, "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "license": "MIT", "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14.16" } }, "node_modules/caching-transform": { @@ -12901,56 +13048,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clipboardy/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/clipboardy/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clipboardy/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -12976,19 +13073,6 @@ "node": ">=0.8" } }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -15407,111 +15491,27 @@ } }, "node_modules/execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/execa/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "license": "ISC", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/execa/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^2.0.0" + "node": ">=10" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true, - "license": "ISC" - }, - "node_modules/executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.2.0" - }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/exit": { @@ -15893,23 +15893,54 @@ } }, "node_modules/file-type": { - "version": "17.1.6", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-17.1.6.tgz", - "integrity": "sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz", + "integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==", "dev": true, "license": "MIT", "dependencies": { - "readable-web-to-node-stream": "^3.0.2", - "strtok3": "^7.0.0-alpha.9", - "token-types": "^5.0.0-alpha.2" + "get-stream": "^9.0.1", + "strtok3": "^9.0.1", + "token-types": "^6.0.0", + "uint8array-extras": "^1.3.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, + "node_modules/file-type/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-type/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -15947,18 +15978,16 @@ } }, "node_modules/filenamify": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-5.1.1.tgz", - "integrity": "sha512-M45CbrJLGACfrPOkrTp3j2EcO9OBkKUYME0eiqOCa7i2poaklU0jhlIaMlr8ijLorT0uLAzrn3qXOp5684CkfA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz", + "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", "dev": true, "license": "MIT", "dependencies": { - "filename-reserved-regex": "^3.0.0", - "strip-outer": "^2.0.0", - "trim-repeated": "^2.0.0" + "filename-reserved-regex": "^3.0.0" }, "engines": { - "node": ">=12.20" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -16263,6 +16292,16 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.17" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -16501,13 +16540,16 @@ } }, "node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-symbol-description": { @@ -16667,26 +16709,26 @@ } }, "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", "dev": true, "license": "MIT", "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">=10.19.0" + "node": ">=16" }, "funding": { "url": "https://github.com/sindresorhus/got?sponsor=1" @@ -16842,19 +16884,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hasha/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/hasha/node_modules/type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -17198,14 +17227,14 @@ "license": "MIT" }, "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "license": "MIT", "dependencies": { "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" + "resolve-alpn": "^1.2.0" }, "engines": { "node": ">=10.19.0" @@ -17372,6 +17401,16 @@ "dev": true, "license": "ISC" }, + "node_modules/inspect-with-kind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", + "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "kind-of": "^6.0.2" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -17813,13 +17852,16 @@ } }, "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-string": { @@ -18225,56 +18267,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-changed-files/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-circus": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", @@ -19392,6 +19384,15 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -19762,6 +19763,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -20441,13 +20452,16 @@ } }, "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lru-cache": { @@ -20687,13 +20701,16 @@ } }, "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/min-indent": { @@ -20946,13 +20963,13 @@ } }, "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -21680,19 +21697,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-filter-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", - "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arch": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -21722,23 +21726,13 @@ } }, "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=12.20" } }, "node_modules/p-limit": { @@ -22033,6 +22027,13 @@ "through2": "^2.0.3" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -22060,16 +22061,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pino": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/pino/-/pino-9.5.0.tgz", @@ -22579,13 +22570,6 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true, - "license": "ISC" - }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -22599,17 +22583,6 @@ "url": "https://github.com/sponsors/lupomontero" } }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/pumpify": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", @@ -22919,6 +22892,22 @@ "react": "^19.0.0" } }, + "node_modules/react-hook-form": { + "version": "7.55.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.55.0.tgz", + "integrity": "sha512-XRnjsH3GVMQz1moZTW53MxfoWN7aDpUg/GpVNc4A3eXRVNdGXfbzJ4vM4aLQ8g6XCUh1nIbx70aaNCl7kxnjog==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -22999,65 +22988,6 @@ "node": ">= 6" } }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", - "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^4.7.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "dev": true, - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", @@ -23384,13 +23314,16 @@ } }, "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, "license": "MIT", "dependencies": { - "lowercase-keys": "^2.0.0" + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -23709,6 +23642,30 @@ "dev": true, "license": "MIT" }, + "node_modules/seek-bzip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", + "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^6.0.0" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -24865,14 +24822,15 @@ "node": ">=4" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "node_modules/strip-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-3.0.0.tgz", + "integrity": "sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "license": "ISC", + "dependencies": { + "inspect-with-kind": "^1.0.5", + "is-plain-obj": "^1.1.0" } }, "node_modules/strip-final-newline": { @@ -24911,28 +24869,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-outer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-2.0.0.tgz", - "integrity": "sha512-A21Xsm1XzUkK0qK1ZrytDUvqsQWict2Cykhvi0fBQntGG5JSprESasEyV1EZ/4CiR5WB5KjzLTrP/bO37B0wPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strtok3": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.1.1.tgz", - "integrity": "sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz", + "integrity": "sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==", "dev": true, "license": "MIT", "dependencies": { "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.1.3" + "peek-readable": "^5.3.1" }, "engines": { "node": ">=16" @@ -25419,9 +25364,9 @@ } }, "node_modules/token-types": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", - "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz", + "integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==", "dev": true, "license": "MIT", "dependencies": { @@ -25489,32 +25434,6 @@ "tree-kill": "cli.js" } }, - "node_modules/trim-repeated": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-2.0.0.tgz", - "integrity": "sha512-QUHBFTJGdOwmp0tbOG505xAgOp/YliZP/6UgafFXYZ26WT1bvQmSMJUvkeVSASuJJHbqsFbynTvkd5W8RBTipg==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^5.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/trim-repeated/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ts-api-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", @@ -25846,6 +25765,19 @@ "node": ">=0.8.0" } }, + "node_modules/uint8array-extras": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", + "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -25865,6 +25797,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", @@ -26043,6 +25986,15 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -26319,9 +26271,9 @@ } }, "node_modules/vite": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz", - "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.4.tgz", + "integrity": "sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==", "dev": true, "license": "MIT", "dependencies": { @@ -27225,6 +27177,20 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", + "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/ylru": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", @@ -27257,6 +27223,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 3c7f58b..3be05e4 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,19 @@ "private": true, "dependencies": { "@ant-design/icons": "^5.6.1", + "@hookform/resolvers": "^5.0.1", "@tanstack/react-query": "^5.67.3", + "@tanstack/react-store": "^0.7.0", "@tanstack/react-table": "^8.21.2", "axios": "^1.8.3", "clsx": "^2.1.1", + "js-cookie": "^3.0.5", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-hook-form": "^7.55.0", "react-router-dom": "^7.3.0", - "tailwind-merge": "^3.0.2" + "tailwind-merge": "^3.0.2", + "zod": "^3.24.2" }, "devDependencies": { "@babel/core": "^7.14.5", @@ -48,7 +53,7 @@ "@storybook/test-runner": "^0.19.0", "@storybook/testing-library": "^0.2.2", "@swc-node/register": "~1.9.1", - "@swc/cli": "~0.3.12", + "@swc/cli": "^0.6.0", "@swc/core": "~1.5.7", "@swc/helpers": "~0.5.11", "@tailwindcss/postcss": "^4.0.13", @@ -56,6 +61,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.1.0", "@testing-library/user-event": "^14.6.1", + "@types/js-cookie": "^3.0.6", "@types/node": "18.16.9", "@types/react": "19.0.0", "@types/react-dom": "19.0.0", From 2bd767d20bf2b71b290e8115bb734b42def6671c Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin <53475078+maulanasdqn@users.noreply.github.com> Date: Thu, 3 Apr 2025 12:26:45 +0700 Subject: [PATCH 05/37] chore: kesalahan pemula --- .env | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 8b453ae..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -VITE_API_URL=https://api.imphnen.dev/v1 \ No newline at end of file From 9c7b8c1c63add476a7e3f0178c5793abcd569861 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin <53475078+maulanasdqn@users.noreply.github.com> Date: Thu, 3 Apr 2025 12:27:22 +0700 Subject: [PATCH 06/37] chore: kesalahan pemula --- .gitignore | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 77912e8..ae360d6 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,10 @@ Thumbs.db vite.config.*.timestamp* vitest.config.*.timestamp* -storybook-static \ No newline at end of file +storybook-static + + +.env +.env.prod +.env.develop +.env.staging From f8f796e07153fbb91f3b3e5dd50d6ea04f7eea71 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Thu, 3 Apr 2025 12:31:16 +0700 Subject: [PATCH 07/37] chore: fix global error build --- .../_components/modal-edit-account.tsx | 10 +-- .../dashboard/_components/modal-add-item.tsx | 8 +-- .../dashboard/_components/modal-edit-item.tsx | 8 +-- apps/backoffice/src/app/page.tsx | 6 +- .../prizes/_components/modal-process-item.tsx | 10 +-- .../_components/modal-validate.tsx | 4 +- apps/dimentorin/src/app/auth/login/page.tsx | 68 +++++++++++++------ .../input-field/input-field.spec.tsx | 2 +- 8 files changed, 72 insertions(+), 44 deletions(-) diff --git a/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx b/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx index adb382a..c7623bf 100644 --- a/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx +++ b/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx @@ -1,5 +1,5 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules'; +import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules'; import { useState } from 'react'; interface IModalEditAccount { @@ -63,7 +63,7 @@ const StepOne = ({ nextStep }: IStepOneProps) => {
    - { size="lg" className="w-full" /> - { size="lg" className="w-full" /> - { size="lg" className="w-full" /> - (
    - - - (
    - - - { const navigate = useNavigate(); @@ -13,14 +13,14 @@ export const Components: FC = (): ReactElement => {

    Welcome to IMPHNEN Backoffice

    - -
    - - - - - { const [searchParams] = useSearchParams(); - const error = searchParams.get("error"); // Ambil nilai ?error= + const error = searchParams.get('error'); // Ambil nilai ?error= return ( -
    -
    +
    +
    -
    -
    -

    Hallo Minna-san

    -
    Welcome to Dimentorin by IMPHNEN
    - - -
    - Lupa Password ? +
    +
    +

    + Hallo Minna-san +

    +
    + Welcome to Dimentorin by IMPHNEN +
    + + + - -
    + +
    Belum Punya akun ?
    - Daftar Disini + + Daftar Disini +
    Or
    - -
    +
    ); }; -export default Components; \ No newline at end of file +export default Components; diff --git a/libs/ui/src/molecules/input-field/input-field.spec.tsx b/libs/ui/src/molecules/input-field/input-field.spec.tsx index 7ed60dc..948c77b 100644 --- a/libs/ui/src/molecules/input-field/input-field.spec.tsx +++ b/libs/ui/src/molecules/input-field/input-field.spec.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import { InputField } from './input-field'; -describe('InputForm Component', () => { +describe('InputField Component', () => { it('renders correctly with disabled prop', () => { render(); From 4693284b37ca3dd560c9ef1687e5a8a7b762bf78 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Thu, 3 Apr 2025 13:59:27 +0700 Subject: [PATCH 08/37] docs: Update README --- README.md | 162 +++++++++++++++++++++++++++----------------------- docs/logo.svg | 9 +++ 2 files changed, 98 insertions(+), 73 deletions(-) create mode 100644 docs/logo.svg diff --git a/README.md b/README.md index 7d8118f..bb120d9 100644 --- a/README.md +++ b/README.md @@ -1,101 +1,117 @@ -# ImphnenFrontendService +# IMPHNEN Frontend Service - +

    + IMPHNEN +

    -✨ Your new, shiny [Nx workspace](https://nx.dev) is ready ✨. +This repository is a **monorepo** for all frontend services of IMPHNEN. The monorepo includes three main applications: -[Learn more about this workspace setup and its capabilities](https://nx.dev/getting-started/tutorials/react-monorepo-tutorial?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or run `npx nx graph` to visually explore what was created. Now, let's get you up to speed! +1. **Gacha** - Gacha feature https://gacha.imphnen.dev/ +2. **Backoffice** - Application for internal management. +3. **Dimentorin** - Application for mentoring services. -## Run tasks +## How to install -To run the dev server for your app, use: +1. Clone this repository: + ```sh + git clone https://github.com/IMPHNEN/imphnen-frontend-service.git + cd imphnen-frontend-service + ``` +2. Install all dependencies: + ```sh + npm install + ``` -```sh -npx nx serve dimentorin -``` +## How to run -To create a production bundle: +### Development -```sh -npx nx build dimentorin -``` +Use the following commands to run in development mode: -To see all available targets to run for a project, run: +- **Gacha**: + ```sh + npm run gacha:dev + ``` +- **Backoffice**: + ```sh + npm run backoffice:dev + ``` +- **Dimentorin**: + ```sh + npm run dimentorin:dev + ``` -```sh -npx nx show project dimentorin -``` +### Build -These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the `project.json` or `package.json` files. +Use the following commands to build the applications: -[More about running tasks in the docs »](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) +- **Gacha**: + ```sh + npm run gacha:build + ``` +- **Backoffice**: + ```sh + npm run backoffice:build + ``` +- **Dimentorin**: + ```sh + npm run dimentorin:build + ``` -## Add new projects +### Production -While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature. +Use the following commands to run the applications in production mode: -Use the plugin's generator to create new projects. +- **Gacha**: + ```sh + npm run gacha:prod + ``` +- **Backoffice**: + ```sh + npm run backoffice:prod + ``` +- **Dimentorin**: + ```sh + npm run dimentorin:prod + ``` -To generate a new application, use: +### Storybook -```sh -npx nx g @nx/react:app demo -``` +This repository uses Storybook to develop, test, and document UI components in an isolated and interactive environment. Below are the commands to work with Storybook: -To generate a new library, use: +- **Run Storybook** + + This command starts Storybook in development mode, allowing you to view and test UI components interactively. -```sh -npx nx g @nx/react:lib mylib -``` + ```sh + npm run ui:storybook + ``` + -You can use `npx nx list` to get a list of installed plugins. Then, run `npx nx list ` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE. +- **Run Unit Test** -[Learn more about Nx plugins »](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry »](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) + This command runs unit tests for the UI components to ensure they function as expected. -## Set up CI! + ```sh + npm run ui:test + ``` -### Step 1 +- **Build Components** -To connect to Nx Cloud, run the following command: + This command generates a static build of Storybook, which can be deployment for sharing and documentation purposes. -```sh -npx nx connect -``` + ```sh + npm run ui:build + ``` -Connecting to Nx Cloud ensures a [fast and scalable CI](https://nx.dev/ci/intro/why-nx-cloud?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) pipeline. It includes features such as: +## How to contribute -- [Remote caching](https://nx.dev/ci/features/remote-cache?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) -- [Task distribution across multiple machines](https://nx.dev/ci/features/distribute-task-execution?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) -- [Automated e2e test splitting](https://nx.dev/ci/features/split-e2e-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) -- [Task flakiness detection and rerunning](https://nx.dev/ci/features/flaky-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) +1. Fork the repository and clone it locally. +2. Create a new branch for new feature or fix: + ```sh + git checkout -b feat/nama-fitur + ``` +3. Make changes, commit, and push to your forked repository. +4. Create a pull request to this repository `develop` branch. -### Step 2 - -Use the following command to configure a CI workflow for your workspace: - -```sh -npx nx g ci-workflow -``` - -[Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) - -## Install Nx Console - -Nx Console is an editor extension that enriches your developer experience. It lets you run tasks, generate code, and improves code autocompletion in your IDE. It is available for VSCode and IntelliJ. - -[Install Nx Console »](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) - -## Useful links - -Learn more: - -- [Learn more about this workspace setup](https://nx.dev/getting-started/tutorials/react-monorepo-tutorial?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) -- [Learn about Nx on CI](https://nx.dev/ci/intro/ci-with-nx?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) -- [Releasing Packages with Nx release](https://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) -- [What are Nx plugins?](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) - -And join the Nx community: -- [Discord](https://go.nx.dev/community) -- [Follow us on X](https://twitter.com/nxdevtools) or [LinkedIn](https://www.linkedin.com/company/nrwl) -- [Our Youtube channel](https://www.youtube.com/@nxdevtools) -- [Our blog](https://nx.dev/blog?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) +If you encounter any issues or problems, feel free to create a new Issue. diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 0000000..ca0f36e --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + From 3a950ea867902b1d90cd6a0ee854ba1203b2e911 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Thu, 3 Apr 2025 14:01:14 +0700 Subject: [PATCH 09/37] docs: align center logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb120d9..b0d9f58 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # IMPHNEN Frontend Service -

    +

    IMPHNEN

    From 0a846b05122c9d3131ae1aabffd081f53350d399 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Thu, 3 Apr 2025 14:03:38 +0700 Subject: [PATCH 10/37] docs: fix --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b0d9f58..fcbae9b 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@

    IMPHNEN -

    +

    This repository is a **monorepo** for all frontend services of IMPHNEN. The monorepo includes three main applications: -1. **Gacha** - Gacha feature https://gacha.imphnen.dev/ +1. **Gacha** - Gacha feature https://gacha.imphnen.dev/ 2. **Backoffice** - Application for internal management. 3. **Dimentorin** - Application for mentoring services. @@ -98,7 +98,7 @@ This repository uses Storybook to develop, test, and document UI components in a - **Build Components** - This command generates a static build of Storybook, which can be deployment for sharing and documentation purposes. + This command generates a static build of Storybook, which can be deployed for sharing and documentation purposes. ```sh npm run ui:build @@ -107,7 +107,7 @@ This repository uses Storybook to develop, test, and document UI components in a ## How to contribute 1. Fork the repository and clone it locally. -2. Create a new branch for new feature or fix: +2. Create a new branch for a new feature or fix: ```sh git checkout -b feat/nama-fitur ``` From b5428d85528986251f726639df5ea4826d3715ae Mon Sep 17 00:00:00 2001 From: Hafid Nur <73023445+hafidnrzs@users.noreply.github.com> Date: Thu, 3 Apr 2025 14:15:52 +0700 Subject: [PATCH 11/37] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fcbae9b..a924890 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This repository is a **monorepo** for all frontend services of IMPHNEN. The monorepo includes three main applications: -1. **Gacha** - Gacha feature https://gacha.imphnen.dev/ +1. **Gacha** - Application for gacha website. 2. **Backoffice** - Application for internal management. 3. **Dimentorin** - Application for mentoring services. @@ -109,7 +109,7 @@ This repository uses Storybook to develop, test, and document UI components in a 1. Fork the repository and clone it locally. 2. Create a new branch for a new feature or fix: ```sh - git checkout -b feat/nama-fitur + git checkout -b feat/feature-name ``` 3. Make changes, commit, and push to your forked repository. 4. Create a pull request to this repository `develop` branch. From 33c04707f8124b3354908374d9b463299fb92195 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Thu, 3 Apr 2025 16:28:54 +0700 Subject: [PATCH 12/37] feat: login backoffice --- apps/backoffice/index.html | 2 +- apps/backoffice/src/app/_hooks/use-login.ts | 31 +++++++++ apps/backoffice/src/app/page.tsx | 66 ++++++++----------- .../src/molecules/input-field/input-field.tsx | 9 ++- 4 files changed, 66 insertions(+), 42 deletions(-) create mode 100644 apps/backoffice/src/app/_hooks/use-login.ts diff --git a/apps/backoffice/index.html b/apps/backoffice/index.html index 3905403..52a9604 100644 --- a/apps/backoffice/index.html +++ b/apps/backoffice/index.html @@ -6,7 +6,7 @@ - + diff --git a/apps/backoffice/src/app/_hooks/use-login.ts b/apps/backoffice/src/app/_hooks/use-login.ts new file mode 100644 index 0000000..e23709a --- /dev/null +++ b/apps/backoffice/src/app/_hooks/use-login.ts @@ -0,0 +1,31 @@ +import { useForm } from 'react-hook-form'; +import { + authLoginSchema, + TLoginRequest, + usePostLogin, +} from '@imphnen-frontend-service/service'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useNavigate } from 'react-router-dom'; + +export const useLogin = () => { + const postLogin = usePostLogin(); + const form = useForm({ + resolver: zodResolver(authLoginSchema), + mode: 'all', + }); + const navigate = useNavigate(); + + const onSubmit = form.handleSubmit((data) => { + postLogin.mutate(data, { + onSuccess: () => { + console.log('Success Login'); + navigate('/dashboard'); + }, + }); + }); + + return { + form, + onSubmit, + }; +}; diff --git a/apps/backoffice/src/app/page.tsx b/apps/backoffice/src/app/page.tsx index 21d9316..99b0c4e 100644 --- a/apps/backoffice/src/app/page.tsx +++ b/apps/backoffice/src/app/page.tsx @@ -1,30 +1,10 @@ -import { FC, ReactElement, useState } from 'react'; -import { useNavigate } from 'react-router'; +import { FC, ReactElement } from 'react'; import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { InputField } from '@imphnen-frontend-service/ui/molecules'; +import { useLogin } from './_hooks/use-login'; +import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; export const Components: FC = (): ReactElement => { - const navigate = useNavigate(); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - - const handleLogin = async (e: React.FormEvent) => { - e.preventDefault(); - try { - const payload = { email, password }; - console.log(payload); // for debugging - const response = await postLogin(payload); - - const { access_token, refresh_token } = response.data.token; - sessionStorage.setItem('access_token', access_token); - localStorage.setItem('refresh_token', refresh_token); - - console.log('Login successful:', response); // for debugging - navigate('/dashboard'); - } catch (error) { - console.log('Login error:', error); // for debugging - } - }; + const { form, onSubmit } = useLogin(); return (
    @@ -33,21 +13,29 @@ export const Components: FC = (): ReactElement => {

    Welcome to IMPHNEN Backoffice

    - - - +
    + + + +
    ); diff --git a/libs/ui/src/molecules/input-field/input-field.tsx b/libs/ui/src/molecules/input-field/input-field.tsx index 0db7b16..0245843 100644 --- a/libs/ui/src/molecules/input-field/input-field.tsx +++ b/libs/ui/src/molecules/input-field/input-field.tsx @@ -75,10 +75,15 @@ export const InputField: FC = ({ {...rest} /> {error ? ( -

    {error}

    +

    {error}

    ) : ( helperText && ( -

    +

    {helperText}

    ) From 6b959a2f2234def95e0a0dc3196fd842ba07df49 Mon Sep 17 00:00:00 2001 From: egagofur Date: Thu, 3 Apr 2025 19:39:53 +0700 Subject: [PATCH 13/37] feat: change from dummy button to navbar button --- .../app/_components/form/modal-form-login.tsx | 12 ++++---- apps/gacha/src/app/layout.tsx | 16 ++++++----- apps/gacha/src/app/page.tsx | 16 ++--------- libs/ui/src/organisms/navbar/navbar.tsx | 13 +++++---- libs/utils/src/hooks/index.ts | 1 + libs/utils/src/hooks/use-modal-login.tsx | 28 +++++++++++++++++++ 6 files changed, 55 insertions(+), 31 deletions(-) create mode 100644 libs/utils/src/hooks/use-modal-login.tsx diff --git a/apps/gacha/src/app/_components/form/modal-form-login.tsx b/apps/gacha/src/app/_components/form/modal-form-login.tsx index fdb4e65..3b2c6e8 100644 --- a/apps/gacha/src/app/_components/form/modal-form-login.tsx +++ b/apps/gacha/src/app/_components/form/modal-form-login.tsx @@ -1,19 +1,20 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; import { Modal } from '@imphnen-frontend-service/ui/molecules'; import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; -import { Link } from 'react-router-dom'; import { useLogin } from '../../_hooks/use-login'; interface IModalFormLogin { isOpen: boolean; onClose: () => void; onForgotPassword: () => void; + setIsOpenRegisterModal: (value: boolean) => void; } const ModalFormLogin = ({ isOpen, onClose, onForgotPassword, + setIsOpenRegisterModal, }: IModalFormLogin) => { const { form, onSubmit } = useLogin(); @@ -59,12 +60,13 @@ const ModalFormLogin = ({

    Belum punya akun?

    - setIsOpenRegisterModal(true)} > Daftar - +
    diff --git a/apps/gacha/src/app/layout.tsx b/apps/gacha/src/app/layout.tsx index 7428943..2b27454 100644 --- a/apps/gacha/src/app/layout.tsx +++ b/apps/gacha/src/app/layout.tsx @@ -1,14 +1,16 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; 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/app/page.tsx b/apps/gacha/src/app/page.tsx index c693317..c9cf71d 100644 --- a/apps/gacha/src/app/page.tsx +++ b/apps/gacha/src/app/page.tsx @@ -5,10 +5,11 @@ import ModalFormForgotPassword from './_components/form/modal-form-forgot-passwo 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'; export const Components: FC = (): ReactElement => { + const { showModalLogin, setShowModalLogin } = useModalLogin(); const [showModalForgotPassword, setShowModalForgotPassword] = useState(false); - const [showModalLogin, setShowModalLogin] = useState(false); const [showModalRegister, setShowModalRegister] = useState(false); const scrollToRoulette = () => { @@ -176,18 +177,6 @@ export const Components: FC = (): ReactElement => { Spin Now
    - -
    - - - -
    @@ -195,6 +184,7 @@ export const Components: FC = (): ReactElement => { isOpen={showModalLogin} onClose={() => setShowModalLogin(false)} onForgotPassword={handleForgotPasswordClick} + setIsOpenRegisterModal={setShowModalRegister} key="login" /> diff --git a/libs/ui/src/organisms/navbar/navbar.tsx b/libs/ui/src/organisms/navbar/navbar.tsx index e675d3c..00a0e24 100644 --- a/libs/ui/src/organisms/navbar/navbar.tsx +++ b/libs/ui/src/organisms/navbar/navbar.tsx @@ -2,11 +2,12 @@ import { MenuOutlined } from '@ant-design/icons'; import { FC, ReactElement, useState } from 'react'; import { Link } from 'react-router-dom'; import { Button } from '../../atoms/button'; -import { useSession } from '@imphnen-frontend-service/utils'; +import { useModalLogin, useSession } from '@imphnen-frontend-service/utils'; export const Navbar: FC = (): ReactElement => { const { session, signOut, isAuthenticated } = useSession(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const { setShowModalLogin } = useModalLogin(); return (
    @@ -44,7 +45,7 @@ export const Navbar: FC = (): ReactElement => { {!isAuthenticated ? (
  • - +
  • ) : (
  • @@ -88,12 +89,12 @@ export const Navbar: FC = (): ReactElement => {
  • {!isAuthenticated ? (
  • - setShowModalLogin(true)} + className="block w-full text-gray-100 transition-colors px-4 py-2 text-center font-semibold" > Login - +
  • ) : (
  • {session.user?.fullname}
  • diff --git a/libs/utils/src/hooks/index.ts b/libs/utils/src/hooks/index.ts index 2279469..6b7067f 100644 --- a/libs/utils/src/hooks/index.ts +++ b/libs/utils/src/hooks/index.ts @@ -1,2 +1,3 @@ export * from './use-query-state'; export * from './use-session'; +export * from './use-modal-login'; diff --git a/libs/utils/src/hooks/use-modal-login.tsx b/libs/utils/src/hooks/use-modal-login.tsx new file mode 100644 index 0000000..181ed57 --- /dev/null +++ b/libs/utils/src/hooks/use-modal-login.tsx @@ -0,0 +1,28 @@ +import { createContext, ReactNode, useContext, useState } from 'react'; + +interface ModalLoginContextType { + showModalLogin: boolean; + setShowModalLogin: (value: boolean) => void; +} + +const ModalLoginContext = createContext( + undefined +); + +export const ModalLoginProvider = ({ children }: { children: ReactNode }) => { + const [showModalLogin, setShowModalLogin] = useState(false); + + return ( + + {children} + + ); +}; + +export const useModalLogin = () => { + const context = useContext(ModalLoginContext); + if (!context) { + throw new Error('useModalLogin must be used within an ModalLoginProvider'); + } + return context; +}; From 6bad504d442aa030a95485609674976ec8d77d29 Mon Sep 17 00:00:00 2001 From: egagofur Date: Thu, 3 Apr 2025 19:40:04 +0700 Subject: [PATCH 14/37] feat: change wording validation --- libs/service/src/schemas/auth/index.ts | 16 ++++++++++++---- .../ui/src/molecules/input-field/input-field.tsx | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/libs/service/src/schemas/auth/index.ts b/libs/service/src/schemas/auth/index.ts index 4da256e..9a61758 100644 --- a/libs/service/src/schemas/auth/index.ts +++ b/libs/service/src/schemas/auth/index.ts @@ -2,8 +2,16 @@ import { z } from 'zod'; export const authLoginSchema = z.object({ email: z - .string() - .min(1, 'Email cannot be empty') - .email('Email must be valid'), - password: z.string().min(1, 'Password cannot be empty'), + .string({ + required_error: 'Email tidak boleh kosong', + invalid_type_error: 'Email harus berupa string', + }) + .min(1, 'Email tidak boleh kosong') + .email('Email harus valid'), + password: z + .string({ + required_error: 'Password tidak boleh kosong', + invalid_type_error: 'Password harus berupa string', + }) + .min(1, 'Password tidak boleh kosong'), }); diff --git a/libs/ui/src/molecules/input-field/input-field.tsx b/libs/ui/src/molecules/input-field/input-field.tsx index 0db7b16..f07eef6 100644 --- a/libs/ui/src/molecules/input-field/input-field.tsx +++ b/libs/ui/src/molecules/input-field/input-field.tsx @@ -75,7 +75,7 @@ export const InputField: FC = ({ {...rest} /> {error ? ( -

    {error}

    +

    {error}

    ) : ( helperText && (

    From 9777b7bea92810830ca40f8493c9c40843ac5d35 Mon Sep 17 00:00:00 2001 From: egagofur Date: Thu, 3 Apr 2025 19:40:19 +0700 Subject: [PATCH 15/37] feat: add toast library --- package-lock.json | 11 +++++++++++ package.json | 1 + 2 files changed, 12 insertions(+) diff --git a/package-lock.json b/package-lock.json index 9269853..9958fd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "react-dom": "^19.0.0", "react-hook-form": "^7.55.0", "react-router-dom": "^7.3.0", + "sonner": "^2.0.3", "tailwind-merge": "^3.0.2", "zod": "^3.24.2" }, @@ -24244,6 +24245,16 @@ "atomic-sleep": "^1.0.0" } }, + "node_modules/sonner": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz", + "integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", diff --git a/package.json b/package.json index 3be05e4..a3188c4 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "react-dom": "^19.0.0", "react-hook-form": "^7.55.0", "react-router-dom": "^7.3.0", + "sonner": "^2.0.3", "tailwind-merge": "^3.0.2", "zod": "^3.24.2" }, From 280afd68abd83c3fdddc8ee19f672637bb5b836f Mon Sep 17 00:00:00 2001 From: egagofur Date: Thu, 3 Apr 2025 19:51:31 +0700 Subject: [PATCH 16/37] fix: template github --- .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md From 988476b4090db47778ce6c622711f7cf73dd3806 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Thu, 3 Apr 2025 21:59:31 +0700 Subject: [PATCH 17/37] refactor auth and logout - Pindah hard-code API version ke environment variable - Gunakan helper untuk logout --- libs/service/src/api/auth/index.ts | 2 +- .../backoffice-sidebar/backoffice-sidebar.tsx | 18 ++++-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/libs/service/src/api/auth/index.ts b/libs/service/src/api/auth/index.ts index 16ea25c..0241638 100644 --- a/libs/service/src/api/auth/index.ts +++ b/libs/service/src/api/auth/index.ts @@ -12,7 +12,7 @@ export const postLogin = async ( ): Promise => { const { data } = await api({ method: 'POST', - url: '/v1/auth/login', + url: '/auth/login', data: payload, }); return data; diff --git a/libs/ui/src/organisms/backoffice-sidebar/backoffice-sidebar.tsx b/libs/ui/src/organisms/backoffice-sidebar/backoffice-sidebar.tsx index a7aace0..3ceb18e 100644 --- a/libs/ui/src/organisms/backoffice-sidebar/backoffice-sidebar.tsx +++ b/libs/ui/src/organisms/backoffice-sidebar/backoffice-sidebar.tsx @@ -7,27 +7,19 @@ import { } from '@ant-design/icons'; import { Button } from '../../atoms'; import { FC, ReactElement } from 'react'; -import { Link, useLocation, useNavigate } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; +import { useSession } from '@imphnen-frontend-service/utils'; export const BackofficeSidebar: FC = (): ReactElement => { + const { signOut } = useSession(); const location = useLocation(); - const navigate = useNavigate(); const isActive = (path: string) => location.pathname.includes(path); - const handleLogout = () => { - sessionStorage.removeItem('access_token'); - localStorage.removeItem('refresh_token'); - - navigate('/'); - }; - return (

    diff --git a/libs/ui/src/molecules/input-field/input-field.tsx b/libs/ui/src/molecules/input-field/input-field.tsx index 72c6910..d7b4901 100644 --- a/libs/ui/src/molecules/input-field/input-field.tsx +++ b/libs/ui/src/molecules/input-field/input-field.tsx @@ -75,7 +75,7 @@ export const InputField: FC = ({ {...rest} /> {error ? ( -

    {error}

    +

    {error}

    ) : ( helperText && (

    Date: Thu, 3 Apr 2025 23:39:41 +0700 Subject: [PATCH 20/37] fix: unused field --- apps/gacha/src/app/_components/form/modal-form-register.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gacha/src/app/_components/form/modal-form-register.tsx b/apps/gacha/src/app/_components/form/modal-form-register.tsx index 6116bc1..0112319 100644 --- a/apps/gacha/src/app/_components/form/modal-form-register.tsx +++ b/apps/gacha/src/app/_components/form/modal-form-register.tsx @@ -1,5 +1,5 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules'; +import { Modal } from '@imphnen-frontend-service/ui/molecules'; import { useQueryState } from '@imphnen-frontend-service/utils'; import { useRegister } from '../../_hooks/use-register'; import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; From d61cb4a21578dce8ca9ad5ee3b729e272a7bc359 Mon Sep 17 00:00:00 2001 From: egagofur Date: Fri, 4 Apr 2025 16:38:21 +0700 Subject: [PATCH 21/37] fix: split zod validation register modal --- .../_components/form/modal-form-register.tsx | 103 ++++++++++-------- apps/gacha/src/app/_hooks/use-register.ts | 30 +++++ libs/service/src/schemas/auth/index.ts | 56 +++++----- 3 files changed, 116 insertions(+), 73 deletions(-) diff --git a/apps/gacha/src/app/_components/form/modal-form-register.tsx b/apps/gacha/src/app/_components/form/modal-form-register.tsx index 0112319..8f50e04 100644 --- a/apps/gacha/src/app/_components/form/modal-form-register.tsx +++ b/apps/gacha/src/app/_components/form/modal-form-register.tsx @@ -12,7 +12,7 @@ interface IModalFormRegisterProps { } const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => { - const { form, onSubmit } = useRegister(); + const { form, onSubmit, isStepOneValid } = useRegister(); const { step: currentStep, @@ -46,7 +46,11 @@ const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => {

    {currentStep === 1 && ( - + )} {currentStep === 2 && } @@ -58,52 +62,59 @@ const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => { interface IStepOneProps { form: UseFormReturn; nextStep: () => void; - onClose: () => void; + isStepOneValid: boolean; } -const StepOne = ({ form, nextStep, onClose }: IStepOneProps) => ( - <> - - - - - - -); +const StepOne = ({ form, nextStep, isStepOneValid }: IStepOneProps) => { + return ( + <> + + + + + + + ); +}; interface IStepTwoProps { form: UseFormReturn; diff --git a/apps/gacha/src/app/_hooks/use-register.ts b/apps/gacha/src/app/_hooks/use-register.ts index c5d7fb6..5817c30 100644 --- a/apps/gacha/src/app/_hooks/use-register.ts +++ b/apps/gacha/src/app/_hooks/use-register.ts @@ -1,19 +1,48 @@ import { useForm } from 'react-hook-form'; import { authRegisterSchema, + stepOneRegisterSchema, TRegisterRequest, usePostRegister, } from '@imphnen-frontend-service/service'; +import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { toast } from 'sonner'; +import { useEffect, useState } from 'react'; export const useRegister = () => { const postRegister = usePostRegister(); const form = useForm({ resolver: zodResolver(authRegisterSchema), mode: 'all', + defaultValues: { + fullname: '', + email: '', + password: '', + confirm_password: '', + phone_number: '', + referral_code: '', + referred_by: '', + student_type: '', + }, }); + const [stepValid, setStepValid] = useState(false); + + useEffect(() => { + const subscription = form.watch(() => { + const { fullname, email, password, confirm_password } = form.getValues(); + const valid = stepOneRegisterSchema.safeParse({ + fullname, + email, + password, + confirm_password, + }).success; + setStepValid(valid); + }); + return () => subscription.unsubscribe(); + }, [form]); + const onSubmit = form.handleSubmit((data) => { postRegister.mutate(data, { onSuccess: (data) => toast.success(data.message), @@ -24,5 +53,6 @@ export const useRegister = () => { return { form, onSubmit, + isStepOneValid: stepValid, }; }; diff --git a/libs/service/src/schemas/auth/index.ts b/libs/service/src/schemas/auth/index.ts index bb8e849..d406f97 100644 --- a/libs/service/src/schemas/auth/index.ts +++ b/libs/service/src/schemas/auth/index.ts @@ -16,7 +16,7 @@ export const authLoginSchema = z.object({ .min(1, 'Password tidak boleh kosong'), }); -export const authRegisterSchema = z +export const stepOneRegisterSchema = z .object({ email: z .string({ @@ -40,24 +40,6 @@ export const authRegisterSchema = z .min(1, 'Password tidak boleh kosong') .min(8, 'Password harus lebih dari 8 karakter') .max(50, 'Password tidak boleh lebih dari 50 karakter'), - phone_number: z - .string({ - required_error: 'Nomor telepon tidak boleh kosong', - invalid_type_error: 'Nomor telepon harus berupa string', - }) - .min(1, 'Nomor telepon tidak boleh kosong') - .max(15, 'Nomor telepon tidak boleh lebih dari 15 karakter'), - referral_code: z - .string() - .min(1, 'Kode referral tidak boleh kosong') - .max(4, 'Kode referral tidak boleh lebih dari 4 karakter') - .optional(), - referred_by: z - .string() - .min(1, 'Kode referral tidak boleh kosong') - .max(50, 'Kode referral tidak boleh lebih dari 50 karakter') - .optional(), - student_type: z.string(), confirm_password: z .string({ required_error: 'Konfirmasi password tidak boleh kosong', @@ -65,12 +47,32 @@ export const authRegisterSchema = z .min(1, 'Konfirmasi password tidak boleh kosong') .max(50, 'Konfirmasi password tidak boleh lebih dari 50 karakter'), }) - .superRefine((val, ctx) => { - if (val.password !== val.confirm_password) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ['confirm_password'], - message: `No duplicates allowed.`, - }); - } + .refine((data) => data.password === data.confirm_password, { + message: 'Password dan Konfirmasi Password harus sama', + path: ['confirm_password'], }); + +const stepTwoRegisterSchema = z.object({ + phone_number: z + .string({ + required_error: 'Nomor telepon tidak boleh kosong', + invalid_type_error: 'Nomor telepon harus berupa string', + }) + .min(1, 'Nomor telepon tidak boleh kosong') + .max(15, 'Nomor telepon tidak boleh lebih dari 15 karakter'), + referral_code: z + .string() + .min(1, 'Kode referral tidak boleh kosong') + .max(4, 'Kode referral tidak boleh lebih dari 4 karakter') + .optional(), + referred_by: z + .string() + .min(1, 'Kode referral tidak boleh kosong') + .max(50, 'Kode referral tidak boleh lebih dari 50 karakter') + .optional(), + student_type: z.string(), +}); + +export const authRegisterSchema = stepOneRegisterSchema.and( + stepTwoRegisterSchema +); From 638b64b71556cd159a2580bc1c610f84b1ada33d Mon Sep 17 00:00:00 2001 From: egagofur Date: Fri, 4 Apr 2025 16:42:27 +0700 Subject: [PATCH 22/37] fix: validation by query param --- .../gacha/src/app/_components/form/modal-form-register.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/gacha/src/app/_components/form/modal-form-register.tsx b/apps/gacha/src/app/_components/form/modal-form-register.tsx index 8f50e04..5eb5c82 100644 --- a/apps/gacha/src/app/_components/form/modal-form-register.tsx +++ b/apps/gacha/src/app/_components/form/modal-form-register.tsx @@ -5,6 +5,7 @@ import { useRegister } from '../../_hooks/use-register'; import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; import { UseFormReturn } from 'react-hook-form'; import { TRegisterRequest } from '@imphnen-frontend-service/service'; +import { useEffect } from 'react'; interface IModalFormRegisterProps { isOpen: boolean; @@ -25,6 +26,12 @@ const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => { minValue: 1, }); + useEffect(() => { + if (!isStepOneValid && currentStep === 2) { + prevStep(); + } + }, [isStepOneValid, currentStep, prevStep]); + return ( Date: Sat, 5 Apr 2025 06:05:50 +0700 Subject: [PATCH 23/37] feat: update ui component sidebar, datatable, button - Tambah halaman "Gacha Roll" pada sidebar - Edit style button variant danger - Adjust sedikit header datatable --- libs/ui/src/atoms/button/button.tsx | 2 +- .../backoffice-sidebar/backoffice-sidebar.tsx | 13 +++++++++++++ libs/ui/src/organisms/datatable/datatable.tsx | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/libs/ui/src/atoms/button/button.tsx b/libs/ui/src/atoms/button/button.tsx index dc831b5..20c1677 100644 --- a/libs/ui/src/atoms/button/button.tsx +++ b/libs/ui/src/atoms/button/button.tsx @@ -31,7 +31,7 @@ const variantClasses: Record = { bordered: 'border border-primary-500 hover:border-primary-600 bg-transparent hover:text-primary-600 hover:bg-gray-50 text-primary-500', success: 'bg-success-500 hover:bg-success-600 text-white shadow-md', - danger: 'bg-danger-500 hover:bg-danger-600 text-white shadow-md', + danger: 'bg-danger-100 hover:bg-danger-200 text-danger-500 shadow-md', }; const sizeClasses: Record = { diff --git a/libs/ui/src/organisms/backoffice-sidebar/backoffice-sidebar.tsx b/libs/ui/src/organisms/backoffice-sidebar/backoffice-sidebar.tsx index 3ceb18e..7239b64 100644 --- a/libs/ui/src/organisms/backoffice-sidebar/backoffice-sidebar.tsx +++ b/libs/ui/src/organisms/backoffice-sidebar/backoffice-sidebar.tsx @@ -3,6 +3,7 @@ import { AuditOutlined, InboxOutlined, LogoutOutlined, + ReloadOutlined, UserOutlined, } from '@ant-design/icons'; import { Button } from '../../atoms'; @@ -33,6 +34,18 @@ export const BackofficeSidebar: FC = (): ReactElement => { Dashboard & Set Gacha + + + Gacha Roll + + ({
    - + {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( From 745a2d28734beb9791ccbc1cea86fa1d34dbc449 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Sat, 5 Apr 2025 06:07:26 +0700 Subject: [PATCH 24/37] feat: slicing Gacha Roll page --- .../gacha-roll/_components/modal-add-item.tsx | 155 +++++++++++++ .../_components/modal-delete-item.tsx | 62 +++++ .../_components/modal-update-item.tsx | 152 ++++++++++++ apps/backoffice/src/app/gacha-roll/layout.tsx | 18 ++ apps/backoffice/src/app/gacha-roll/page.tsx | 216 ++++++++++++++++++ 5 files changed, 603 insertions(+) create mode 100644 apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx create mode 100644 apps/backoffice/src/app/gacha-roll/_components/modal-delete-item.tsx create mode 100644 apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx create mode 100644 apps/backoffice/src/app/gacha-roll/layout.tsx create mode 100644 apps/backoffice/src/app/gacha-roll/page.tsx diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx new file mode 100644 index 0000000..b077c94 --- /dev/null +++ b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx @@ -0,0 +1,155 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules'; +import { useState } from 'react'; + +interface IModalAddItem { + isOpen: boolean; + onClose: () => void; + handleAddItem?: () => void; + currentStep?: number; + nextStep: () => void; + prevStep: () => void; + resetStep: () => void; +} + +const ModalAddItem = ({ + isOpen, + onClose, + currentStep, + nextStep, + prevStep, + resetStep, + handleAddItem, +}: IModalAddItem) => { + return ( + { + onClose(); + resetStep(); + }} + disableEscapeKeyDown={true} + > + {currentStep === 1 && } + {currentStep === 2 && ( + + )} + + ); +}; + +interface IStepOneProps { + nextStep: () => void; + onClose: () => void; +} + +const StepOne = ({ nextStep }: IStepOneProps) => { + const [itemName, setItemName] = useState(''); + const [quantity, setQuantity] = useState(''); + const [chanceRate, setChanceRate] = useState(''); + + return ( + <> + +

    + Tambah Item Roll Gacha +

    +

    + Lengkapi detal di bawah ini, untuk menambahkan item gacha +

    +
    + +
    + setItemName(e.target.value)} + size="lg" + className="w-full" + /> + setQuantity(e.target.value)} + size="lg" + className="w-full" + /> + setChanceRate(e.target.value)} + size="lg" + className="w-full" + /> +
    + + +
    + + ); +}; + +interface IStepTwoProps { + onClose: () => void; + handleAddItem?: () => void; + resetStep: () => void; +} + +const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => ( + <> + +

    + Tambah ke Roll Gacha +

    +

    + Apakah kamu yakin ingin +
    menambahkan item ini ke roll gacha? +

    +
    + + + + + +); + +export default ModalAddItem; diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-delete-item.tsx b/apps/backoffice/src/app/gacha-roll/_components/modal-delete-item.tsx new file mode 100644 index 0000000..3663ee0 --- /dev/null +++ b/apps/backoffice/src/app/gacha-roll/_components/modal-delete-item.tsx @@ -0,0 +1,62 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { Modal } from '@imphnen-frontend-service/ui/molecules'; + +interface IModalDeleteItem { + isOpen: boolean; + onClose: () => void; + handleDeleteItem?: () => void; +} + +const ModalDeleteItem = ({ + isOpen, + onClose, + handleDeleteItem, +}: IModalDeleteItem) => { + return ( + + + Delete item? +
    +

    + Delete Item +

    +

    + Apakah kamu yakin untuk menghapus item ini? +

    +
    +
    + + + + +
    + ); +}; + +export default ModalDeleteItem; diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx b/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx new file mode 100644 index 0000000..f9dee1c --- /dev/null +++ b/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx @@ -0,0 +1,152 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules'; +import { useState } from 'react'; + +interface IModalUpdateItem { + isOpen: boolean; + onClose: () => void; + handleUpdateItem?: () => void; + currentStep?: number; + nextStep: () => void; + prevStep: () => void; + resetStep: () => void; +} + +const ModalUpdateItem = ({ + isOpen, + onClose, + currentStep, + nextStep, + prevStep, + resetStep, + handleUpdateItem, +}: IModalUpdateItem) => { + return ( + { + onClose(); + resetStep(); + }} + disableEscapeKeyDown={true} + > + {currentStep === 1 && } + {currentStep === 2 && ( + + )} + + ); +}; + +interface IStepOneProps { + nextStep: () => void; + onClose: () => void; +} + +const StepOne = ({ nextStep }: IStepOneProps) => { + const [itemName, setItemName] = useState('Hoodie IMPHNEN Official 2025'); + const [quantity, setQuantity] = useState('10'); + const [chanceRate, setChanceRate] = useState('0.1'); + + return ( + <> + +

    + Update Item Roll Gacha +

    +
    + +
    + setItemName(e.target.value)} + size="lg" + className="w-full" + /> + setQuantity(e.target.value)} + size="lg" + className="w-full" + /> + setChanceRate(e.target.value)} + size="lg" + className="w-full" + /> +
    + + +
    + + ); +}; + +interface IStepTwoProps { + onClose: () => void; + handleUpdateItem?: () => void; + resetStep: () => void; +} + +const StepTwo = ({ onClose, handleUpdateItem, resetStep }: IStepTwoProps) => ( + <> + +

    + Update Item +

    +

    + Apakah kamu yakin dengan +
    perubahan yang dilakukan? +

    +
    + + + + + +); + +export default ModalUpdateItem; diff --git a/apps/backoffice/src/app/gacha-roll/layout.tsx b/apps/backoffice/src/app/gacha-roll/layout.tsx new file mode 100644 index 0000000..a770c99 --- /dev/null +++ b/apps/backoffice/src/app/gacha-roll/layout.tsx @@ -0,0 +1,18 @@ +import { FC, ReactElement } from 'react'; +import { Outlet } from 'react-router-dom'; +import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms'; + +export const AppLayout: FC = (): ReactElement => { + return ( +
    +
    + +
    + +
    +
    +
    + ); +}; + +export default AppLayout; diff --git a/apps/backoffice/src/app/gacha-roll/page.tsx b/apps/backoffice/src/app/gacha-roll/page.tsx new file mode 100644 index 0000000..0bc7499 --- /dev/null +++ b/apps/backoffice/src/app/gacha-roll/page.tsx @@ -0,0 +1,216 @@ +import * as React from 'react'; + +import { FC, Fragment, ReactElement, 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'; + +import { + ColumnDef, + getCoreRowModel, + getPaginationRowModel, + 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'; + +interface GachaItem { + id: number; + name: string; + chanceRate: number; + quantity: number; +} + +const mockData: GachaItem[] = Array.from({ length: 90 }, (_, i) => ({ + id: i + 1, + name: 'Hoodie IMPHNEN Official 2025', + chanceRate: 0.1, + quantity: 10, +})); + +export const Components: FC = (): ReactElement => { + const [showModalAddItem, setShowModalAddItem] = useState(false); + const [showModalUpdateItem, setShowModalUpdateItem] = useState(false); + const [showModalDeleteItem, setShowModalDeleteItem] = useState(false); + + const { + step: currentStep, + nextStep, + prevStep, + resetStep, + } = useQueryState('step', { + defaultValue: 1, + maxValue: 2, + minValue: 1, + }); + + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 9, + }); + + const [rowSelection, setRowSelection] = React.useState({}); + + const columns: ColumnDef[] = [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ), + }, + { + header: 'No', + accessorKey: 'id', + }, + { + header: 'Nama Item', + accessorKey: 'name', + }, + { + header: 'Chance Rate', + accessorKey: 'chanceRate', + }, + { + header: 'Quantity', + accessorKey: 'quantity', + }, + { + header: 'Action', + cell: () => ( +
    + + +
    + ), + }, + ]; + + const table = useReactTable({ + data: mockData, + columns, + state: { + pagination, + rowSelection, + }, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onPaginationChange: setPagination, + pageCount: Math.ceil(mockData.length / pagination.pageSize), + manualPagination: false, + }); + + return ( + +
    +
    +

    Gacha Roll

    +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    + + setShowModalAddItem(false)} + handleAddItem={() => { + console.log('Item added'); + }} + nextStep={nextStep} + prevStep={prevStep} + resetStep={resetStep} + /> + setShowModalUpdateItem(false)} + handleUpdateItem={() => { + console.log('Item updated'); + }} + nextStep={nextStep} + prevStep={prevStep} + resetStep={resetStep} + /> + setShowModalDeleteItem(false)} + handleDeleteItem={() => { + console.log('Item deleted'); + }} + /> +
    + ); +}; + +export default Components; From 2746af0b3f1ca23c4ecf7487fdbe81ab237cc343 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Sat, 5 Apr 2025 06:21:56 +0700 Subject: [PATCH 25/37] fix: unit test for button and input-field component --- libs/ui/src/atoms/button/button.spec.tsx | 6 +++--- libs/ui/src/molecules/input-field/input-field.spec.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/ui/src/atoms/button/button.spec.tsx b/libs/ui/src/atoms/button/button.spec.tsx index d6c2dd3..bb74faa 100644 --- a/libs/ui/src/atoms/button/button.spec.tsx +++ b/libs/ui/src/atoms/button/button.spec.tsx @@ -24,9 +24,9 @@ describe('Test Button Component', () => { const button = screen.getByText('Delete'); - expect(button).toHaveClass('bg-danger-500'); - expect(button).toHaveClass('hover:bg-danger-600'); - expect(button).toHaveClass('text-white'); + expect(button).toHaveClass('bg-danger-100'); + expect(button).toHaveClass('hover:bg-danger-200'); + expect(button).toHaveClass('text-danger-500'); }); it("disables the button when 'disabled' prop is set", async () => { diff --git a/libs/ui/src/molecules/input-field/input-field.spec.tsx b/libs/ui/src/molecules/input-field/input-field.spec.tsx index 948c77b..909b56b 100644 --- a/libs/ui/src/molecules/input-field/input-field.spec.tsx +++ b/libs/ui/src/molecules/input-field/input-field.spec.tsx @@ -3,7 +3,7 @@ import { InputField } from './input-field'; describe('InputField Component', () => { it('renders correctly with disabled prop', () => { - render(); + render(); const input = screen.getByLabelText('Test Label'); expect(input).toBeDisabled(); @@ -11,7 +11,7 @@ describe('InputField Component', () => { }); it('renders correctly without disabled prop', () => { - render(); + render(); const input = screen.getByLabelText('Test Label'); expect(input).not.toBeDisabled(); From 885d15683302ea4f9836d729f0e76187ed3e9d2f Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Sat, 5 Apr 2025 06:41:11 +0700 Subject: [PATCH 26/37] feat: custom hook for add gacha roll item - Buat custom hook untuk add item --- .../gacha-roll/_components/modal-add-item.tsx | 155 +++++++++--------- .../src/app/gacha-roll/_hook/use-add-item.ts | 59 +++++++ 2 files changed, 133 insertions(+), 81 deletions(-) create mode 100644 apps/backoffice/src/app/gacha-roll/_hook/use-add-item.ts diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx index b077c94..5da4435 100644 --- a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx +++ b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx @@ -1,6 +1,7 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules'; -import { useState } from 'react'; +import { Modal } from '@imphnen-frontend-service/ui/molecules'; +import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; +import { useAddItem, useConfirmAddItem } from '../_hook/use-add-item'; interface IModalAddItem { isOpen: boolean; @@ -49,9 +50,7 @@ interface IStepOneProps { } const StepOne = ({ nextStep }: IStepOneProps) => { - const [itemName, setItemName] = useState(''); - const [quantity, setQuantity] = useState(''); - const [chanceRate, setChanceRate] = useState(''); + const { form, onSubmit } = useAddItem(nextStep); return ( <> @@ -63,45 +62,42 @@ const StepOne = ({ nextStep }: IStepOneProps) => { Lengkapi detal di bawah ini, untuk menambahkan item gacha

    - -
    - setItemName(e.target.value)} - size="lg" - className="w-full" - /> - setQuantity(e.target.value)} - size="lg" - className="w-full" - /> - setChanceRate(e.target.value)} - size="lg" - className="w-full" - /> -
    + +
    +
    + + + +
    - + +
    ); @@ -113,43 +109,40 @@ interface IStepTwoProps { resetStep: () => void; } -const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => ( - <> - -

    - Tambah ke Roll Gacha -

    -

    - Apakah kamu yakin ingin -
    menambahkan item ini ke roll gacha? -

    -
    - - - - - -); +const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => { + const { onConfirm, onCancel } = useConfirmAddItem(onClose, resetStep); + + return ( + <> + +

    + Tambah ke Roll Gacha +

    +

    + Apakah kamu yakin ingin +
    menambahkan item ini ke roll gacha? +

    +
    + + + + + + ); +}; export default ModalAddItem; diff --git a/apps/backoffice/src/app/gacha-roll/_hook/use-add-item.ts b/apps/backoffice/src/app/gacha-roll/_hook/use-add-item.ts new file mode 100644 index 0000000..2fc48d1 --- /dev/null +++ b/apps/backoffice/src/app/gacha-roll/_hook/use-add-item.ts @@ -0,0 +1,59 @@ +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; + +const addItemSchema = z.object({ + itemName: z.string().min(1, 'Item name is required'), + quantity: z.string().min(1, 'Quantity is required'), + chanceRate: z + .string() + .min(1, 'Chance rate is required') + .refine( + (val) => { + const num = parseFloat(val); + return num >= 0.1 && num <= 1; + }, + { message: 'Chance rate must be between 0.1 and 1' } + ), +}); + +type TAddItemForm = z.infer; + +export const useAddItem = (nextStep: () => void) => { + const form = useForm({ + resolver: zodResolver(addItemSchema), + mode: 'all', + }); + + const onSubmit = form.handleSubmit((data) => { + console.log('Form data:', data); + nextStep(); + }); + + return { + form, + onSubmit, + }; +}; + +export const useConfirmAddItem = ( + onClose: () => void, + resetStep: () => void, + handleAddItem?: () => void, +) => { + const onConfirm = () => { + handleAddItem?.(); + onClose(); + resetStep(); + }; + + const onCancel = () => { + onClose(); + resetStep(); + }; + + return { + onConfirm, + onCancel, + }; +}; From 3958ae1098490cfcf756dede0df07816a6a2a602 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Sat, 5 Apr 2025 07:39:57 +0700 Subject: [PATCH 27/37] refactor: fix validation for add gacha roll item - Pisahkan type dan schema untuk gacha roll item - Perbaiki validasi gacha roll item - Parsing angka pada component ControlledInputField - Update type number untuk component Input dan InputField --- .../gacha-roll/_components/modal-add-item.tsx | 11 +++++--- .../src/app/gacha-roll/_hook/use-add-item.ts | 26 +++++-------------- libs/service/src/schemas/gacha/index.ts | 23 ++++++++++++++++ libs/service/src/schemas/index.ts | 1 + libs/service/src/types/gacha/index.ts | 6 ++++- libs/ui/src/atoms/input/input.tsx | 2 +- .../src/molecules/input-field/input-field.tsx | 2 +- .../controlled-input-field.tsx | 14 +++++++++- 8 files changed, 58 insertions(+), 27 deletions(-) create mode 100644 libs/service/src/schemas/gacha/index.ts diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx index 5da4435..967d89f 100644 --- a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx +++ b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx @@ -78,7 +78,8 @@ const StepOne = ({ nextStep }: IStepOneProps) => { control={form.control} label="Quantity" name="quantity" - type="text" + type="number" + min={1} placeholder="Masukkan Kuantitas Item" size="lg" className="w-full" @@ -87,8 +88,12 @@ const StepOne = ({ nextStep }: IStepOneProps) => { control={form.control} label="Chance Rate" name="chanceRate" - type="text" - placeholder="Masukkan Chance Rate (0.1 - 1)" + type="number" + value={0.1} + min={0.1} + step={0.1} + max={1} + placeholder="Masukkan Chance Rate (0,1 - 1)" size="lg" className="w-full" /> diff --git a/apps/backoffice/src/app/gacha-roll/_hook/use-add-item.ts b/apps/backoffice/src/app/gacha-roll/_hook/use-add-item.ts index 2fc48d1..21c36e4 100644 --- a/apps/backoffice/src/app/gacha-roll/_hook/use-add-item.ts +++ b/apps/backoffice/src/app/gacha-roll/_hook/use-add-item.ts @@ -1,27 +1,13 @@ import { useForm } from 'react-hook-form'; -import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; - -const addItemSchema = z.object({ - itemName: z.string().min(1, 'Item name is required'), - quantity: z.string().min(1, 'Quantity is required'), - chanceRate: z - .string() - .min(1, 'Chance rate is required') - .refine( - (val) => { - const num = parseFloat(val); - return num >= 0.1 && num <= 1; - }, - { message: 'Chance rate must be between 0.1 and 1' } - ), -}); - -type TAddItemForm = z.infer; +import { + gachaRollItemSchema, + TGachaRollItem +} from '@imphnen-frontend-service/service'; export const useAddItem = (nextStep: () => void) => { - const form = useForm({ - resolver: zodResolver(addItemSchema), + const form = useForm({ + resolver: zodResolver(gachaRollItemSchema), mode: 'all', }); diff --git a/libs/service/src/schemas/gacha/index.ts b/libs/service/src/schemas/gacha/index.ts new file mode 100644 index 0000000..2e9065b --- /dev/null +++ b/libs/service/src/schemas/gacha/index.ts @@ -0,0 +1,23 @@ +import { z } from 'zod'; + +export const gachaRollItemSchema = z.object({ + itemName: z + .string({ + required_error: 'Nama item tidak boleh kosong', + invalid_type_error: 'Nama item harus berupa string', + }) + .min(1, 'Nama item tidak boleh kosong'), + quantity: z + .number({ + required_error: 'Quantity tidak boleh kosong', + invalid_type_error: 'Quantity harus berupa angka', + }) + .min(1, 'Quantity paling sedikit adalah 1'), + chanceRate: z + .number({ + required_error: 'Chance rate tidak boleh kosong', + invalid_type_error: 'Chance rate harus berupa angka', + }) + .min(0.1, 'Chance rate paling sedikit adalah 0,1') + .max(1, 'Chance rate paling banyak adalah 1'), +}); diff --git a/libs/service/src/schemas/index.ts b/libs/service/src/schemas/index.ts index 269586e..94e94e5 100644 --- a/libs/service/src/schemas/index.ts +++ b/libs/service/src/schemas/index.ts @@ -1 +1,2 @@ export * from './auth'; +export * from './gacha'; diff --git a/libs/service/src/types/gacha/index.ts b/libs/service/src/types/gacha/index.ts index cb0ff5c..3c0493d 100644 --- a/libs/service/src/types/gacha/index.ts +++ b/libs/service/src/types/gacha/index.ts @@ -1 +1,5 @@ -export {}; +export type TGachaRollItem = { + itemName: string; + quantity: number; + chanceRate: number; +}; diff --git a/libs/ui/src/atoms/input/input.tsx b/libs/ui/src/atoms/input/input.tsx index e780059..d8a6bf3 100644 --- a/libs/ui/src/atoms/input/input.tsx +++ b/libs/ui/src/atoms/input/input.tsx @@ -9,7 +9,7 @@ import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons'; // Import import { cn } from '@imphnen-frontend-service/utils'; import { Button } from '../button'; -type TInputType = 'text' | 'email' | 'password' | 'file'; +type TInputType = 'text' | 'email' | 'number' | 'password' | 'file'; type TInputSize = 'sm' | 'md' | 'lg'; type TInputProps = Omit< diff --git a/libs/ui/src/molecules/input-field/input-field.tsx b/libs/ui/src/molecules/input-field/input-field.tsx index d7b4901..463be6c 100644 --- a/libs/ui/src/molecules/input-field/input-field.tsx +++ b/libs/ui/src/molecules/input-field/input-field.tsx @@ -7,7 +7,7 @@ import { import { Input } from '../../atoms'; import { cn } from '@imphnen-frontend-service/utils'; -export type TInputType = 'text' | 'email' | 'password' | 'file'; +export type TInputType = 'text' | 'email' | 'number' | 'password' | 'file'; export type TInputSize = 'sm' | 'md' | 'lg'; export type TInputFieldProps = Omit< DetailedHTMLProps, HTMLInputElement>, diff --git a/libs/ui/src/organisms/controlled-field/controlled-input-field.tsx b/libs/ui/src/organisms/controlled-field/controlled-input-field.tsx index 08f8552..e8e10b9 100644 --- a/libs/ui/src/organisms/controlled-field/controlled-input-field.tsx +++ b/libs/ui/src/organisms/controlled-field/controlled-input-field.tsx @@ -15,7 +15,19 @@ export const ControlledInputField = ( props: TControlledInputFieldProps ) => { const { field, fieldState } = useController(props); + + const handleChange = (e: React.ChangeEvent) => { + const value = + props.type === 'number' ? Number(e.target.value) : e.target.value; + field.onChange(value); + }; + return ( - + ); }; From 07f1fb02080e91bc1f9773c873feb9d7073ea570 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Sat, 5 Apr 2025 08:02:13 +0700 Subject: [PATCH 28/37] feat: modal for update gacha roll item - Sesuaikan modal untuk update gacha roll item - Refactor custom hook untuk tambah dan update gacha roll item --- .../gacha-roll/_components/modal-add-item.tsx | 11 +- .../_components/modal-update-item.tsx | 176 ++++++++++-------- .../_hook/{use-add-item.ts => use-item.ts} | 12 +- 3 files changed, 109 insertions(+), 90 deletions(-) rename apps/backoffice/src/app/gacha-roll/_hook/{use-add-item.ts => use-item.ts} (76%) diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx index 967d89f..83dd111 100644 --- a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx +++ b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx @@ -1,7 +1,7 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; import { Modal } from '@imphnen-frontend-service/ui/molecules'; import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; -import { useAddItem, useConfirmAddItem } from '../_hook/use-add-item'; +import { useItem, useConfirmItem } from '../_hook/use-item'; interface IModalAddItem { isOpen: boolean; @@ -18,7 +18,6 @@ const ModalAddItem = ({ onClose, currentStep, nextStep, - prevStep, resetStep, handleAddItem, }: IModalAddItem) => { @@ -50,7 +49,7 @@ interface IStepOneProps { } const StepOne = ({ nextStep }: IStepOneProps) => { - const { form, onSubmit } = useAddItem(nextStep); + const { form, onSubmit } = useItem(nextStep); return ( <> @@ -115,7 +114,11 @@ interface IStepTwoProps { } const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => { - const { onConfirm, onCancel } = useConfirmAddItem(onClose, resetStep); + const { onConfirm, onCancel } = useConfirmItem( + onClose, + resetStep, + handleAddItem + ); return ( <> diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx b/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx index f9dee1c..057afe7 100644 --- a/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx +++ b/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx @@ -1,6 +1,7 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules'; -import { useState } from 'react'; +import { Modal } from '@imphnen-frontend-service/ui/molecules'; +import { useConfirmItem, useItem } from '../_hook/use-item'; +import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; interface IModalUpdateItem { isOpen: boolean; @@ -17,7 +18,6 @@ const ModalUpdateItem = ({ onClose, currentStep, nextStep, - prevStep, resetStep, handleUpdateItem, }: IModalUpdateItem) => { @@ -49,9 +49,13 @@ interface IStepOneProps { } const StepOne = ({ nextStep }: IStepOneProps) => { - const [itemName, setItemName] = useState('Hoodie IMPHNEN Official 2025'); - const [quantity, setQuantity] = useState('10'); - const [chanceRate, setChanceRate] = useState('0.1'); + const initialValues = { + itemName: 'Hoodie IMPHNEN Official 2025', + quantity: 10, + chanceRate: 0.1, + }; + + const { form, onSubmit } = useItem(nextStep, initialValues); return ( <> @@ -60,45 +64,52 @@ const StepOne = ({ nextStep }: IStepOneProps) => { Update Item Roll Gacha - -
    - setItemName(e.target.value)} - size="lg" - className="w-full" - /> - setQuantity(e.target.value)} - size="lg" - className="w-full" - /> - setChanceRate(e.target.value)} - size="lg" - className="w-full" - /> -
    + +
    +
    + + + +
    - + +
    ); @@ -110,43 +121,44 @@ interface IStepTwoProps { resetStep: () => void; } -const StepTwo = ({ onClose, handleUpdateItem, resetStep }: IStepTwoProps) => ( - <> - -

    - Update Item -

    -

    - Apakah kamu yakin dengan -
    perubahan yang dilakukan? -

    -
    - - - - - -); +const StepTwo = ({ onClose, handleUpdateItem, resetStep }: IStepTwoProps) => { + const { onConfirm, onCancel } = useConfirmItem( + onClose, + resetStep, + handleUpdateItem + ); + + return ( + <> + +

    + Update Item +

    +

    + Apakah kamu yakin dengan +
    perubahan yang dilakukan? +

    +
    + + + + + + ); +}; export default ModalUpdateItem; diff --git a/apps/backoffice/src/app/gacha-roll/_hook/use-add-item.ts b/apps/backoffice/src/app/gacha-roll/_hook/use-item.ts similarity index 76% rename from apps/backoffice/src/app/gacha-roll/_hook/use-add-item.ts rename to apps/backoffice/src/app/gacha-roll/_hook/use-item.ts index 21c36e4..42514ed 100644 --- a/apps/backoffice/src/app/gacha-roll/_hook/use-add-item.ts +++ b/apps/backoffice/src/app/gacha-roll/_hook/use-item.ts @@ -5,10 +5,14 @@ import { TGachaRollItem } from '@imphnen-frontend-service/service'; -export const useAddItem = (nextStep: () => void) => { +export const useItem = ( + nextStep: () => void, + initialValues?: TGachaRollItem +) => { const form = useForm({ resolver: zodResolver(gachaRollItemSchema), mode: 'all', + defaultValues: initialValues, }); const onSubmit = form.handleSubmit((data) => { @@ -22,13 +26,13 @@ export const useAddItem = (nextStep: () => void) => { }; }; -export const useConfirmAddItem = ( +export const useConfirmItem = ( onClose: () => void, resetStep: () => void, - handleAddItem?: () => void, + actionFunction?: () => void, ) => { const onConfirm = () => { - handleAddItem?.(); + actionFunction?.(); onClose(); resetStep(); }; From 5bdf8f5a4d9b44c88d73e4bcea4cb635054d9232 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Sat, 5 Apr 2025 08:40:19 +0700 Subject: [PATCH 29/37] feat(backoffice): add Toast --- apps/backoffice/src/app/_hooks/use-login.ts | 9 +++----- .../gacha-roll/_components/modal-add-item.tsx | 10 ++++++--- .../_components/modal-update-item.tsx | 10 ++++++--- .../src/app/gacha-roll/_hook/use-item.ts | 22 ++++++++++++++----- apps/backoffice/src/main.tsx | 2 ++ 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/apps/backoffice/src/app/_hooks/use-login.ts b/apps/backoffice/src/app/_hooks/use-login.ts index e23709a..3c031d3 100644 --- a/apps/backoffice/src/app/_hooks/use-login.ts +++ b/apps/backoffice/src/app/_hooks/use-login.ts @@ -5,7 +5,7 @@ import { usePostLogin, } from '@imphnen-frontend-service/service'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useNavigate } from 'react-router-dom'; +import { toast } from 'sonner'; export const useLogin = () => { const postLogin = usePostLogin(); @@ -13,14 +13,11 @@ export const useLogin = () => { resolver: zodResolver(authLoginSchema), mode: 'all', }); - const navigate = useNavigate(); const onSubmit = form.handleSubmit((data) => { postLogin.mutate(data, { - onSuccess: () => { - console.log('Success Login'); - navigate('/dashboard'); - }, + onSuccess: () => toast.success("Login sukses"), + onError: (error) => toast.error(error.message), }); }); diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx index 83dd111..2d13e2d 100644 --- a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx +++ b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx @@ -6,7 +6,7 @@ import { useItem, useConfirmItem } from '../_hook/use-item'; interface IModalAddItem { isOpen: boolean; onClose: () => void; - handleAddItem?: () => void; + handleAddItem?: () => Promise; currentStep?: number; nextStep: () => void; prevStep: () => void; @@ -109,7 +109,7 @@ const StepOne = ({ nextStep }: IStepOneProps) => { interface IStepTwoProps { onClose: () => void; - handleAddItem?: () => void; + handleAddItem?: () => Promise; resetStep: () => void; } @@ -117,7 +117,11 @@ const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => { const { onConfirm, onCancel } = useConfirmItem( onClose, resetStep, - handleAddItem + handleAddItem, + { + success: 'Item ditambahkan ke gacha item', + error: 'Item gagal ditambahkan ke gacha item', + } ); return ( diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx b/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx index 057afe7..db9f000 100644 --- a/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx +++ b/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx @@ -6,7 +6,7 @@ import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; interface IModalUpdateItem { isOpen: boolean; onClose: () => void; - handleUpdateItem?: () => void; + handleUpdateItem?: () => Promise; currentStep?: number; nextStep: () => void; prevStep: () => void; @@ -117,7 +117,7 @@ const StepOne = ({ nextStep }: IStepOneProps) => { interface IStepTwoProps { onClose: () => void; - handleUpdateItem?: () => void; + handleUpdateItem?: () => Promise; resetStep: () => void; } @@ -125,7 +125,11 @@ const StepTwo = ({ onClose, handleUpdateItem, resetStep }: IStepTwoProps) => { const { onConfirm, onCancel } = useConfirmItem( onClose, resetStep, - handleUpdateItem + handleUpdateItem, + { + success: 'Item ditambahkan ke gacha item', + error: 'Item gagal ditambahkan ke gacha item', + } ); return ( diff --git a/apps/backoffice/src/app/gacha-roll/_hook/use-item.ts b/apps/backoffice/src/app/gacha-roll/_hook/use-item.ts index 42514ed..1889359 100644 --- a/apps/backoffice/src/app/gacha-roll/_hook/use-item.ts +++ b/apps/backoffice/src/app/gacha-roll/_hook/use-item.ts @@ -4,6 +4,7 @@ import { gachaRollItemSchema, TGachaRollItem } from '@imphnen-frontend-service/service'; +import { toast } from 'sonner'; export const useItem = ( nextStep: () => void, @@ -29,12 +30,23 @@ export const useItem = ( export const useConfirmItem = ( onClose: () => void, resetStep: () => void, - actionFunction?: () => void, + actionFunction?: () => Promise, + messages?: { + success?: string; + error?: string; + } ) => { - const onConfirm = () => { - actionFunction?.(); - onClose(); - resetStep(); + const onConfirm = async () => { + try { + // const result = await actionFunction?.(); + // result ? toast.success(messages?.success) : toast.error(messages?.error); + toast.success(messages?.success); + onClose(); + resetStep(); + } catch (error) { + console.log(error); + toast.error(messages?.error); + } }; const onCancel = () => { diff --git a/apps/backoffice/src/main.tsx b/apps/backoffice/src/main.tsx index 7201952..4f9c907 100644 --- a/apps/backoffice/src/main.tsx +++ b/apps/backoffice/src/main.tsx @@ -8,6 +8,7 @@ import { convertPagesToRoute, QueryProvider, } from '@imphnen-frontend-service/utils'; +import { Toaster } from 'sonner'; import './index.css'; const files = import.meta.glob('./app/**/*(page|layout).tsx'); @@ -34,6 +35,7 @@ if (!rootElement) throw new Error('Failed to find the root element'); createRoot(rootElement).render( + From e805e5fd410f36e793c4e8d59f2746b00e624ee3 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Sat, 5 Apr 2025 08:48:53 +0700 Subject: [PATCH 30/37] fix: update Toast success/error message --- .../src/app/gacha-roll/_components/modal-add-item.tsx | 4 ++-- .../src/app/gacha-roll/_components/modal-update-item.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx index 2d13e2d..46a59ea 100644 --- a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx +++ b/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx @@ -119,8 +119,8 @@ const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => { resetStep, handleAddItem, { - success: 'Item ditambahkan ke gacha item', - error: 'Item gagal ditambahkan ke gacha item', + success: 'Item ditambahkan ke roll gacha', + error: 'Item gagal ditambahkan ke roll gacha', } ); diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx b/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx index db9f000..770f867 100644 --- a/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx +++ b/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx @@ -127,8 +127,8 @@ const StepTwo = ({ onClose, handleUpdateItem, resetStep }: IStepTwoProps) => { resetStep, handleUpdateItem, { - success: 'Item ditambahkan ke gacha item', - error: 'Item gagal ditambahkan ke gacha item', + success: 'Perubahan item roll berhasil dilakukan', + error: 'Perubahan item roll gagal dilakukan', } ); From 0e3dc101d57adc08429ba2a7b9f006010e46ccd0 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Sat, 5 Apr 2025 09:36:49 +0700 Subject: [PATCH 31/37] feat: update Dashboard page - Perubahan tampilan dan modal pada halaman Dashboard - Tambah schema dan type untuk Gacha Item - Gunakan form dan Toast --- .../dashboard/_components/modal-add-item.tsx | 187 +++++++++-------- .../dashboard/_components/modal-edit-item.tsx | 188 ++++++++++-------- .../src/app/dashboard/_hook/use-item.ts | 61 ++++++ apps/backoffice/src/app/dashboard/page.tsx | 32 +-- apps/backoffice/src/app/gacha-roll/page.tsx | 6 - libs/service/src/schemas/gacha/index.ts | 26 +++ libs/service/src/types/gacha/index.ts | 6 + .../controlled-input-field.tsx | 19 +- 8 files changed, 327 insertions(+), 198 deletions(-) create mode 100644 apps/backoffice/src/app/dashboard/_hook/use-item.ts diff --git a/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx b/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx index 520cfe4..5e0aa55 100644 --- a/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx +++ b/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx @@ -1,10 +1,12 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules'; +import { Modal } from '@imphnen-frontend-service/ui/molecules'; +import { useConfirmItem, useItem } from '../_hook/use-item'; +import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; interface IModalAddItem { isOpen: boolean; onClose: () => void; - handleAddItem: () => void; + handleAddItem?: () => Promise; currentStep?: number; nextStep: () => void; prevStep: () => void; @@ -46,91 +48,114 @@ interface IStepOneProps { onClose: () => void; } -const StepOne = ({ nextStep }: IStepOneProps) => ( - <> - -

    - Tambah Item Gacha -

    -

    - Lengkapi detail di bawah ini untuk menambahkan item gacha -

    -
    - -
    - - - -
    +const StepOne = ({ nextStep }: IStepOneProps) => { + const { form, onSubmit } = useItem(nextStep); - -
    - -); + return ( + <> + +

    + Tambah Item Gacha +

    +

    + Lengkapi detail di bawah ini untuk menambahkan item gacha +

    +
    + +
    +
    + + + +
    + + + +
    + + ); +}; interface IStepTwoProps { onClose: () => void; - handleAddItem?: () => void; + handleAddItem?: () => Promise; resetStep: () => void; } -const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => ( - <> - -

    - Tambah Item -

    -

    - Apakah kamu yakin ingin -
    menambahkan item ini? -

    -
    - - - - - -); +const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => { + const { onConfirm, onCancel } = useConfirmItem( + onClose, + resetStep, + handleAddItem, + { + success: 'Item ditambahkan ke gacha item', + error: 'Item gagal ditambahkan ke gacha item', + } + ); + + return ( + <> + +

    + Tambah Item +

    +

    + Apakah kamu yakin ingin +
    menambahkan item ini? +

    +
    + + + + + + ); +}; export default ModalAddItem; diff --git a/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx b/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx index 4d9bc7e..19a22fe 100644 --- a/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx +++ b/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx @@ -1,10 +1,12 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules'; +import { useConfirmItem, useItem } from '../_hook/use-item'; +import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; interface IModalEditItem { isOpen: boolean; onClose: () => void; - handleEditItem?: () => void; + handleEditItem?: () => Promise; currentStep?: number; nextStep: () => void; prevStep: () => void; @@ -46,91 +48,117 @@ interface IStepOneProps { onClose: () => void; } -const StepOne = ({ nextStep }: IStepOneProps) => ( - <> - -

    - Edit Item Gacha -

    -

    - Silakan mengubah detail dari item yang diperlukan -

    -
    - -
    - - - -
    +const StepOne = ({ nextStep }: IStepOneProps) => { + const initialValues = { + itemName: 'Hoodie IMPHNEN Official 2025', + quantity: 10, + }; - -
    - -); + const { form, onSubmit } = useItem(nextStep, initialValues); + + return ( + <> + +

    + Edit Item Gacha +

    +

    + Silakan mengubah detail dari item yang diperlukan +

    +
    + +
    +
    + + + +
    + + + +
    + + ); +}; interface IStepTwoProps { onClose: () => void; - handleEditItem?: () => void; + handleEditItem?: () => Promise; resetStep: () => void; } -const StepTwo = ({ onClose, handleEditItem, resetStep }: IStepTwoProps) => ( - <> - -

    - Update Item -

    -

    - Apakah kamu yakin dengan -
    perubahan yang dilakukan? -

    -
    - - - - - -); +const StepTwo = ({ onClose, handleEditItem, resetStep }: IStepTwoProps) => { + const { onConfirm, onCancel } = useConfirmItem( + onClose, + resetStep, + handleEditItem, + { + success: 'Perubahan item berhasil dilakukan', + error: 'Perubahan item gagal dilakukan', + } + ); + return ( + <> + +

    + Update Item +

    +

    + Apakah kamu yakin dengan +
    perubahan yang dilakukan? +

    +
    + + + + + + ); +}; export default ModalEditItem; diff --git a/apps/backoffice/src/app/dashboard/_hook/use-item.ts b/apps/backoffice/src/app/dashboard/_hook/use-item.ts new file mode 100644 index 0000000..d432d56 --- /dev/null +++ b/apps/backoffice/src/app/dashboard/_hook/use-item.ts @@ -0,0 +1,61 @@ +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { + gachaItemSchema, + TGachaItem +} from '@imphnen-frontend-service/service'; +import { toast } from 'sonner'; + +export const useItem = ( + nextStep: () => void, + initialValues?: TGachaItem +) => { + const form = useForm({ + resolver: zodResolver(gachaItemSchema), + mode: 'all', + defaultValues: initialValues, + }); + + const onSubmit = form.handleSubmit((data) => { + console.log('Form data:', data); + nextStep(); + }); + + return { + form, + onSubmit, + }; +}; + +export const useConfirmItem = ( + onClose: () => void, + resetStep: () => void, + actionFunction?: () => Promise, + messages?: { + success?: string; + error?: string; + } +) => { + const onConfirm = async () => { + try { + // const result = await actionFunction?.(); + // result ? toast.success(messages?.success) : toast.error(messages?.error); + toast.success(messages?.success); + onClose(); + resetStep(); + } catch (error) { + console.log(error); + toast.error(messages?.error); + } + }; + + const onCancel = () => { + onClose(); + resetStep(); + }; + + return { + onConfirm, + onCancel, + }; +}; diff --git a/apps/backoffice/src/app/dashboard/page.tsx b/apps/backoffice/src/app/dashboard/page.tsx index d337e53..be30690 100644 --- a/apps/backoffice/src/app/dashboard/page.tsx +++ b/apps/backoffice/src/app/dashboard/page.tsx @@ -31,20 +31,17 @@ export const Components: FC = (): ReactElement => { return (
    - {/* Dashboard Header */}

    Dashboard

    - {/* Summary Section */}

    Summary

    - {/* Participants */}
    @@ -55,7 +52,6 @@ export const Components: FC = (): ReactElement => {
    - {/* Roll and Reroll */}
    @@ -68,7 +64,6 @@ export const Components: FC = (): ReactElement => {
    - {/* Redeem */}
    @@ -79,7 +74,6 @@ export const Components: FC = (): ReactElement => {
    - {/* Inactive Users */}
    @@ -94,7 +88,6 @@ export const Components: FC = (): ReactElement => {
    - {/* Gacha Items Section */}

    @@ -111,21 +104,20 @@ export const Components: FC = (): ReactElement => {

    - {/* Gacha Items List */}
    {[1, 2, 3, 4, 5, 6].map((item) => (
    -
    +

    Lanyard IMPHNEN

    -
    +
    Prize {item} - Chance Rate: (0.1%) + Quantity: 10
    @@ -148,12 +140,7 @@ export const Components: FC = (): ReactElement => {
    - {/* Lebih baik gunakan gambar yang sudah di-clip dengan size height: 78px daripada hard-code object-position dan margin */} - Lanyard IMPHNEN + Lanyard IMPHNEN
    ))}
    @@ -174,9 +161,6 @@ export const Components: FC = (): ReactElement => { currentStep={currentStep} isOpen={showModalAddItem} onClose={() => setShowModalAddItem(false)} - handleAddItem={() => { - console.log('Item added'); - }} nextStep={nextStep} prevStep={prevStep} resetStep={resetStep} @@ -187,9 +171,6 @@ export const Components: FC = (): ReactElement => { currentStep={currentStep} isOpen={showModalEditItem} onClose={() => setShowModalEditItem(false)} - handleEditItem={() => { - console.log('Item edited'); - }} nextStep={nextStep} prevStep={prevStep} resetStep={resetStep} @@ -199,9 +180,6 @@ export const Components: FC = (): ReactElement => { setShowModalDeleteItem(false)} - handleDeleteItem={() => { - console.log('Item deleted'); - }} /> ); diff --git a/apps/backoffice/src/app/gacha-roll/page.tsx b/apps/backoffice/src/app/gacha-roll/page.tsx index 0bc7499..c57d204 100644 --- a/apps/backoffice/src/app/gacha-roll/page.tsx +++ b/apps/backoffice/src/app/gacha-roll/page.tsx @@ -184,9 +184,6 @@ export const Components: FC = (): ReactElement => { currentStep={currentStep} isOpen={showModalAddItem} onClose={() => setShowModalAddItem(false)} - handleAddItem={() => { - console.log('Item added'); - }} nextStep={nextStep} prevStep={prevStep} resetStep={resetStep} @@ -195,9 +192,6 @@ export const Components: FC = (): ReactElement => { currentStep={currentStep} isOpen={showModalUpdateItem} onClose={() => setShowModalUpdateItem(false)} - handleUpdateItem={() => { - console.log('Item updated'); - }} nextStep={nextStep} prevStep={prevStep} resetStep={resetStep} diff --git a/libs/service/src/schemas/gacha/index.ts b/libs/service/src/schemas/gacha/index.ts index 2e9065b..765037d 100644 --- a/libs/service/src/schemas/gacha/index.ts +++ b/libs/service/src/schemas/gacha/index.ts @@ -1,5 +1,31 @@ import { z } from 'zod'; +export const gachaItemSchema = z.object({ + itemName: z + .string({ + required_error: 'Nama item tidak boleh kosong', + invalid_type_error: 'Nama item harus berupa string', + }) + .min(1, 'Nama item tidak boleh kosong'), + quantity: z + .number({ + required_error: 'Quantity tidak boleh kosong', + invalid_type_error: 'Quantity harus berupa angka', + }) + .min(1, 'Quantity paling sedikit adalah 1'), + foto: z + .instanceof(File) + .optional() + .refine( + (file) => !file || file.size <= 5000000, // 5MB in bytes + 'Ukuran file maksimal 5MB' + ) + .refine( + (file) => !file || ['image/jpeg', 'image/png', 'image/webp'].includes(file.type), + 'Format file harus JPG, PNG, atau WEBP' + ) +}); + export const gachaRollItemSchema = z.object({ itemName: z .string({ diff --git a/libs/service/src/types/gacha/index.ts b/libs/service/src/types/gacha/index.ts index 3c0493d..14a1415 100644 --- a/libs/service/src/types/gacha/index.ts +++ b/libs/service/src/types/gacha/index.ts @@ -1,3 +1,9 @@ +export type TGachaItem = { + itemName: string; + quantity: number; + foto?: File; +}; + export type TGachaRollItem = { itemName: string; quantity: number; diff --git a/libs/ui/src/organisms/controlled-field/controlled-input-field.tsx b/libs/ui/src/organisms/controlled-field/controlled-input-field.tsx index e8e10b9..6f89836 100644 --- a/libs/ui/src/organisms/controlled-field/controlled-input-field.tsx +++ b/libs/ui/src/organisms/controlled-field/controlled-input-field.tsx @@ -17,16 +17,27 @@ export const ControlledInputField = ( const { field, fieldState } = useController(props); const handleChange = (e: React.ChangeEvent) => { - const value = - props.type === 'number' ? Number(e.target.value) : e.target.value; + let value; + + if (props.type === 'number') { + value = Number(e.target.value); + } else if (props.type === 'file') { + value = e.target.files?.[0]; + } else { + value = e.target.value; + } field.onChange(value); }; + const inputProps = + props.type === 'file' + ? { ...props, ...field, value: undefined } + : { ...props, ...field }; + return ( ); From a7c67cb77ffcf954f8b2ddbbd75c9486cb816a7c Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Mon, 7 Apr 2025 02:19:07 +0700 Subject: [PATCH 32/37] feat: add permissions and roles page - Tambah halaman permissions & roles beserta modal - Update Backoffice Sidebar - Update styling label pada input field --- .../_components/modal-add-permission.tsx | 132 ++++++++++++ .../_components/modal-delete-permission.tsx | 72 +++++++ .../_components/modal-update-permission.tsx | 62 ++++++ .../src/app/permissions/_hook/use-item.ts | 61 ++++++ .../backoffice/src/app/permissions/layout.tsx | 18 ++ apps/backoffice/src/app/permissions/page.tsx | 202 +++++++++++++++++ .../app/roles/_components/modal-add-role.tsx | 185 ++++++++++++++++ .../roles/_components/modal-delete-role.tsx | 72 +++++++ .../roles/_components/modal-update-role.tsx | 111 ++++++++++ .../src/app/roles/_hook/use-item.ts | 61 ++++++ apps/backoffice/src/app/roles/layout.tsx | 18 ++ apps/backoffice/src/app/roles/page.tsx | 203 ++++++++++++++++++ .../src/molecules/input-field/input-field.tsx | 4 +- .../backoffice-sidebar/backoffice-sidebar.tsx | 26 +++ 14 files changed, 1225 insertions(+), 2 deletions(-) create mode 100644 apps/backoffice/src/app/permissions/_components/modal-add-permission.tsx create mode 100644 apps/backoffice/src/app/permissions/_components/modal-delete-permission.tsx create mode 100644 apps/backoffice/src/app/permissions/_components/modal-update-permission.tsx create mode 100644 apps/backoffice/src/app/permissions/_hook/use-item.ts create mode 100644 apps/backoffice/src/app/permissions/layout.tsx create mode 100644 apps/backoffice/src/app/permissions/page.tsx create mode 100644 apps/backoffice/src/app/roles/_components/modal-add-role.tsx create mode 100644 apps/backoffice/src/app/roles/_components/modal-delete-role.tsx create mode 100644 apps/backoffice/src/app/roles/_components/modal-update-role.tsx create mode 100644 apps/backoffice/src/app/roles/_hook/use-item.ts create mode 100644 apps/backoffice/src/app/roles/layout.tsx create mode 100644 apps/backoffice/src/app/roles/page.tsx diff --git a/apps/backoffice/src/app/permissions/_components/modal-add-permission.tsx b/apps/backoffice/src/app/permissions/_components/modal-add-permission.tsx new file mode 100644 index 0000000..a3b6243 --- /dev/null +++ b/apps/backoffice/src/app/permissions/_components/modal-add-permission.tsx @@ -0,0 +1,132 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { Modal } from '@imphnen-frontend-service/ui/molecules'; +import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; +import { useItem, useConfirmItem } from '../_hook/use-item'; + +interface IModalAddPermission { + isOpen: boolean; + onClose: () => void; + handleAddItem?: () => Promise; + currentStep?: number; + nextStep: () => void; + prevStep: () => void; + resetStep: () => void; +} + +const ModalAddPermission = ({ + isOpen, + onClose, + currentStep, + nextStep, + resetStep, + handleAddItem, +}: IModalAddPermission) => { + return ( + { + onClose(); + resetStep(); + }} + disableEscapeKeyDown={true} + > + {currentStep === 1 && } + {currentStep === 2 && ( + + )} + + ); +}; + +interface IStepOneProps { + nextStep: () => void; + onClose: () => void; +} + +const StepOne = ({ nextStep }: IStepOneProps) => { + const { form, onSubmit } = useItem(nextStep); + + return ( + <> + +

    + Tambah Permissions +

    +
    + +
    + + + + +
    + + ); +}; + +interface IStepTwoProps { + onClose: () => void; + handleAddItem?: () => Promise; + resetStep: () => void; +} + +const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => { + const { onConfirm, onCancel } = useConfirmItem( + onClose, + resetStep, + handleAddItem, + { + success: 'Data permissions berhasil ditambahkan', + error: 'Data permissions gagal ditambahkan', + } + ); + + return ( + <> + +

    + Tambah Permissions +

    +

    + Apakah kamu yakin ingin +
    menambahkan permission ini? +

    +
    + + + + + + ); +}; + +export default ModalAddPermission; diff --git a/apps/backoffice/src/app/permissions/_components/modal-delete-permission.tsx b/apps/backoffice/src/app/permissions/_components/modal-delete-permission.tsx new file mode 100644 index 0000000..ea96c09 --- /dev/null +++ b/apps/backoffice/src/app/permissions/_components/modal-delete-permission.tsx @@ -0,0 +1,72 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { Modal } from '@imphnen-frontend-service/ui/molecules'; +import { useConfirmItem } from '../_hook/use-item'; + +interface IModalDeletePermission { + isOpen: boolean; + onClose: () => void; + handleDelete?: () => Promise; + currentStep?: number; + nextStep: () => void; + prevStep: () => void; + resetStep: () => void; +} + +const ModalDeletePermission = ({ + isOpen, + onClose, + resetStep, + handleDelete, +}: IModalDeletePermission) => { + const { onConfirm } = useConfirmItem(onClose, resetStep, handleDelete, { + success: 'Data permissions berhasil dihapus', + error: 'Data permissions gagal dihapus', + }); + + return ( + + + Delete? +
    +

    + Delete Permissions +

    +

    + Apakah kamu yakin untuk menghapus permission ini? Menghapus data ini + mungkin akan mempengaruhi fungsional sistem +

    +
    +
    + + + + +
    + ); +}; + +export default ModalDeletePermission; diff --git a/apps/backoffice/src/app/permissions/_components/modal-update-permission.tsx b/apps/backoffice/src/app/permissions/_components/modal-update-permission.tsx new file mode 100644 index 0000000..985b125 --- /dev/null +++ b/apps/backoffice/src/app/permissions/_components/modal-update-permission.tsx @@ -0,0 +1,62 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules'; +import { useConfirmItem } from '../_hook/use-item'; + +interface IModalUpdatePermission { + isOpen: boolean; + onClose: () => void; + handleUpdate?: () => Promise; + currentStep?: number; + nextStep: () => void; + prevStep: () => void; + resetStep: () => void; +} + +const ModalUpdatePermission = ({ + isOpen, + onClose, + resetStep, + handleUpdate, +}: IModalUpdatePermission) => { + const { onConfirm } = useConfirmItem(onClose, resetStep, handleUpdate, { + success: 'Perubahan permissions berhasil dilakukan', + error: 'Perubahan permissions gagal dilakukan', + }); + + return ( + + +

    + Update Permissions +

    +
    + + + + + +
    + ); +}; + +export default ModalUpdatePermission; diff --git a/apps/backoffice/src/app/permissions/_hook/use-item.ts b/apps/backoffice/src/app/permissions/_hook/use-item.ts new file mode 100644 index 0000000..7b9bcf0 --- /dev/null +++ b/apps/backoffice/src/app/permissions/_hook/use-item.ts @@ -0,0 +1,61 @@ +import { useForm } from 'react-hook-form'; +// import { zodResolver } from '@hookform/resolvers/zod'; +// import { +// gachaRollItemSchema, +// TGachaRollItem +// } from '@imphnen-frontend-service/service'; +import { toast } from 'sonner'; + +export const useItem = ( + nextStep: () => void, + initialValues?: any +) => { + const form = useForm({ + // resolver: zodResolver(), + mode: 'all', + defaultValues: initialValues, + }); + + const onSubmit = form.handleSubmit((data) => { + console.log('Form data:', data); + nextStep(); + }); + + return { + form, + onSubmit, + }; +}; + +export const useConfirmItem = ( + onClose: () => void, + resetStep: () => void, + actionFunction?: () => Promise, + messages?: { + success?: string; + error?: string; + } +) => { + const onConfirm = async () => { + try { + // const result = await actionFunction?.(); + // result ? toast.success(messages?.success) : toast.error(messages?.error); + toast.success(messages?.success); + onClose(); + resetStep(); + } catch (error) { + console.log(error); + toast.error(messages?.error); + } + }; + + const onCancel = () => { + onClose(); + resetStep(); + }; + + return { + onConfirm, + onCancel, + }; +}; diff --git a/apps/backoffice/src/app/permissions/layout.tsx b/apps/backoffice/src/app/permissions/layout.tsx new file mode 100644 index 0000000..a770c99 --- /dev/null +++ b/apps/backoffice/src/app/permissions/layout.tsx @@ -0,0 +1,18 @@ +import { FC, ReactElement } from 'react'; +import { Outlet } from 'react-router-dom'; +import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms'; + +export const AppLayout: FC = (): ReactElement => { + return ( +
    +
    + +
    + +
    +
    +
    + ); +}; + +export default AppLayout; diff --git a/apps/backoffice/src/app/permissions/page.tsx b/apps/backoffice/src/app/permissions/page.tsx new file mode 100644 index 0000000..95e7fb5 --- /dev/null +++ b/apps/backoffice/src/app/permissions/page.tsx @@ -0,0 +1,202 @@ +import { FC, Fragment, ReactElement, 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'; +import { + ColumnDef, + getCoreRowModel, + getPaginationRowModel, + 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'; +import React from 'react'; + +interface Permission { + id: number; + name: string; +} + +const mockData: Permission[] = [ + { id: 1, name: 'Read' }, + { id: 2, name: 'Create' }, + { id: 3, name: 'Update' }, + { id: 4, name: 'Delete' }, +]; + +export const Components: FC = (): ReactElement => { + const [showModalAddItem, setShowModalAddItem] = useState(false); + const [showModalUpdateItem, setShowModalUpdateItem] = useState(false); + const [showModalDeleteItem, setShowModalDeleteItem] = useState(false); + + const { + step: currentStep, + nextStep, + prevStep, + resetStep, + } = useQueryState('step', { + defaultValue: 1, + maxValue: 2, + minValue: 1, + }); + + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 9, + }); + + const [rowSelection, setRowSelection] = React.useState({}); + + const columns: ColumnDef[] = [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ), + }, + { + header: 'No', + accessorKey: 'id', + }, + { + header: 'Name', + accessorKey: 'name', + }, + { + header: 'Action', + cell: () => ( +
    + + +
    + ), + }, + ]; + + const table = useReactTable({ + data: mockData, + columns, + state: { + pagination, + rowSelection, + }, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onPaginationChange: setPagination, + pageCount: Math.ceil(mockData.length / pagination.pageSize), + manualPagination: false, + }); + + return ( + +
    +
    +

    Permissions

    +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    + + setShowModalAddItem(false)} + nextStep={nextStep} + prevStep={prevStep} + resetStep={resetStep} + /> + setShowModalUpdateItem(false)} + nextStep={nextStep} + prevStep={prevStep} + resetStep={resetStep} + /> + setShowModalDeleteItem(false)} + nextStep={nextStep} + prevStep={prevStep} + resetStep={resetStep} + /> +
    + ); +}; + +export default Components; diff --git a/apps/backoffice/src/app/roles/_components/modal-add-role.tsx b/apps/backoffice/src/app/roles/_components/modal-add-role.tsx new file mode 100644 index 0000000..da66e16 --- /dev/null +++ b/apps/backoffice/src/app/roles/_components/modal-add-role.tsx @@ -0,0 +1,185 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { Modal } from '@imphnen-frontend-service/ui/molecules'; +import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; +import { useItem, useConfirmItem } from '../_hook/use-item'; + +interface IModalAddRole { + isOpen: boolean; + onClose: () => void; + handleAdd?: () => Promise; + currentStep?: number; + nextStep: () => void; + prevStep: () => void; + resetStep: () => void; +} + +const ModalAddRole = ({ + isOpen, + onClose, + currentStep, + nextStep, + resetStep, + handleAdd, +}: IModalAddRole) => { + return ( + { + onClose(); + resetStep(); + }} + disableEscapeKeyDown={true} + > + {currentStep === 1 && } + {currentStep === 2 && ( + + )} + + ); +}; + +interface IStepOneProps { + nextStep: () => void; + onClose: () => void; +} + +const StepOne = ({ nextStep }: IStepOneProps) => { + const { form, onSubmit } = useItem(nextStep); + + return ( + <> + +

    + Tambah Roles +

    +
    + +
    +
    + +
    + +
    + + Permissions + +
    + {[ + 'Gacha Items', + 'Gacha Roll', + 'Roll', + 'Users', + 'Gacha Claim', + ].map((title) => ( +
    + {title} +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + ))} +
    +
    + + + +
    + + ); +}; + +interface IStepTwoProps { + onClose: () => void; + handleAdd?: () => Promise; + resetStep: () => void; +} + +const StepTwo = ({ onClose, handleAdd, resetStep }: IStepTwoProps) => { + const { onConfirm, onCancel } = useConfirmItem( + onClose, + resetStep, + handleAdd, + { + success: 'Data role berhasil ditambahkan', + error: 'Data role gagal ditambahkan', + } + ); + + return ( + <> + +

    + Tambah Roles +

    +

    + Apakah kamu yakin ingin +
    menambahkan role ini? +

    +
    + + + + + + ); +}; + +export default ModalAddRole; diff --git a/apps/backoffice/src/app/roles/_components/modal-delete-role.tsx b/apps/backoffice/src/app/roles/_components/modal-delete-role.tsx new file mode 100644 index 0000000..68c45e9 --- /dev/null +++ b/apps/backoffice/src/app/roles/_components/modal-delete-role.tsx @@ -0,0 +1,72 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { Modal } from '@imphnen-frontend-service/ui/molecules'; +import { useConfirmItem } from '../_hook/use-item'; + +interface IModalDeletePermission { + isOpen: boolean; + onClose: () => void; + handleDelete?: () => Promise; + currentStep?: number; + nextStep: () => void; + prevStep: () => void; + resetStep: () => void; +} + +const ModalDeletePermission = ({ + isOpen, + onClose, + resetStep, + handleDelete, +}: IModalDeletePermission) => { + const { onConfirm } = useConfirmItem(onClose, resetStep, handleDelete, { + success: 'Data roles berhasil dihapus', + error: 'Data roles gagal dihapus', + }); + + return ( + + + Delete? +
    +

    + Delete Roles +

    +

    + Apakah kamu yakin untuk menghapus role ini? Menghapus data ini + mungkin akan mempengaruhi fungsional sistem +

    +
    +
    + + + + +
    + ); +}; + +export default ModalDeletePermission; diff --git a/apps/backoffice/src/app/roles/_components/modal-update-role.tsx b/apps/backoffice/src/app/roles/_components/modal-update-role.tsx new file mode 100644 index 0000000..f2605c7 --- /dev/null +++ b/apps/backoffice/src/app/roles/_components/modal-update-role.tsx @@ -0,0 +1,111 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { InputField, Modal } from '@imphnen-frontend-service/ui/molecules'; +import { useConfirmItem } from '../_hook/use-item'; + +interface IModalUpdatePermission { + isOpen: boolean; + onClose: () => void; + handleUpdate?: () => Promise; + currentStep?: number; + nextStep: () => void; + prevStep: () => void; + resetStep: () => void; +} + +const ModalUpdatePermission = ({ + isOpen, + onClose, + resetStep, + handleUpdate, +}: IModalUpdatePermission) => { + const { onConfirm } = useConfirmItem(onClose, resetStep, handleUpdate, { + success: 'Perubahan roles berhasil dilakukan', + error: 'Perubahan roles gagal dilakukan', + }); + + return ( + + +

    + Update Roles +

    +
    + +
    + +
    + +
    + + Permissions + +
    + {['Gacha Items', 'Gacha Roll', 'Roll', 'Users', 'Gacha Claim'].map( + (title) => ( +
    + {title} +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + ) + )} +
    +
    + + +
    +
    + ); +}; + +export default ModalUpdatePermission; diff --git a/apps/backoffice/src/app/roles/_hook/use-item.ts b/apps/backoffice/src/app/roles/_hook/use-item.ts new file mode 100644 index 0000000..7b9bcf0 --- /dev/null +++ b/apps/backoffice/src/app/roles/_hook/use-item.ts @@ -0,0 +1,61 @@ +import { useForm } from 'react-hook-form'; +// import { zodResolver } from '@hookform/resolvers/zod'; +// import { +// gachaRollItemSchema, +// TGachaRollItem +// } from '@imphnen-frontend-service/service'; +import { toast } from 'sonner'; + +export const useItem = ( + nextStep: () => void, + initialValues?: any +) => { + const form = useForm({ + // resolver: zodResolver(), + mode: 'all', + defaultValues: initialValues, + }); + + const onSubmit = form.handleSubmit((data) => { + console.log('Form data:', data); + nextStep(); + }); + + return { + form, + onSubmit, + }; +}; + +export const useConfirmItem = ( + onClose: () => void, + resetStep: () => void, + actionFunction?: () => Promise, + messages?: { + success?: string; + error?: string; + } +) => { + const onConfirm = async () => { + try { + // const result = await actionFunction?.(); + // result ? toast.success(messages?.success) : toast.error(messages?.error); + toast.success(messages?.success); + onClose(); + resetStep(); + } catch (error) { + console.log(error); + toast.error(messages?.error); + } + }; + + const onCancel = () => { + onClose(); + resetStep(); + }; + + return { + onConfirm, + onCancel, + }; +}; diff --git a/apps/backoffice/src/app/roles/layout.tsx b/apps/backoffice/src/app/roles/layout.tsx new file mode 100644 index 0000000..a770c99 --- /dev/null +++ b/apps/backoffice/src/app/roles/layout.tsx @@ -0,0 +1,18 @@ +import { FC, ReactElement } from 'react'; +import { Outlet } from 'react-router-dom'; +import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms'; + +export const AppLayout: FC = (): ReactElement => { + return ( +
    +
    + +
    + +
    +
    +
    + ); +}; + +export default AppLayout; diff --git a/apps/backoffice/src/app/roles/page.tsx b/apps/backoffice/src/app/roles/page.tsx new file mode 100644 index 0000000..d01581d --- /dev/null +++ b/apps/backoffice/src/app/roles/page.tsx @@ -0,0 +1,203 @@ +import { FC, Fragment, ReactElement, 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'; +import { + ColumnDef, + getCoreRowModel, + getPaginationRowModel, + 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'; +import React from 'react'; + +interface Role { + id: number; + name: string; +} + +const mockData: Role[] = [ + { id: 1, name: 'Admin' }, + { id: 2, name: 'Admin Pembayaran' }, + { id: 3, name: 'Staff' }, + { id: 4, name: 'Staff Aktivasi User' }, + { id: 5, name: 'User' }, +]; + +export const Components: FC = (): ReactElement => { + const [showModalAddItem, setShowModalAddItem] = useState(false); + const [showModalUpdateItem, setShowModalUpdateItem] = useState(false); + const [showModalDeleteItem, setShowModalDeleteItem] = useState(false); + + const { + step: currentStep, + nextStep, + prevStep, + resetStep, + } = useQueryState('step', { + defaultValue: 1, + maxValue: 2, + minValue: 1, + }); + + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 9, + }); + + const [rowSelection, setRowSelection] = React.useState({}); + + const columns: ColumnDef[] = [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ), + }, + { + header: 'ID', + accessorKey: 'id', + }, + { + header: 'Roles Name', + accessorKey: 'name', + }, + { + header: 'Action', + cell: () => ( +
    + + +
    + ), + }, + ]; + + const table = useReactTable({ + data: mockData, + columns, + state: { + pagination, + rowSelection, + }, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onPaginationChange: setPagination, + pageCount: Math.ceil(mockData.length / pagination.pageSize), + manualPagination: false, + }); + + return ( + +
    +
    +

    Roles

    +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    + + setShowModalAddItem(false)} + nextStep={nextStep} + prevStep={prevStep} + resetStep={resetStep} + /> + setShowModalUpdateItem(false)} + nextStep={nextStep} + prevStep={prevStep} + resetStep={resetStep} + /> + setShowModalDeleteItem(false)} + nextStep={nextStep} + prevStep={prevStep} + resetStep={resetStep} + /> +
    + ); +}; + +export default Components; diff --git a/libs/ui/src/molecules/input-field/input-field.tsx b/libs/ui/src/molecules/input-field/input-field.tsx index 463be6c..08460ea 100644 --- a/libs/ui/src/molecules/input-field/input-field.tsx +++ b/libs/ui/src/molecules/input-field/input-field.tsx @@ -54,7 +54,7 @@ export const InputField: FC = ({