Skip to content

Commit 472bcf9

Browse files
committed
feat: done setting account
1 parent 45cfa09 commit 472bcf9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1423
-34
lines changed

client/src/apis/upload.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { ENDPOINTS } from '@/constants';
2+
import http_client from '@/lib/http-client';
3+
import { TBaseResponse } from '@/types/share';
4+
5+
export class UploadApi {
6+
single(file: File): Promise<TBaseResponse<string>> {
7+
const formData = new FormData();
8+
9+
formData.append('file', file);
10+
11+
return http_client.post(ENDPOINTS.UPLOAD.SINGLE, formData, {
12+
headers: {
13+
'Content-Type': 'multipart/form-data',
14+
},
15+
});
16+
}
17+
}
18+
19+
export const uploadApi = new UploadApi();

client/src/apis/user.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ENDPOINTS } from '@/constants';
2+
import http_client from '@/lib/http-client';
3+
import { TUpdateInfor } from '@/lib/schema/update-infor';
4+
import { TBaseResponse } from '@/types/share';
5+
import { TUser } from '@/types/user';
6+
7+
class User {
8+
updateInfor(data: TUpdateInfor): Promise<TBaseResponse<TUser>> {
9+
return http_client.patch(ENDPOINTS.USER.UPDATE_INFO, data);
10+
}
11+
12+
changePass(data: any): Promise<TBaseResponse<null>> {
13+
return http_client.post(ENDPOINTS.USER.CHANGE_PASS, data);
14+
}
15+
}
16+
17+
export const userApi = new User();
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { useErrorsLngChange } from '@/hooks/use-errors-lng-change';
2+
import { TChangePass, useChangePasswordSchema } from '@/lib/schema/change-pass';
3+
import { zodResolver } from '@hookform/resolvers/zod';
4+
import { useForm } from 'react-hook-form';
5+
import { useTranslation } from 'react-i18next';
6+
import {
7+
Button,
8+
Form,
9+
FormControl,
10+
FormField,
11+
FormItem,
12+
FormLabel,
13+
FormMessage,
14+
Input,
15+
} from '../ui';
16+
17+
type Props = {
18+
onSubmit?: (data: TChangePass) => void;
19+
hasBtn?: boolean;
20+
loading?: boolean;
21+
};
22+
23+
export const ChangePassForm = ({ onSubmit, hasBtn, loading }: Props) => {
24+
const { t } = useTranslation(['forms', 'common']);
25+
26+
const schema = useChangePasswordSchema();
27+
28+
const form = useForm<TChangePass>({
29+
resolver: zodResolver(schema),
30+
mode: 'onChange',
31+
});
32+
33+
const handleSubmit = (data: TChangePass) => {
34+
onSubmit?.(data);
35+
36+
form.reset({
37+
oldPassword: '',
38+
password: '',
39+
passwordConfirm: '',
40+
});
41+
};
42+
43+
useErrorsLngChange(form);
44+
45+
return (
46+
<Form {...form}>
47+
<form
48+
className="space-y-3"
49+
onSubmit={form.handleSubmit(handleSubmit)}
50+
id="change-pass-form"
51+
>
52+
<FormField
53+
control={form.control}
54+
name="oldPassword"
55+
render={({ field }) => {
56+
return (
57+
<FormItem>
58+
<FormLabel required>{t('oldPassword.label')}</FormLabel>
59+
<FormControl>
60+
<Input
61+
{...field}
62+
placeholder={t('oldPassword.placeholder')}
63+
type="password"
64+
/>
65+
</FormControl>
66+
<FormMessage />
67+
</FormItem>
68+
);
69+
}}
70+
/>
71+
<FormField
72+
control={form.control}
73+
name="password"
74+
render={({ field }) => {
75+
return (
76+
<FormItem>
77+
<FormLabel required>{t('password.label')}</FormLabel>
78+
<FormControl>
79+
<Input
80+
{...field}
81+
placeholder={t('password.placeholder')}
82+
type="password"
83+
/>
84+
</FormControl>
85+
<FormMessage />
86+
</FormItem>
87+
);
88+
}}
89+
/>
90+
<FormField
91+
control={form.control}
92+
name="passwordConfirm"
93+
render={({ field }) => {
94+
return (
95+
<FormItem>
96+
<FormLabel required>
97+
{t('confirmPassword.label')}
98+
</FormLabel>
99+
<FormControl>
100+
<Input
101+
{...field}
102+
placeholder={t('confirmPassword.placeholder')}
103+
type="password"
104+
/>
105+
</FormControl>
106+
<FormMessage />
107+
</FormItem>
108+
);
109+
}}
110+
/>
111+
{hasBtn && <Button loading={loading}>{t('common:save')}</Button>}
112+
</form>
113+
</Form>
114+
);
115+
};
116+
117+
export default ChangePassForm;

