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()