This commit is contained in:
hamid zarghami
2025-05-03 11:58:06 +03:30
parent 9a2ea35918
commit 551dfa0a56
20 changed files with 124 additions and 66 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ import 'react-loading-skeleton/dist/skeleton.css'
import i18next from 'i18next'
import { I18nextProvider } from 'react-i18next'
import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ToastContainer } from 'react-toastify'
import ToastContainer from './components/Toast'
import FaJson from './langs/fa.json'
import { IApiErrorRepsonse } from './types/error.types'
+1 -1
View File
@@ -19,7 +19,7 @@ const Button: FC<Props> = memo((props: Props) => {
);
return (
<button {...props} className={`${buttonClass} ${props.className}`} >
<button {...props} className={`${buttonClass} ${props.className}`} disabled={props.disabled || props.isLoading}>
{
props.isLoading ?
<div className='flex gap-2 items-center'>
+58
View File
@@ -0,0 +1,58 @@
import { CloseCircle, InfoCircle, TickCircle } from 'iconsax-react';
import React, { useState, useEffect } from 'react';
interface Toast {
id: string;
message: string;
type?: 'success' | 'error' | 'info';
}
let addToast: (toast: Toast) => void;
const ToastContainer: React.FC = () => {
const [toasts, setToasts] = useState<Toast[]>([]);
// ثبت toast جدید
const showToast = (toast: Toast) => {
setToasts((prev) => [...prev, toast]);
// حذف خودکار بعد از ۳ ثانیه
setTimeout(() => {
setToasts((prev) => prev.filter((t) => t.id !== toast.id));
}, 3000);
};
// تخصیص تابع نمایش toast به متغیر سراسری
useEffect(() => {
addToast = showToast;
}, []);
return (
<div className="fixed top-4 text-sm right-0 left-0 mx-auto w-fit z-50 flex flex-col gap-2">
{toasts.map((toast) => (
<div
key={toast.id}
className={`px-4 flex items-center gap-2 backdrop-blur-2xl h-16 min-w-[300px] rounded-2xl shadow-md transition-transform ${toast.type === 'success'
? 'bg-white/70 text-black'
: toast.type === 'error'
? 'bg-white/70 text-black'
: 'bg-white/70 text-black'
}`}
>
{
toast.type === 'success' ? <TickCircle className='size-5' color='green' /> :
toast.type === 'error' ? <CloseCircle className='size-5' color='red' /> :
<InfoCircle className='size-5' color='blue' />
}
{toast.message}
</div>
))}
</div>
);
};
export const toast = (message?: string, type: 'success' | 'error' | 'info' = 'info') => {
addToast({ id: Date.now().toString(), message: message || '', type });
};
export default ToastContainer;
+4 -5
View File
@@ -11,10 +11,9 @@ import { Link } from 'react-router-dom'
import { Pages } from '../../../config/Pages'
import { isEmail } from '../../../config/func'
import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData'
import { toast } from 'react-toastify'
import { ErrorType } from '../../../helpers/types'
import { ArrowLeft } from 'iconsax-react'
import { toast } from '../../../components/Toast'
const LoginStep1: FC = () => {
const { t } = useTranslation('global')
@@ -38,7 +37,7 @@ const LoginStep1: FC = () => {
setStepLogin(2)
},
onError(error: ErrorType) {
toast.error(error?.response?.data?.error?.message[0])
toast(error.response?.data?.error.message?.[0], 'error')
},
})
} else {
@@ -46,10 +45,10 @@ const LoginStep1: FC = () => {
loginWithOtp.mutate({ phone: values.phone_email }, {
onSuccess() {
setStepLogin(2)
toast.success(t('auth.otp_sent'))
toast(t('auth.otp_sent'), 'success')
},
onError(error: ErrorType) {
toast.error(error?.response?.data?.error?.message[0])
toast(error?.response?.data?.error?.message[0], 'error')
},
})
}
+4 -4
View File
@@ -11,10 +11,10 @@ import { useCountDown } from '../../../hooks/useCountDown'
import { Pages } from '../../../config/Pages'
import { useLoginWithOtp, useOtpVerify } from '../hooks/useAuthData'
import { ErrorType } from '../../../helpers/types'
import { toast } from 'react-toastify'
import { setRefreshToken } from '../../../config/func'
import { setToken } from '../../../config/func'
import { getRedirectUrl } from '../../../config/func'
import { toast } from '../../../components/Toast'
const LoginStep2: FC = () => {
const { t } = useTranslation('global')
@@ -50,7 +50,7 @@ const LoginStep2: FC = () => {
window.location.href = Pages.dashboard
},
onError(error: ErrorType) {
toast.error(error?.response?.data?.error?.message[0])
toast(error?.response?.data?.error?.message[0], 'error')
},
})
},
@@ -81,10 +81,10 @@ const LoginStep2: FC = () => {
loginWithOtp.mutate({ phone: phone }, {
onSuccess: () => {
reset()
toast.success(t('auth.toast_resend_code'))
toast(t('auth.toast_resend_code'), 'success')
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error.message[0])
toast(error.response?.data?.error.message[0], 'error')
}
})
}
+2 -2
View File
@@ -11,7 +11,7 @@ import { Link } from 'react-router-dom'
import { Pages } from '../../../config/Pages'
import ArrowLeftIcon from '../../../assets/images/arrow-left.svg'
import { useLoginWithPassword } from '../hooks/useAuthData'
import { toast } from 'react-toastify'
import { toast } from '../../../components/Toast'
import { ErrorType } from '../../../helpers/types'
import { setRefreshToken } from '../../../config/func'
import { setToken } from '../../../config/func'
@@ -46,7 +46,7 @@ const LoginStep3: FC = () => {
window.location.href = Pages.dashboard
},
onError(error: ErrorType) {
toast.error(error?.response?.data?.error?.message[0])
toast(error?.response?.data?.error?.message[0], 'error')
},
})
+4 -4
View File
@@ -9,7 +9,7 @@ import { useMultiUpload } from '../ticket/hooks/useTicketData'
import { useFormik } from 'formik'
import { CreateCriticismsTypes } from './types/CriticismsTypes'
import * as Yup from 'yup'
import { toast } from 'react-toastify'
import { toast } from '../../components/Toast'
import { ErrorType } from '../../helpers/types'
import { useCreateCriticisms } from './hooks/useCriticismsData'
import { Helmet } from 'react-helmet-async'
@@ -43,7 +43,7 @@ const AddCriticisms: FC = () => {
values.files = await data.data.map((item: { url: string }) => item?.url)
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
@@ -53,14 +53,14 @@ const AddCriticisms: FC = () => {
createCriticisms.mutate(values, {
onSuccess: () => {
toast.success(t('success'))
toast(t('success'), 'success')
formik.resetForm()
setTimeout(() => {
window.location.reload()
}, 1000);
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
+2 -2
View File
@@ -11,7 +11,7 @@ import { timeAgo } from '../../config/func';
import InfiniteScroll from 'react-infinite-scroll-component';
import MoonLoader from "react-spinners/MoonLoader"
import Button from '../../components/Button';
import { toast } from 'react-toastify';
import { toast } from '../../components/Toast';
import { useGetDashboardSummary } from '../home/hooks/useHomeData';
import { useNavigate } from 'react-router-dom';
import { Pages } from '../../config/Pages';
@@ -32,7 +32,7 @@ const Notifications: FC = () => {
onSuccess: () => {
getDashboard.refetch()
refetch()
toast.success(t('success'))
toast(t('success'), 'success')
}
})
}
+7 -7
View File
@@ -8,7 +8,7 @@ import DatePickerComponent from '../../components/DatePicker'
import { useGetProfile, useUpdateProfile } from './hooks/useProfileData'
import Username from './components/Username'
import { useDropzone } from 'react-dropzone'
import { toast } from 'react-toastify'
import { toast } from '../../components/Toast'
import { useSingleUpload } from '../ticket/hooks/useTicketData'
import { ErrorType } from '../../helpers/types'
import { UpdateProfileType } from './types/ProfileTypes'
@@ -43,10 +43,10 @@ const Profile: FC = () => {
onSubmit: (values) => {
updateProfile.mutate(values, {
onSuccess: () => {
toast.success(t('success'))
toast(t('success'), 'success')
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
@@ -82,19 +82,19 @@ const Profile: FC = () => {
}
updateProfile.mutate(params, {
onSuccess: () => {
toast.success(t('profile.image_uploaded_successfully'))
toast(t('profile.image_uploaded_successfully'), 'success')
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
} else {
toast.error(t('errors.is_not_image'))
toast(t('errors.is_not_image'), 'error')
}
}
+3 -3
View File
@@ -8,7 +8,7 @@ import DefaulModal from '../../../components/DefaulModal'
import OTPInput from 'react-otp-input'
import { useUpdateEmail } from '../hooks/useProfileData'
import { ErrorType } from '../../../helpers/types'
import { toast } from 'react-toastify'
import { toast } from '../../../components/Toast'
type Props = {
email: string | null,
@@ -27,10 +27,10 @@ const Email: FC<Props> = (props: Props) => {
const handleShowModal = () => {
updateEmail.mutate({ email: email }, {
onSuccess: () => {
toast.success(t('profile.link_email'))
toast(t('profile.link_email'), 'success')
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
+4 -4
View File
@@ -7,7 +7,7 @@ import DefaulModal from '../../../components/DefaulModal'
import OTPInput from 'react-otp-input'
import { useUpdatePhone, useVerifyPhone } from '../hooks/useProfileData'
import { ErrorType } from '../../../helpers/types'
import { toast } from 'react-toastify'
import { toast } from '../../../components/Toast'
import { VerifyPhoneType } from '../types/ProfileTypes'
type Props = {
@@ -30,7 +30,7 @@ const Phone: FC<Props> = (props: Props) => {
setShowModal(true)
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
@@ -45,10 +45,10 @@ const Phone: FC<Props> = (props: Props) => {
onSuccess: () => {
setShowModal(false)
setIsReadOnly(true)
toast.success(t('success'))
toast(t('success'), 'success')
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
+3 -3
View File
@@ -5,7 +5,7 @@ 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 { toast } from '../../../components/Toast'
import { ErrorType } from '../../../helpers/types'
type Props = {
@@ -54,13 +54,13 @@ const Username: FC<Props> = (props: Props) => {
updateProfile.mutate(params, {
onSuccess: () => {
toast.success(t('profile.username_save_success'))
toast(t('profile.username_save_success'), 'success')
setCanEdit(false)
setCanSave(false)
setStatus('')
},
onError(error: ErrorType) {
toast.error(error.response?.data?.error?.message?.[0])
toast(error.response?.data?.error?.message[0], 'error')
},
})
}
+3 -3
View File
@@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next'
import { useFormik } from 'formik'
import * as Yup from 'yup'
import { BuyServiceType } from './types/ServiecTypes'
import { toast } from 'react-toastify'
import { toast } from '../../components/Toast'
import { ErrorType } from '../../helpers/types'
import { Pages } from '../../config/Pages'
@@ -40,11 +40,11 @@ const BuyService: FC = () => {
onSubmit: (values) => {
buyService.mutate(values, {
onSuccess: (data) => {
toast.success(t('success'))
toast(t('success'), 'success')
navigate(Pages.receipts.detail + data?.data?.invoice?.id)
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
@@ -10,7 +10,7 @@ import * as Yup from 'yup'
import { useParams } from 'react-router-dom'
import { useCreateReview } from '../hooks/useServiceData'
import { ErrorType } from '../../../helpers/types'
import { toast } from 'react-toastify'
import { toast } from '../../../components/Toast'
import Error from '../../../components/Error'
type Props = {
@@ -41,9 +41,10 @@ const CreateReview: FC<Props> = ({ refetch }) => {
onSuccess: () => {
formik.resetForm()
refetch()
toast(t('success'), 'success')
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
},
@@ -5,7 +5,7 @@ import Button from '../../../components/Button'
import { useFormik } from 'formik'
import { ChangePasswordType } from '../types/SettingTypes'
import * as Yup from 'yup'
import { toast } from 'react-toastify'
import { toast } from '../../../components/Toast'
import { useChangePassword } from '../hooks/useSettingData'
import { ErrorType } from '../../../helpers/types'
import { TickCircle } from 'iconsax-react'
@@ -31,15 +31,15 @@ const ChangePassword: FC = () => {
}),
onSubmit: (values) => {
if (values.newPassword !== values.repeatPassword) {
toast.error(t('errors.password_not_match'))
toast(t('errors.password_not_match'), 'error')
} else {
changePassword.mutate(values, {
onSuccess: () => {
toast.success(t('setting.password_changed'))
toast(t('setting.password_changed'), 'success')
formik.resetForm()
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
+4 -4
View File
@@ -12,7 +12,7 @@ import PageLoading from '../../components/PageLoading'
import { useFormik } from 'formik'
import { CreateTicketType } from './types/TicketTypes'
import * as Yup from 'yup'
import { toast } from 'react-toastify'
import { toast } from '../../components/Toast'
import { ErrorType } from '../../helpers/types'
import { useNavigate } from 'react-router-dom'
import { Pages } from '../../config/Pages'
@@ -59,7 +59,7 @@ const CreateTicket: FC = () => {
values.attachmentUrls = await data.data.map((item: { url: string }) => item?.url)
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
} else {
@@ -73,12 +73,12 @@ const CreateTicket: FC = () => {
createTicket.mutate(values, {
onSuccess: (data) => {
toast.success(t('success'))
toast(t('success'), 'success')
formik.resetForm()
navigate(Pages.ticket.detail + data.data?.ticketId)
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
+6 -6
View File
@@ -12,7 +12,7 @@ import moment from 'moment-jalaali'
import { useFormik } from 'formik'
import { AddMessageTicketType } from './types/TicketTypes'
import * as Yup from 'yup'
import { toast } from 'react-toastify'
import { toast } from '../../components/Toast'
import { ErrorType } from '../../helpers/types'
import { clx } from '../../helpers/utils'
import { Pages } from '../../config/Pages'
@@ -50,7 +50,7 @@ const TicketDetail: FC = () => {
values.attachmentUrls = await data.data.map((item: { url: string }) => item?.url)
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
} else {
@@ -59,7 +59,7 @@ const TicketDetail: FC = () => {
addMessage.mutate(values, {
onSuccess: () => {
toast.success(t('success'))
toast(t('success'), 'success')
formik.resetForm()
setFiles([])
setIsResetFiles(true)
@@ -68,7 +68,7 @@ const TicketDetail: FC = () => {
}, 1000);
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
},
@@ -77,11 +77,11 @@ const TicketDetail: FC = () => {
const hadleCloseTicket = () => {
closeTicket.mutate(id ? id : '', {
onSuccess: () => {
toast.success(t('success'))
toast(t('success'), 'success')
navigate(Pages.ticket.list)
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
+3 -3
View File
@@ -10,7 +10,7 @@ import { useFormik } from 'formik'
import { DepositTransferType } from '../types/WalletTypes'
import * as Yup from 'yup'
import { useSingleUpload } from '../../ticket/hooks/useTicketData'
import { toast } from 'react-toastify'
import { toast } from '../../../components/Toast'
import { ErrorType } from '../../../helpers/types'
import { TickCircle } from 'iconsax-react'
@@ -44,10 +44,10 @@ const CardtoCard: FC = () => {
onSuccess: () => {
formik.resetForm()
setFile(undefined)
toast.success(t('wallet.successful_transfer'))
toast(t('wallet.successful_transfer'), 'success')
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
+5 -5
View File
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'
import { clx } from '../../../helpers/utils'
import Input from '../../../components/Input'
import { useGetGetWays, usePayment } from '../hooks/useWalletData'
import { toast } from 'react-toastify'
import { toast } from '../../../components/Toast'
import { ErrorType } from '../../../helpers/types'
import { GatewayItemType } from '../types/WalletTypes'
import { TickCircle } from 'iconsax-react'
@@ -22,20 +22,20 @@ const Online: FC = () => {
const handlePay = () => {
if (desiredAmount === 0 && amountSelected === 0) {
toast.error(t('wallet.error_select_price'))
toast(t('wallet.error_select_price'), 'error')
} else {
const amout = desiredAmount === 0 ? amountSelected : desiredAmount
if (amout < 500000) {
toast.error(t('wallet.error_min_price'))
toast(t('wallet.error_min_price'), 'error')
return
} else {
payment.mutate({ amount: amout, gatewayId: bankSelected }, {
onSuccess: (data) => {
toast.success(t('wallet.transfer_to_getway'))
toast(t('wallet.transfer_to_getway'), 'success')
window.location.href = data?.data?.redirectUrl
},
onError: (error: ErrorType) => {
toast.error(error?.response?.data?.error?.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
+3 -3
View File
@@ -10,7 +10,7 @@ import { useFormik } from 'formik'
import { DepositTransferType } from '../types/WalletTypes'
import * as Yup from 'yup'
import { useSingleUpload } from '../../ticket/hooks/useTicketData'
import { toast } from 'react-toastify'
import { toast } from '../../../components/Toast'
import { ErrorType } from '../../../helpers/types'
import { TickCircle } from 'iconsax-react'
@@ -44,11 +44,11 @@ const CardtoCard: FC = () => {
onSuccess: () => {
formik.resetForm()
setFile(undefined)
toast.success(t('wallet.successful_transfer'))
toast(t('wallet.successful_transfer'), 'success')
window.location.reload()
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error.message[0])
toast(error.response?.data?.error?.message[0], 'error')
}
})
}