Fix error handle
fix pagination issue change logic for change passsword and profile update
This commit is contained in:
@@ -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"
|
||||
>
|
||||
<CaretLeftIcon />
|
||||
@@ -85,6 +101,7 @@ const Pagination = ({
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
disabled={currentPage === totalPages}
|
||||
onClick={() => onPageChange(Number(currentPage + 1))}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<CaretRightIcon />
|
||||
|
||||
@@ -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<UserContext | null>(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(
|
||||
() => ({
|
||||
|
||||
@@ -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) => (
|
||||
<field.TextField
|
||||
label={m.change_password_form_current_password()}
|
||||
type="password"
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
@@ -91,7 +89,10 @@ const ChangePasswordForm = () => {
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton label={m.ui_change_password_btn()} />
|
||||
<form.SubscribeButton
|
||||
label={m.ui_change_password_btn()}
|
||||
disabled={isPending}
|
||||
/>
|
||||
</form.AppForm>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
@@ -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 <Skeleton className="h-100 col-span-1" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="@container/card col-span-1">
|
||||
@@ -136,7 +125,10 @@ const ProfileForm = () => {
|
||||
</Field>
|
||||
<Field>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton label={m.ui_update_btn()} />
|
||||
<form.SubscribeButton
|
||||
label={m.ui_update_btn()}
|
||||
disabled={isRunning}
|
||||
/>
|
||||
</form.AppForm>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -7,10 +7,12 @@ import DeleteUserHouseAction from './delete-user-house-dialog';
|
||||
import EditHouseAction from './edit-house-dialog';
|
||||
|
||||
type CurrentUserActionGroupProps = {
|
||||
oneHouse: boolean;
|
||||
activeHouse: ReturnType<typeof authClient.useActiveOrganization>['data'];
|
||||
};
|
||||
|
||||
const CurrentUserActionGroup = ({
|
||||
oneHouse,
|
||||
activeHouse,
|
||||
}: CurrentUserActionGroupProps) => {
|
||||
return (
|
||||
@@ -22,10 +24,7 @@ const CurrentUserActionGroup = ({
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-row gap-2">
|
||||
<EditHouseAction
|
||||
data={activeHouse as OrganizationWithMembers}
|
||||
isPersonal
|
||||
>
|
||||
<EditHouseAction data={activeHouse as HouseWithMembers} isPersonal>
|
||||
<Button
|
||||
type="button"
|
||||
size="icon-lg"
|
||||
@@ -35,7 +34,7 @@ const CurrentUserActionGroup = ({
|
||||
<span className="sr-only">{m.ui_edit_house_btn()}</span>
|
||||
</Button>
|
||||
</EditHouseAction>
|
||||
<DeleteUserHouseAction activeHouse={activeHouse} />
|
||||
{!oneHouse && <DeleteUserHouseAction activeHouse={activeHouse} />}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -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<typeof authClient.useActiveOrganization>['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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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<OrganizationWithMembers>[] = [
|
||||
export const houseColumns: ColumnDef<HouseWithMembers>[] = [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: m.houses_page_ui_table_header_name(),
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user