From 49e09fa9ab1376e7a5a968372c92bc7b2f97dce6 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sun, 26 Jan 2025 09:07:21 +0330 Subject: [PATCH] dsc --- src/components/Select.tsx | 5 +- src/index.css | 51 ++++++++ src/langs/fa.json | 8 +- src/pages/profile/Profile.tsx | 23 ++-- src/pages/profile/components/Username.tsx | 132 ++++++++++++++++++++ src/pages/profile/hooks/useProfileData.tsx | 22 ++++ src/pages/profile/service/ProfileService.ts | 17 +++ src/pages/profile/types/ProfileTypes.ts | 11 ++ src/shared/Header.tsx | 27 ++-- 9 files changed, 268 insertions(+), 28 deletions(-) create mode 100644 src/pages/profile/components/Username.tsx create mode 100644 src/pages/profile/hooks/useProfileData.tsx create mode 100644 src/pages/profile/service/ProfileService.ts create mode 100644 src/pages/profile/types/ProfileTypes.ts diff --git a/src/components/Select.tsx b/src/components/Select.tsx index 65234a6..a907ec3 100644 --- a/src/components/Select.tsx +++ b/src/components/Select.tsx @@ -43,7 +43,10 @@ const Select: FC = (props: Props) => { } - + { props.error_text && props.error_text !== '' ?
diff --git a/src/index.css b/src/index.css index 1fd5cdc..e71247d 100644 --- a/src/index.css +++ b/src/index.css @@ -131,3 +131,54 @@ tbody tr { direction: rtl; font-family: "irancell" !important; } + +/* loader */ +.loader { + width: 22px; + height: 22px; + border-radius: 50%; + position: relative; + animation: rotate 1s linear infinite; +} +.loader::before, +.loader::after { + content: ""; + box-sizing: border-box; + position: absolute; + inset: 0px; + border-radius: 50%; + border: 2px solid #0047ff; + animation: prixClipFix 2s linear infinite; +} +.loader::after { + border-color: #000; + animation: prixClipFix 2s linear infinite, rotate 0.5s linear infinite reverse; + inset: 6px; +} + +@keyframes rotate { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +@keyframes prixClipFix { + 0% { + clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0); + } + 25% { + clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0); + } + 50% { + clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%); + } + 75% { + clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%); + } + 100% { + clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0); + } +} diff --git a/src/langs/fa.json b/src/langs/fa.json index 2e7c092..cf858d3 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -282,7 +282,11 @@ "address_live": "آدرس محل سکونت خود را با دقت وارد کنید", "province": "استان", "city": "شهر", - "postal_code": "کد پستی" + "postal_code": "کد پستی", + "username_available": "نام کاربری موجود", + "username_not_available": "نام کاربری موجود نیست", + "username_save_success": "نام کاربری با موفقیت ذخیره شد" }, - "email": "ایمیل" + "email": "ایمیل", + "save": "ذخیره" } diff --git a/src/pages/profile/Profile.tsx b/src/pages/profile/Profile.tsx index fbf9f08..e1cda59 100644 --- a/src/pages/profile/Profile.tsx +++ b/src/pages/profile/Profile.tsx @@ -7,10 +7,13 @@ import Input from '../../components/Input' import DatePickerComponent from '../../components/DatePicker' import Select from '../../components/Select' import Textarea from '../../components/Textarea' +import { useGetProfile } from './hooks/useProfileData' +import Username from './components/Username' const Profile: FC = () => { const { t } = useTranslation('global') + const getProfile = useGetProfile() return (
@@ -56,23 +59,13 @@ const Profile: FC = () => {
-
- -
- -
- {t('edit')} -
-
-
+
@@ -85,7 +78,7 @@ const Profile: FC = () => {
diff --git a/src/pages/profile/components/Username.tsx b/src/pages/profile/components/Username.tsx new file mode 100644 index 0000000..23d8f5f --- /dev/null +++ b/src/pages/profile/components/Username.tsx @@ -0,0 +1,132 @@ +import { FC, useEffect, useState } from 'react' +import Input from '../../../components/Input' +import { Edit } from 'iconsax-react' +import { useTranslation } from 'react-i18next' +import Button from '../../../components/Button' +import { useCheckUserName, useUpdateProfile } from '../hooks/useProfileData' +import { CheckUserNameType, UpdateProfileType } from '../types/ProfileTypes' +import { toast } from 'react-toastify' +import { ErrorType } from '../../../helpers/types' + +type Props = { + username: string | null, +} +const Username: FC = (props: Props) => { + + const { t } = useTranslation('global') + const [username, setUsername] = useState(props.username ? props.username : '') + const [canSave, setCanSave] = useState(false) + const [canEdit, setCanEdit] = useState(false) + const [status, setStatus] = useState<'success' | 'error' | ''>('') + const checkUsername = useCheckUserName() + const updateProfile = useUpdateProfile() + + const handleChange = (e: React.ChangeEvent) => { + const value = e.target.value + setCanSave(false) + if (value.indexOf(' ') === -1) { + if (value.length <= 3) { + setUsername(value) + return + } + setStatus('') + const params: CheckUserNameType = { + value: e.target.value, + type: 'userName' + } + setUsername(e.target.value) + checkUsername.mutate(params, { + onSuccess() { + setStatus('success') + setCanSave(true) + }, + onError() { + setStatus('error') + } + }) + } + } + + const handleSave = () => { + const params: UpdateProfileType = { + userName: username + } + + updateProfile.mutate(params, { + onSuccess: () => { + toast.success(t('profile.username_save_success')) + setCanEdit(false) + setCanSave(false) + setStatus('') + }, + onError(error: ErrorType) { + toast.error(error.response?.data?.error?.message?.[0]) + }, + }) + } + + useEffect(() => { + if (username !== props.username) { + setUsername(props.username ? props.username : '') + } + }, [props.username]) + + + return ( + <> +
+
+ + + { + checkUsername.isPending && +
+
+
+ } +
+
+ { + !canEdit ? +
setCanEdit(true)} className='flex gap-1 cursor-pointer relative -top-3'> + +
+ {t('edit')} +
+
+ : +
+
+ { + status === 'success' ? +
+ {t('profile.username_available')} +
+ : + status === 'error' ? +
+ {t('profile.username_not_available')} +
+ : null + } + + ) +} + +export default Username \ No newline at end of file diff --git a/src/pages/profile/hooks/useProfileData.tsx b/src/pages/profile/hooks/useProfileData.tsx new file mode 100644 index 0000000..346669b --- /dev/null +++ b/src/pages/profile/hooks/useProfileData.tsx @@ -0,0 +1,22 @@ +import { useMutation, useQuery } from '@tanstack/react-query'; +import * as api from '../service/ProfileService' +import { CheckUserNameType, UpdateProfileType } from '../types/ProfileTypes'; + +export const useGetProfile = () => { + return useQuery({ + queryKey: ["profile"], + queryFn: () => api.getProfile(), + }); +}; + +export const useCheckUserName = () => { + return useMutation({ + mutationFn: (variables: CheckUserNameType) => api.checkUserName(variables), + }); +}; + +export const useUpdateProfile = () => { + return useMutation({ + mutationFn: (variables: UpdateProfileType) => api.updateProfile(variables), + }); +}; \ No newline at end of file diff --git a/src/pages/profile/service/ProfileService.ts b/src/pages/profile/service/ProfileService.ts new file mode 100644 index 0000000..5a59db1 --- /dev/null +++ b/src/pages/profile/service/ProfileService.ts @@ -0,0 +1,17 @@ +import axios from "../../../config/axios"; +import { CheckUserNameType, UpdateProfileType } from "../types/ProfileTypes"; + +export const getProfile = async () => { + const { data } = await axios.get(`/users/me`); + return data; +}; + +export const checkUserName = async (params: CheckUserNameType) => { + const { data } = await axios.post(`/users/check-validity`, params); + return data; +}; + +export const updateProfile = async (params: UpdateProfileType) => { + const { data } = await axios.patch(`/users/update-profile`, params); + return data; +}; diff --git a/src/pages/profile/types/ProfileTypes.ts b/src/pages/profile/types/ProfileTypes.ts new file mode 100644 index 0000000..7b305f1 --- /dev/null +++ b/src/pages/profile/types/ProfileTypes.ts @@ -0,0 +1,11 @@ +export type CheckUserNameType = { + type: "userName"; + value: string; +}; + +export type UpdateProfileType = { + firstName?: string; + lastName?: string; + birthDate?: string; + userName?: string; +}; diff --git a/src/shared/Header.tsx b/src/shared/Header.tsx index 78848b1..a6cc720 100644 --- a/src/shared/Header.tsx +++ b/src/shared/Header.tsx @@ -7,11 +7,13 @@ import { Link } from 'react-router-dom' import { Pages } from '../config/Pages' import Notifications from '../pages/notification/Notification' import { useSharedStore } from './store/sharedStore' +import { useGetProfile } from '../pages/profile/hooks/useProfileData' const Header: FC = () => { const { t } = useTranslation('global') const { setOpenSidebar, openSidebar } = useSharedStore() + const { data } = useGetProfile() return (
@@ -32,17 +34,22 @@ const Header: FC = () => { -
-
- - - + { + data && +
+
+ + + +
+
+
+ {data?.data?.user?.firstName + ' ' + data?.data?.user?.lastName} +
+ +
-
-
مهرداد مظفری
- -
-
+ }
)