diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx index 0582c9d..8052519 100644 --- a/src/components/Pagination.tsx +++ b/src/components/Pagination.tsx @@ -20,18 +20,33 @@ const Pagination = ({ const getPageNumbers = () => { const pages: (number | string)[] = []; - if (totalPages <= 5) { + if (totalPages <= 6) { // Hiển thị tất cả nếu trang ít for (let i = 1; i <= totalPages; i++) { pages.push(i); } } else { if (currentPage <= 3) { - pages.push(1, 2, 3, 'dot', totalPages); + pages.push(1, 2, 3, 4, 'dot', totalPages); } else if (currentPage >= totalPages - 2) { - pages.push(1, 'dot', totalPages - 2, totalPages - 1, totalPages); + pages.push( + 1, + 'dot', + totalPages - 3, + totalPages - 2, + totalPages - 1, + totalPages, + ); } else { - pages.push(1, 'dot', currentPage, 'dot', totalPages); + pages.push( + 1, + 'dot', + currentPage - 1, + currentPage, + currentPage + 1, + 'dot', + totalPages, + ); } } @@ -48,6 +63,7 @@ const Pagination = ({ variant="outline" size="icon-sm" disabled={currentPage === 1} + onClick={() => onPageChange(Number(currentPage - 1))} className="cursor-pointer" > @@ -85,6 +101,7 @@ const Pagination = ({ variant="outline" size="icon-sm" disabled={currentPage === totalPages} + onClick={() => onPageChange(Number(currentPage + 1))} className="cursor-pointer" > diff --git a/src/components/auth/auth-provider.tsx b/src/components/auth/auth-provider.tsx index fa33520..4175ff9 100644 --- a/src/components/auth/auth-provider.tsx +++ b/src/components/auth/auth-provider.tsx @@ -1,5 +1,6 @@ -import { ClientSession, useSession } from '@lib/auth-client'; -import { BetterFetchError } from 'better-auth/client'; +import { sessionQueries } from '@/service/queries'; +import { ClientSession } from '@lib/auth-client'; +import { useQuery } from '@tanstack/react-query'; import { createContext, useContext, useMemo } from 'react'; export type UserContext = { @@ -7,12 +8,13 @@ export type UserContext = { isAuth: boolean; isAdmin: boolean; isPending: boolean; - error: BetterFetchError | null; + error: Error | null; }; const AuthContext = createContext(null); + export function AuthProvider({ children }: { children: React.ReactNode }) { - const { data: session, isPending, error } = useSession(); + const { data: session, isPending, error } = useQuery(sessionQueries.user()); const contextSession: UserContext = useMemo( () => ({ diff --git a/src/components/form/account/change-password-form.tsx b/src/components/form/account/change-password-form.tsx index 536f65f..7833837 100644 --- a/src/components/form/account/change-password-form.tsx +++ b/src/components/form/account/change-password-form.tsx @@ -1,8 +1,9 @@ import { useAppForm } from '@hooks/use-app-form'; -import { authClient } from '@lib/auth-client'; import { m } from '@paraglide/messages'; import { KeyIcon } from '@phosphor-icons/react'; -import { ChangePassword, ChangePasswordFormSchema } from '@service/user.schema'; +import { changePassword } from '@service/profile.api'; +import { ChangePassword, changePasswordFormSchema } from '@service/user.schema'; +import { useMutation } from '@tanstack/react-query'; import { Card, CardContent, CardHeader, CardTitle } from '@ui/card'; import { Field, FieldGroup } from '@ui/field'; import { toast } from 'sonner'; @@ -14,37 +15,33 @@ const defaultValues: ChangePassword = { }; const ChangePasswordForm = () => { + const { mutate: changePasswordMutation, isPending } = useMutation({ + mutationFn: changePassword, + onSuccess: () => { + form.reset(); + toast.success(m.change_password_messages_change_password_success(), { + richColors: true, + }); + }, + onError: (error: ReturnError) => { + console.error(error); + const code = error.code as Parameters< + typeof m.backend_message + >[0]['code']; + toast.error(m.backend_message({ code }), { + richColors: true, + }); + }, + }); + const form = useAppForm({ defaultValues, validators: { - onSubmit: ChangePasswordFormSchema, - onChange: ChangePasswordFormSchema, + onSubmit: changePasswordFormSchema, + onChange: changePasswordFormSchema, }, onSubmit: async ({ value }) => { - await authClient.changePassword( - { - newPassword: value.newPassword, - currentPassword: value.currentPassword, - revokeOtherSessions: true, - }, - { - onSuccess: () => { - form.reset(); - toast.success( - m.change_password_messages_change_password_success(), - { - richColors: true, - }, - ); - }, - onError: (ctx) => { - console.error(ctx.error.code); - toast.error(m.backend_message({ code: ctx.error.code }), { - richColors: true, - }); - }, - }, - ); + changePasswordMutation({ data: value }); }, }); @@ -70,6 +67,7 @@ const ChangePasswordForm = () => { {(field) => ( )} @@ -91,7 +89,10 @@ const ChangePasswordForm = () => { - + diff --git a/src/components/form/account/profile-form.tsx b/src/components/form/account/profile-form.tsx index 20f6858..4e3a265 100644 --- a/src/components/form/account/profile-form.tsx +++ b/src/components/form/account/profile-form.tsx @@ -1,13 +1,13 @@ +import { Skeleton } from '@/components/ui/skeleton'; +import { updateProfile } from '@/service/profile.api'; import { useAuth } from '@components/auth/auth-provider'; import AvatarUser from '@components/avatar/avatar-user'; import RoleBadge from '@components/avatar/role-badge'; import { useAppForm } from '@hooks/use-app-form'; -import { authClient } from '@lib/auth-client'; import { m } from '@paraglide/messages'; import { UserCircleIcon } from '@phosphor-icons/react'; -import { uploadProfileImage } from '@service/profile.api'; import { ProfileInput, profileUpdateSchema } from '@service/profile.schema'; -import { useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Card, CardContent, CardHeader, CardTitle } from '@ui/card'; import { Field, FieldGroup, FieldLabel } from '@ui/field'; import { Input } from '@ui/input'; @@ -24,6 +24,31 @@ const ProfileForm = () => { const { data: session, isPending } = useAuth(); const queryClient = useQueryClient(); + const { mutate: updateProfileMutation, isPending: isRunning } = useMutation({ + mutationFn: updateProfile, + onSuccess: () => { + form.reset(); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + queryClient.refetchQueries({ + queryKey: ['auth', 'session'], + }); + toast.success(m.profile_messages_update_success(), { + richColors: true, + }); + }, + onError: (error: ReturnError) => { + console.error(error); + const code = error.code as Parameters< + typeof m.backend_message + >[0]['code']; + toast.error(m.backend_message({ code }), { + richColors: true, + }); + }, + }); + const form = useAppForm({ defaultValues: { ...defaultValues, @@ -34,55 +59,19 @@ const ProfileForm = () => { onChange: profileUpdateSchema, }, onSubmit: async ({ value }) => { - try { - let imageKey; - if (value.image) { - // upload image - const formData = new FormData(); - formData.set('file', value.image); - const { imageKey: uploadedKey } = await uploadProfileImage({ - data: formData, - }); - imageKey = uploadedKey; - } - - await authClient.updateUser( - { - name: value.name, - image: imageKey, - }, - { - onSuccess: () => { - form.reset(); - if (fileInputRef.current) { - fileInputRef.current.value = ''; - } - queryClient.refetchQueries({ - queryKey: ['auth', 'session'], - }); - toast.success(m.profile_messages_update_success(), { - richColors: true, - }); - }, - onError: (ctx) => { - console.error(ctx.error.code); - toast.error(m.backend_message({ code: ctx.error.code }), { - richColors: true, - }); - }, - }, - ); - } catch (error) { - console.error('update load file', error); - toast.error(JSON.stringify(error), { - richColors: true, - }); + const formData = new FormData(); + formData.set('name', value.name); + if (value.image) { + formData.set('file', value.image); } + + updateProfileMutation({ data: formData }); }, }); - if (isPending) return null; - if (!session?.user?.name) return null; + if (isPending || !session?.user?.name) { + return ; + } return ( @@ -136,7 +125,10 @@ const ProfileForm = () => { - + diff --git a/src/components/form/house/admin-edit-house-form.tsx b/src/components/form/house/admin-edit-house-form.tsx index db66dda..8cc8354 100644 --- a/src/components/form/house/admin-edit-house-form.tsx +++ b/src/components/form/house/admin-edit-house-form.tsx @@ -13,7 +13,7 @@ import { slugify } from '@utils/helper'; import { toast } from 'sonner'; type EditHouseFormProps = { - data: OrganizationWithMembers; + data: HouseWithMembers; onSubmit: (open: boolean) => void; mutateKey: string; }; diff --git a/src/components/house/current-user-action-group.tsx b/src/components/house/current-user-action-group.tsx index 7b84ced..5fa0644 100644 --- a/src/components/house/current-user-action-group.tsx +++ b/src/components/house/current-user-action-group.tsx @@ -7,10 +7,12 @@ import DeleteUserHouseAction from './delete-user-house-dialog'; import EditHouseAction from './edit-house-dialog'; type CurrentUserActionGroupProps = { + oneHouse: boolean; activeHouse: ReturnType['data']; }; const CurrentUserActionGroup = ({ + oneHouse, activeHouse, }: CurrentUserActionGroupProps) => { return ( @@ -22,10 +24,7 @@ const CurrentUserActionGroup = ({ - + - + {!oneHouse && } ); diff --git a/src/components/house/current-user-house-list.tsx b/src/components/house/current-user-house-list.tsx index 90a2b02..bb95792 100644 --- a/src/components/house/current-user-house-list.tsx +++ b/src/components/house/current-user-house-list.tsx @@ -2,8 +2,6 @@ import { authClient } from '@lib/auth-client'; import { cn } from '@lib/utils'; import { m } from '@paraglide/messages'; import { CheckIcon, WarehouseIcon } from '@phosphor-icons/react'; -import { housesQueries } from '@service/queries'; -import { useQuery } from '@tanstack/react-query'; import { Button } from '@ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@ui/card'; import parse from 'html-react-parser'; @@ -20,11 +18,15 @@ import { Skeleton } from '../ui/skeleton'; import CreateNewHouse from './create-house-dialog'; type CurrentUserHouseListProps = { + houses: HouseWithMembersCount[]; activeHouse: ReturnType['data']; }; -const CurrentUserHouseList = ({ activeHouse }: CurrentUserHouseListProps) => { - const { data: houses } = useQuery(housesQueries.currentUser()); +const CurrentUserHouseList = ({ + activeHouse, + houses, +}: CurrentUserHouseListProps) => { + // const { data: houses } = useQuery(housesQueries.currentUser()); const activeHouseAction = async ({ id, diff --git a/src/components/house/delete-house-dialog.tsx b/src/components/house/delete-house-dialog.tsx index ba41481..c6bcdeb 100644 --- a/src/components/house/delete-house-dialog.tsx +++ b/src/components/house/delete-house-dialog.tsx @@ -33,7 +33,7 @@ import RoleBadge from '../avatar/role-badge'; import { Spinner } from '../ui/spinner'; type DeleteHouseProps = { - data: OrganizationWithMembers; + data: HouseWithMembers; }; const DeleteHouseAction = ({ data }: DeleteHouseProps) => { diff --git a/src/components/house/edit-house-dialog.tsx b/src/components/house/edit-house-dialog.tsx index 6c8df93..2a16f71 100644 --- a/src/components/house/edit-house-dialog.tsx +++ b/src/components/house/edit-house-dialog.tsx @@ -16,7 +16,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@ui/tooltip'; import { useState } from 'react'; type EditHouseProps = { - data: OrganizationWithMembers; + data: HouseWithMembers; children: React.ReactNode; isPersonal?: boolean; }; diff --git a/src/components/house/house-column.tsx b/src/components/house/house-column.tsx index 3cd263a..4992614 100644 --- a/src/components/house/house-column.tsx +++ b/src/components/house/house-column.tsx @@ -7,7 +7,7 @@ import DeleteHouseAction from './delete-house-dialog'; import EditHouseAction from './edit-house-dialog'; import ViewDetailHouse from './view-house-detail-dialog'; -export const houseColumns: ColumnDef[] = [ +export const houseColumns: ColumnDef[] = [ { accessorKey: 'name', header: m.houses_page_ui_table_header_name(), diff --git a/src/components/house/view-house-detail-dialog.tsx b/src/components/house/view-house-detail-dialog.tsx index 5dd4f39..05a8ec0 100644 --- a/src/components/house/view-house-detail-dialog.tsx +++ b/src/components/house/view-house-detail-dialog.tsx @@ -24,7 +24,7 @@ import { formatters } from '@utils/formatters'; import RoleBadge from '../avatar/role-badge'; type ViewDetailProps = { - data: OrganizationWithMembers; + data: HouseWithMembers; }; const ViewDetailHouse = ({ data }: ViewDetailProps) => { diff --git a/src/hooks/use-session.ts b/src/hooks/use-session.ts deleted file mode 100644 index 416cdd4..0000000 --- a/src/hooks/use-session.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { sessionQueries } from '@service/queries'; -import { useQuery } from '@tanstack/react-query'; - -export function useSessionQuery() { - return useQuery(sessionQueries.user()); -} diff --git a/src/lib/auth.ts b/src/lib/auth.ts index e1b96d9..3d30ce0 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -77,43 +77,6 @@ export const auth = betterAuth({ }); }, }, - update: { - before: async (user, ctx) => { - if (ctx?.context.session && ctx?.path === '/update-user') { - const newUser = JSON.parse(JSON.stringify(user)); - const keys = Object.keys(newUser); - const oldUser = Object.fromEntries( - Object.entries(ctx?.context.session?.user).filter(([key]) => - keys.includes(key), - ), - ); - await createAuditLog({ - action: LOG_ACTION.UPDATE, - tableName: DB_TABLE.USER, - recordId: ctx?.context.session?.user.id, - oldValue: JSON.stringify(oldUser), - newValue: JSON.stringify(newUser), - userId: ctx?.context.session?.user.id, - }); - } - }, - }, - }, - account: { - update: { - after: async (account, context) => { - if (context?.path === '/change-password') { - await createAuditLog({ - action: LOG_ACTION.CHANGE_PASSWORD, - tableName: DB_TABLE.ACCOUNT, - recordId: account.id, - oldValue: 'Change Password', - newValue: 'Change Password', - userId: account.userId, - }); - } - }, - }, }, session: { create: { diff --git a/src/lib/errors/index.ts b/src/lib/errors/index.ts new file mode 100644 index 0000000..26cd1ee --- /dev/null +++ b/src/lib/errors/index.ts @@ -0,0 +1 @@ +export * from './parse-error'; diff --git a/src/lib/errors/parse-error.ts b/src/lib/errors/parse-error.ts new file mode 100644 index 0000000..29279d3 --- /dev/null +++ b/src/lib/errors/parse-error.ts @@ -0,0 +1,77 @@ +import { Prisma } from '@/generated/prisma/client'; + +export type ErrorBody = { + message?: unknown; + code?: unknown; + status?: unknown; +}; + +function hasErrorBody(error: unknown): error is { body: ErrorBody } { + return ( + typeof error === 'object' && + error !== null && + 'body' in error && + typeof (error as any).body === 'object' + ); +} + +export function parseError(error: unknown): { + message: string; + code?: string; + status?: number; +} { + // Recognize AppError even if it's a plain object from network + if ( + typeof error === 'object' && + error !== null && + 'name' in error && + (error as any).name === 'AppError' && + 'code' in error && + 'message' in error && + 'status' in error + ) { + return { + message: (error as any).message, + code: (error as any).code, + status: (error as any).status, + }; + } + + // better-auth / fetch error (có body) + if (hasErrorBody(error)) { + return { + message: + typeof error.body.message === 'string' + ? error.body.message + : 'Unknown error', + code: typeof error.body.code === 'string' ? error.body.code : undefined, + status: + typeof error.body.status === 'number' ? error.body.status : undefined, + }; + } + + // Prisma (giữ nguyên code như "P2002") + if (error instanceof Prisma.PrismaClientKnownRequestError) { + return { + message: error.message, + code: error.code, + status: 400, + }; + } + + // Error thường + if (error instanceof Error) { + return { + message: error.message, + code: 'NORMAL_ERROR', + status: undefined, + }; + } + + // Fallback + return { + message: String(error), + code: 'NORMAL_ERROR', + status: undefined, + }; +} diff --git a/src/routes/(app)/(auth)/management/houses.tsx b/src/routes/(app)/(auth)/management/houses.tsx index 6392485..a71d8c7 100644 --- a/src/routes/(app)/(auth)/management/houses.tsx +++ b/src/routes/(app)/(auth)/management/houses.tsx @@ -21,9 +21,12 @@ function RouteComponent() { return (
- + - +
); diff --git a/src/service/audit.api.ts b/src/service/audit.api.ts index a43f84b..2f8090f 100644 --- a/src/service/audit.api.ts +++ b/src/service/audit.api.ts @@ -1,8 +1,8 @@ import { prisma } from '@/db'; import { AuditWhereInput } from '@/generated/prisma/models'; +import { parseError } from '@lib/errors'; import { authMiddleware } from '@lib/middleware'; import { createServerFn } from '@tanstack/react-start'; -import { parseError } from '@utils/helper'; import { auditListSchema } from './audit.schema'; export const getAllAudit = createServerFn({ method: 'GET' }) diff --git a/src/utils/disk-storage.ts b/src/service/disk-storage.ts similarity index 87% rename from src/utils/disk-storage.ts rename to src/service/disk-storage.ts index 6ae31fc..5794f83 100644 --- a/src/utils/disk-storage.ts +++ b/src/service/disk-storage.ts @@ -1,3 +1,4 @@ +import { AppError } from '@/lib/errors'; import fs, { writeFile } from 'fs/promises'; import path from 'path'; @@ -19,8 +20,10 @@ export async function saveFile(key: string, file: Buffer | File) { return key; } catch (error) { console.error(`Error saving file: ${key}`, error); - throw new Error( + throw new AppError( + 'FILE_SAVE_ERROR', `Failed to save file: ${error instanceof Error ? error.message : 'Unknown error'}`, + 500, ); } } diff --git a/src/service/house.api.ts b/src/service/house.api.ts index 026c573..b5e0657 100644 --- a/src/service/house.api.ts +++ b/src/service/house.api.ts @@ -2,10 +2,10 @@ import { prisma } from '@/db'; import { OrganizationWhereInput } from '@/generated/prisma/models'; import { DB_TABLE, LOG_ACTION } from '@/types/enum'; import { auth } from '@lib/auth'; +import { parseError } from '@lib/errors'; import { authMiddleware } from '@lib/middleware'; import { createServerFn } from '@tanstack/react-start'; import { getRequestHeaders } from '@tanstack/react-start/server'; -import { parseError } from '@utils/helper'; import { baseHouse, houseCreateBESchema, @@ -33,31 +33,30 @@ export const getAllHouse = createServerFn({ method: 'GET' }) ], }; - const [list, total]: [OrganizationWithMembers[], number] = - await Promise.all([ - await prisma.organization.findMany({ - where, - orderBy: { createdAt: 'desc' }, - include: { - members: { - select: { - role: true, - user: { - select: { - id: true, - name: true, - email: true, - image: true, - }, + const [list, total]: [HouseWithMembers[], number] = await Promise.all([ + await prisma.organization.findMany({ + where, + orderBy: { createdAt: 'desc' }, + include: { + members: { + select: { + role: true, + user: { + select: { + id: true, + name: true, + email: true, + image: true, }, }, }, }, - take: limit, - skip, - }), - await prisma.organization.count({ where }), - ]); + }, + take: limit, + skip, + }), + await prisma.organization.count({ where }), + ]); const totalPage = Math.ceil(+total / limit); diff --git a/src/service/profile.api.ts b/src/service/profile.api.ts index 5024364..387a080 100644 --- a/src/service/profile.api.ts +++ b/src/service/profile.api.ts @@ -1,17 +1,92 @@ +import { prisma } from '@/db'; +import { auth } from '@/lib/auth'; +import { parseError } from '@/lib/errors'; +import { DB_TABLE, LOG_ACTION } from '@/types/enum'; import { authMiddleware } from '@lib/middleware'; import { createServerFn } from '@tanstack/react-start'; -import { saveFile } from '@utils/disk-storage'; +import { getRequestHeaders } from '@tanstack/react-start/server'; import z from 'zod'; +import { saveFile } from './disk-storage'; +import { createAuditLog } from './repository'; +import { changePasswordBESchema } from './user.schema'; -export const uploadProfileImage = createServerFn({ method: 'POST' }) +export const updateProfile = createServerFn({ method: 'POST' }) .middleware([authMiddleware]) .inputValidator(z.instanceof(FormData)) - .handler(async ({ data: formData }) => { - const uuid = crypto.randomUUID(); - const file = formData.get('file') as File; - if (!(file instanceof File)) throw new Error('File not found'); - const imageKey = `${uuid}.${file.type.split('/')[1]}`; - const buffer = Buffer.from(await file.arrayBuffer()); - await saveFile(imageKey, buffer); - return { imageKey }; + .handler(async ({ data: formData, context: { user } }) => { + try { + let imageKey; + const file = formData.get('file') as File; + if (file) { + const uuid = crypto.randomUUID(); + if (!(file instanceof File)) throw new Error('File not found'); + const buffer = Buffer.from(await file.arrayBuffer()); + imageKey = await saveFile(`${uuid}.${file.type.split('/')[1]}`, buffer); + } + + const getOldUser = await prisma.user.findUnique({ + where: { id: user.id }, + }); + + const name = formData.get('name') as string; + + const newUser = JSON.parse(JSON.stringify({ name, image: imageKey })); + const keys = Object.keys(newUser); + const oldUser = Object.fromEntries( + Object.entries(getOldUser || {}).filter(([key]) => keys.includes(key)), + ); + + const headers = getRequestHeaders(); + const result = await auth.api.updateUser({ + body: newUser, + headers, + }); + + await createAuditLog({ + action: LOG_ACTION.UPDATE, + tableName: DB_TABLE.USER, + recordId: user.id, + oldValue: JSON.stringify(oldUser), + newValue: JSON.stringify(newUser), + userId: user.id, + }); + + return result; + } catch (error) { + console.error(error); + const { message, code } = parseError(error); + throw { message, code }; + } + }); + +export const changePassword = createServerFn({ method: 'POST' }) + .middleware([authMiddleware]) + .inputValidator(changePasswordBESchema) + .handler(async ({ data, context: { user } }) => { + try { + const headers = getRequestHeaders(); + const result = await auth.api.changePassword({ + body: { + newPassword: data.newPassword, // required + currentPassword: data.currentPassword, // required + revokeOtherSessions: true, + }, + headers, + }); + + await createAuditLog({ + action: LOG_ACTION.CHANGE_PASSWORD, + tableName: DB_TABLE.ACCOUNT, + recordId: user.id, + oldValue: 'Change Password', + newValue: 'Change Password', + userId: user.id, + }); + + return result; + } catch (error) { + // console.error(error); + const { message, code } = parseError(error); + throw { message, code }; + } }); diff --git a/src/service/repository.ts b/src/service/repository.ts index 65decbe..3f67e49 100644 --- a/src/service/repository.ts +++ b/src/service/repository.ts @@ -1,6 +1,5 @@ import { prisma } from '@/db'; import { Audit, Setting } from '@/generated/prisma/client'; -import { parseError } from '@utils/helper'; type AdminSettingValue = Pick; @@ -42,17 +41,11 @@ export async function getAllAdminSettings(valueOnly = false) { } export const createAuditLog = async (data: Omit) => { - try { - await prisma.audit.create({ - data: { - ...data, - }, - }); - } catch (error) { - console.error(error); - const { message, code } = parseError(error); - throw { message, code }; - } + await prisma.audit.create({ + data: { + ...data, + }, + }); }; export const getInitialOrganization = async (userId: string) => { @@ -65,5 +58,6 @@ export const getInitialOrganization = async (userId: string) => { }, }, }); + return organization; }; diff --git a/src/service/setting.api.ts b/src/service/setting.api.ts index 0d14ab4..8dba773 100644 --- a/src/service/setting.api.ts +++ b/src/service/setting.api.ts @@ -1,7 +1,8 @@ import { prisma } from '@/db'; +import { parseError } from '@/lib/errors'; import { authMiddleware } from '@lib/middleware'; import { createServerFn } from '@tanstack/react-start'; -import { extractDiffObjects, parseError } from '@utils/helper'; +import { extractDiffObjects } from '@utils/helper'; import { createAuditLog, getAllAdminSettings } from './repository'; import { settingSchema, userSettingSchema } from './setting.schema'; diff --git a/src/service/user.api.ts b/src/service/user.api.ts index 1b374ff..aabfb11 100644 --- a/src/service/user.api.ts +++ b/src/service/user.api.ts @@ -1,10 +1,10 @@ import { prisma } from '@/db'; import { DB_TABLE, LOG_ACTION } from '@/types/enum'; import { auth } from '@lib/auth'; +import { parseError } from '@lib/errors'; import { authMiddleware } from '@lib/middleware'; import { createServerFn } from '@tanstack/react-start'; import { getRequestHeaders } from '@tanstack/react-start/server'; -import { parseError } from '@utils/helper'; import { createAuditLog } from './repository'; import { baseUser, diff --git a/src/service/user.schema.ts b/src/service/user.schema.ts index c6480d3..71d7f49 100644 --- a/src/service/user.schema.ts +++ b/src/service/user.schema.ts @@ -5,7 +5,7 @@ export const baseUser = z.object({ id: z.string().nonempty(m.users_page_message_user_not_found()), }); -export const ChangePasswordFormSchema = z +export const changePasswordFormSchema = z .object({ currentPassword: z.string().nonempty( m.common_is_required({ @@ -33,7 +33,20 @@ export const ChangePasswordFormSchema = z } }); -export type ChangePassword = z.infer; +export const changePasswordBESchema = z.object({ + currentPassword: z.string().nonempty( + m.common_is_required({ + field: m.change_password_form_current_password(), + }), + ), + newPassword: z.string().nonempty( + m.common_is_required({ + field: m.change_password_form_new_password(), + }), + ), +}); + +export type ChangePassword = z.infer; export const userListSchema = z.object({ page: z.coerce.number().min(1).default(1), diff --git a/src/types/db.d.ts b/src/types/db.d.ts index eccffb8..0e5b9d0 100644 --- a/src/types/db.d.ts +++ b/src/types/db.d.ts @@ -12,7 +12,7 @@ declare global { }; }>; - type OrganizationWithMembers = Prisma.OrganizationGetPayload<{ + type HouseWithMembers = Prisma.OrganizationGetPayload<{ include: { members: { select: { @@ -30,8 +30,14 @@ declare global { }; }>; + type HouseWithMembersCount = HouseWithMembers & { + _count: { + members: number; + }; + }; + type ReturnError = Error & { - message: string; code: string; + message: string; }; } diff --git a/src/utils/helper.ts b/src/utils/helper.ts index f2659f8..5faffb3 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -26,22 +26,6 @@ export function extractDiffObjects( ); } -export function parseError(error: unknown) { - if (typeof error === 'object' && error !== null && 'body' in error) { - const e = error as any; - return { - message: e.body?.message ?? 'Unknown error', - code: e.body?.code, - }; - } - - if (error instanceof Error) { - return { message: error.message }; - } - - return { message: String(error) }; -} - export function slugify(text: string) { return text .toLowerCase()