From 8887e3b0a986d30849d72f81473fa20551bf974f Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Sat, 5 Apr 2025 06:05:50 +0700 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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 ( );