client/src/components/forms/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './change-pass';
12
export * from './forgot-pass';
23
export * from './login';
34
export * from './register';
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { useErrorsLngChange } from '@/hooks/use-errors-lng-change';
2+
import { TSettingMail, useSettingMailSchema } from '@/lib/schema/setting-mail';
3+
import { zodResolver } from '@hookform/resolvers/zod';
4+
import { useForm } from 'react-hook-form';
5+
import { useTranslation } from 'react-i18next';
6+
import {
7+
Button,
8+
Form,
9+
FormControl,
10+
FormField,
11+
FormItem,
12+
FormLabel,
13+
FormMessage,
14+
Input,
15+
} from '../ui';
16+
17+
type Props = {
18+
loading?: boolean;
19+
onSubmit?: (data: TSettingMail) => void;
20+
hasBtn?: boolean;
21+
};
22+
23+
export const SettingMailForm = ({ loading, onSubmit, hasBtn }: Props) => {
24+
const { t } = useTranslation(['forms', 'common']);
25+
26+
const schema = useSettingMailSchema();
27+
28+
const form = useForm<TSettingMail>({
29+
resolver: zodResolver(schema),
30+
mode: 'onChange',
31+
});
32+
33+
const handleSubmit = (data: TSettingMail) => {
34+
onSubmit?.(data);
35+
};
36+
37+
useErrorsLngChange(form);
38+
39+
return (
40+
<Form {...form}>
41+
<form className="space-y-3" onSubmit={form.handleSubmit(handleSubmit)}>
42+
<FormField
43+
control={form.control}
44+
name="email"
45+
render={({ field }) => {
46+
return (
47+
<FormItem>
48+
<FormLabel required>{t('email.label')}</FormLabel>
49+
<FormControl>
50+
<Input
51+
{...field}
52+
placeholder={t('email.placeholder')}
53+
/>
54+
</FormControl>
55+
<FormMessage />
56+
</FormItem>
57+
);
58+
}}
59+
/>
60+
<FormField
61+
control={form.control}
62+
name="password"
63+
render={({ field }) => {
64+
return (
65+
<FormItem>
66+
<FormLabel required>{t('password.label')}</FormLabel>
67+
<FormControl>
68+
<Input
69+
{...field}
70+
placeholder={t('password.placeholder')}
71+
type="password"
72+
/>
73+
</FormControl>
74+
<FormMessage />
75+
</FormItem>
76+
);
77+
}}
78+
/>
79+
{hasBtn && (
80+
<Button className="w-full" type="submit" loading={loading}>
81+
{t('common:save')}
82+
</Button>
83+
)}
84+
</form>
85+
</Form>
86+
);
87+
};
88+
89+
export default SettingMailForm;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './layout';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Outlet } from '@tanstack/react-router';
2+
import Sidebar from './sidebar';
3+
export const Layout = () => {
4+
return (
5+
<div className="flex min-h-svh ">
6+
<Sidebar />
7+
<div className="ml-sidebar-setting w-full">
8+
<Outlet />
9+
</div>
10+
</div>
11+
);
12+
};
13+
14+
export default Layout;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import PageTitle from '@/components/page-title';
2+
import { Link } from '@tanstack/react-router';
3+
import { CircleUserRound, Mails } from 'lucide-react';
4+
import { useTranslation } from 'react-i18next';
5+
6+
const SIDEBAR_ITEMS = [
7+
{
8+
name: 'Profiles',
9+
to: '/settings/profiles',
10+
i18n: 'profiles',
11+
Icon: CircleUserRound,
12+
},
13+
{
14+
name: 'Mail',
15+
to: '/settings/mail',
16+
i18n: 'mail',
17+
Icon: Mails,
18+
},
19+
] as const;
20+
21+
const Sidebar = () => {
22+
const { t } = useTranslation('common');
23+
return (
24+
<div className="min-h-svh w-sidebar-setting flex-shrink-0 bg-primary-foreground fixed top-0 bottom-0 left-sidebar">
25+
<div className="h-[3.75rem] flex items-center px-4">
26+
<PageTitle>{t('settings')}</PageTitle>
27+
</div>
28+
<ul className="space-y-1">
29+
{SIDEBAR_ITEMS.map((item: any) => {
30+
const Icon = item.Icon;
31+
return (
32+
<li key={item.i18n}>
33+
<Link
34+
to={item.to}
35+
search
36+
className="flex items-center px-4 py-2 gap-2 transition-all"
37+
activeProps={{
38+
className: 'text-primary',
39+
}}
40+
>
41+
<Icon className="w-5 h-5" />
42+
<span>{t(item.i18n)}</span>
43+
</Link>
44+
</li>
45+
);
46+
})}
47+
</ul>
48+
</div>
49+
);
50+
};
51+
52+
export default Sidebar;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
type Props = {
4+
children: React.ReactNode;
5+
};
6+
7+
export const PageTitle = ({ children }: Props) => {
8+
return <h2 className="font-semibold text-xl">{children}</h2>;
9+
};
10+
11+
export default PageTitle;

0 commit comments

Comments
 (0)