my ccompany + footer+ nofit + ...
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
VITE_TOKEN_NAME = 'dzone_token'
|
||||
VITE_REFRESH_TOKEN_NAME = 'dzone_refresh_token'
|
||||
# VITE_BASE_URL = 'https://dzone-api.danakcorp.com'
|
||||
VITE_BASE_URL = 'http://192.168.1.113:4001'
|
||||
VITE_BASE_URL = 'https://api-dzone.danakcorp.com'
|
||||
# VITE_BASE_URL = 'http://192.168.1.113:4001'
|
||||
|
||||
VITE_SLUG_ID = 'slug_id'
|
||||
+4
-8
@@ -6,15 +6,11 @@ RUN apk add --no-cache tzdata
|
||||
# Set the timezone to Asia/Tehran
|
||||
RUN cp /usr/share/zoneinfo/Asia/Tehran /etc/localtime && echo "Asia/Tehran" > /etc/timezone
|
||||
|
||||
# Install pnpm
|
||||
RUN npm install -g corepack@latest
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
|
||||
WORKDIR /build
|
||||
COPY package*.json pnpm-lock.yaml* ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY package*.json ./
|
||||
RUN npm ci --legacy-peer-deps
|
||||
COPY . ./
|
||||
RUN pnpm run build
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:stable-alpine AS production-stage
|
||||
|
||||
@@ -25,4 +21,4 @@ RUN cat /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
Generated
+43
@@ -21,10 +21,13 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-i18next": "^15.4.0",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-loading": "^2.0.3",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-multi-date-picker": "^4.5.2",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"react-router-dom": "^7.1.3",
|
||||
"react-spinners": "^0.17.0",
|
||||
"react-toastify": "^11.0.3",
|
||||
"swiper": "^11.2.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
@@ -4391,6 +4394,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-infinite-scroll-component": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz",
|
||||
"integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"throttle-debounce": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
@@ -4407,6 +4422,15 @@
|
||||
"react": ">=0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-loading-skeleton": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.5.0.tgz",
|
||||
"integrity": "sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-multi-date-picker": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-4.5.2.tgz",
|
||||
@@ -4479,6 +4503,16 @@
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-spinners": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.17.0.tgz",
|
||||
"integrity": "sha512-L/8HTylaBmIWwQzIjMq+0vyaRXuoAevzWoD35wKpNTxxtYXWZp+xtgkfD7Y4WItuX0YvdxMPU79+7VhhmbmuTQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-toastify": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.3.tgz",
|
||||
@@ -4964,6 +4998,15 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/throttle-debounce": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz",
|
||||
"integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-case": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
|
||||
|
||||
+4
-2
@@ -23,10 +23,13 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-i18next": "^15.4.0",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-loading": "^2.0.3",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-multi-date-picker": "^4.5.2",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"react-router-dom": "^7.1.3",
|
||||
"react-spinners": "^0.17.0",
|
||||
"react-toastify": "^11.0.3",
|
||||
"swiper": "^11.2.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
@@ -51,6 +54,5 @@
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"vite": "^6.0.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
-4124
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -81,7 +81,7 @@ function App() {
|
||||
// Clear tokens and redirect to login
|
||||
await removeToken();
|
||||
await removeRefreshToken();
|
||||
window.location.href = '/auth/login';
|
||||
// window.location.href = '/auth/login';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { clx } from '../helpers/utils'
|
||||
import Button from './Button'
|
||||
import book from '../assets/images/books.jpg'
|
||||
import { FC } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../config/Pages'
|
||||
type Props = {
|
||||
image: string;
|
||||
title: string;
|
||||
time: string;
|
||||
teacher: string;
|
||||
price: string;
|
||||
}
|
||||
|
||||
const CourseCard: FC<Props> = (props: Props) => {
|
||||
const cardClass = clx(
|
||||
'w-full bg-white rounded-lg overflow-hidden'
|
||||
);
|
||||
return (
|
||||
<div
|
||||
{...props} className={`${cardClass}`}
|
||||
>
|
||||
<img className='h-[92px] w-full' src={book} alt="" />
|
||||
<div className='mt-1 p-2 text-xs space-y-3'>
|
||||
<div className='flex justify-between items-center '>
|
||||
<p>{props.title}</p>
|
||||
<p>{props.price} تومان</p>
|
||||
</div>
|
||||
<div className='text-description font-extralight space-y-2'>
|
||||
<p>{props.time} ساعت آموزش</p>
|
||||
<p>مدرس : {props.teacher}</p>
|
||||
</div>
|
||||
<Link
|
||||
to={Pages.courses.detail + '123'}>
|
||||
<Button
|
||||
label='ثبت نام' className='mt-4 h-7 text-[10px] rounded-[8px]' />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CourseCard
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FC, InputHTMLAttributes, useState } from 'react'
|
||||
import { FC, InputHTMLAttributes, useEffect, useState } from 'react'
|
||||
import { clx } from '../helpers/utils';
|
||||
import EyeIcon from '../assets/images/eye.svg'
|
||||
import { SearchNormal } from 'iconsax-react';
|
||||
import Error from './Error';
|
||||
|
||||
type Variant = "floating_outlined" | "primary" | "search";
|
||||
|
||||
@@ -14,11 +15,23 @@ type Props = {
|
||||
unit?: string;
|
||||
seprator?: boolean;
|
||||
isNotRequired?: boolean;
|
||||
onChangeSearchFinal?: (value: string) => void;
|
||||
} & InputHTMLAttributes<HTMLInputElement>
|
||||
|
||||
const formatNumber = (value: string | number): string => {
|
||||
if (!value) return '';
|
||||
const inputValue = String(value).replace(/,/g, '');
|
||||
return inputValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
};
|
||||
|
||||
const Input: FC<Props> = (props: Props) => {
|
||||
|
||||
const [formattedValue, setFormattedValue] = useState<string>(
|
||||
props.value ? formatNumber(props.value as string) : ''
|
||||
);
|
||||
|
||||
const [showPassword, setShowPassword] = useState<boolean>(false)
|
||||
const [search, setSearch] = useState<string>('')
|
||||
|
||||
const inputClass = clx(
|
||||
'w-full h-10 text-black block px-4 text-xs rounded-xl border border-border',
|
||||
@@ -27,6 +40,44 @@ const Input: FC<Props> = (props: Props) => {
|
||||
props.className
|
||||
);
|
||||
|
||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (props.seprator) {
|
||||
const inputValue = event.target.value.replace(/,/g, ''); // حذف کاماها
|
||||
const formatted = formatNumber(inputValue);
|
||||
|
||||
// بهروزرسانی مقدار قالببندیشده
|
||||
setFormattedValue(formatted);
|
||||
|
||||
// ارسال مقدار خام به `onChange` والد
|
||||
props.onChange?.({
|
||||
...event,
|
||||
target: {
|
||||
...event.target,
|
||||
value: inputValue,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
props.onChange?.(event);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.value) {
|
||||
setFormattedValue(formatNumber(props.value as string));
|
||||
} else {
|
||||
setFormattedValue('');
|
||||
}
|
||||
}, [props.value])
|
||||
|
||||
useEffect(() => {
|
||||
if (props.variant === 'search' && props.onChangeSearchFinal) {
|
||||
const timeout = setTimeout(() => {
|
||||
props.onChangeSearchFinal?.(search)
|
||||
}, 1000)
|
||||
return () => clearTimeout(timeout)
|
||||
}
|
||||
}, [search, props])
|
||||
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
@@ -35,7 +86,10 @@ const Input: FC<Props> = (props: Props) => {
|
||||
</label>
|
||||
|
||||
<div className='w-full relative mt-1'>
|
||||
<input {...props} type={props.type === 'password' && showPassword ? 'text' : props.type === 'password' ? 'password' : undefined} className={inputClass} />
|
||||
<input {...props} onChange={(e) => {
|
||||
setSearch(e.target.value)
|
||||
handleInputChange(e)
|
||||
}} value={props.seprator ? formattedValue : props.value} type={props.type === 'password' && showPassword ? 'text' : props.type === 'password' ? 'password' : undefined} className={inputClass} />
|
||||
|
||||
{
|
||||
props.type === 'password' &&
|
||||
@@ -46,6 +100,13 @@ const Input: FC<Props> = (props: Props) => {
|
||||
props.variant === 'search' &&
|
||||
<SearchNormal size={20} color='#8C90A3' className='absolute top-0 w-5 bottom-0 my-auto right-3' />
|
||||
}
|
||||
|
||||
{
|
||||
props.error_text &&
|
||||
<Error
|
||||
errorText={props.error_text}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Link } from 'react-router-dom'
|
||||
|
||||
type Props = {
|
||||
title: string,
|
||||
link: string
|
||||
link?: string
|
||||
}
|
||||
|
||||
const TitleSeeAll: FC<Props> = (props: Props) => {
|
||||
@@ -17,15 +17,18 @@ const TitleSeeAll: FC<Props> = (props: Props) => {
|
||||
<div>
|
||||
{props.title}
|
||||
</div>
|
||||
<Link to={props.link} className='flex items-center gap-2'>
|
||||
<div className='text-sm'>
|
||||
{t('home.see_all')}
|
||||
</div>
|
||||
<ArrowLeft
|
||||
size={16}
|
||||
color='black'
|
||||
/>
|
||||
</Link>
|
||||
{
|
||||
props.link &&
|
||||
<Link to={props.link} className='flex items-center gap-2'>
|
||||
<div className='text-sm'>
|
||||
{t('home.see_all')}
|
||||
</div>
|
||||
<ArrowLeft
|
||||
size={16}
|
||||
color='black'
|
||||
/>
|
||||
</Link>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export const Pages = {
|
||||
},
|
||||
company: {
|
||||
detail: "company/",
|
||||
my: "company/my",
|
||||
},
|
||||
grade: {
|
||||
records: "grade-records",
|
||||
|
||||
+4
-2
@@ -8,7 +8,7 @@ import {
|
||||
removeRefreshToken,
|
||||
} from "./func";
|
||||
import { refreshToken } from "../pages/auth/service/AuthService";
|
||||
// import { Pages } from "./Pages";
|
||||
import { buildPath } from "../helpers/utils";
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_BASE_URL,
|
||||
@@ -55,7 +55,9 @@ axiosInstance.interceptors.response.use(
|
||||
console.error("Token refresh failed:", refreshError);
|
||||
await removeToken();
|
||||
await removeRefreshToken();
|
||||
window.location.href = "/auth/login";
|
||||
const slug = localStorage.getItem(import.meta.env.VITE_SLUG_ID);
|
||||
const slug_parse = slug ? JSON.parse(slug) : null;
|
||||
window.location.href = buildPath("auth/login", slug_parse?.slug);
|
||||
return Promise.reject(refreshError);
|
||||
} finally {
|
||||
window.isRefreshingToken = false;
|
||||
|
||||
+25
-2
@@ -141,7 +141,8 @@
|
||||
"other_courses": "سایر دوره ها",
|
||||
"my_services": "سرویس های من",
|
||||
"announcements": "اطلاعیه ها",
|
||||
"tickets": "تیکت ها"
|
||||
"tickets": "تیکت ها",
|
||||
"invoice": "صورتحساب ها"
|
||||
},
|
||||
"ticket": {
|
||||
"tickets": "تیکت ها",
|
||||
@@ -354,7 +355,8 @@
|
||||
"natification": "اعلان ها",
|
||||
"add_notification": "درج اطلاعیه",
|
||||
"title": "عنوان",
|
||||
"group": "گروه"
|
||||
"group": "گروه",
|
||||
"all_read": "همه خوانده شده"
|
||||
},
|
||||
"profile": {
|
||||
"account_user": "حساب کاربری",
|
||||
@@ -418,5 +420,26 @@
|
||||
"select_company": "انتخاب شرکت",
|
||||
"register_date": "تاریخ ثبت نام",
|
||||
"company": "شرکت"
|
||||
},
|
||||
"save": "ذخیره",
|
||||
"confrim": {
|
||||
"subject": "پرداخت",
|
||||
"content": "برای حذف این آیتم مطمئن هستید؟",
|
||||
"yes": "بله",
|
||||
"cancel": "لغو"
|
||||
},
|
||||
"callback": {
|
||||
"title_success": "پرداخت موفق بود!",
|
||||
"desc_success": "پرداخت شما با موفقیت انجام شد. از خرید شما متشکریم!",
|
||||
"order_number": "کد رهگیری",
|
||||
"amount": "مبلغ پرداختی",
|
||||
"date": "تاریخ",
|
||||
"back": "تراکنش ها",
|
||||
"failed": "پرداخت ناموفق!",
|
||||
"desc_failed": "متأسفیم! پرداخت شما انجام نشد. لطفاً دوباره تلاش کنید یا روش پرداخت دیگری انتخاب کنید.",
|
||||
"pending": "وضعیت پرداخت نامشخص است!",
|
||||
"desc_pending": "اگر مبلغی از حساب شما کسر شده ولی سفارش شما بعد از ۷۲ ساعت تأیید نشد، لطفاً با پشتیبانی تماس بگیرید.",
|
||||
"home": "صفحه اصلی",
|
||||
"invoice": " صورتحساب ها"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -5,7 +5,6 @@ import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { ArrowLeft2 } from 'iconsax-react'
|
||||
import { useGetAnnoncementDetail } from './hooks/useAnnoncementData'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
|
||||
const AnnouncementDetail: FC = () => {
|
||||
|
||||
@@ -16,11 +15,6 @@ const AnnouncementDetail: FC = () => {
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<Helmet>
|
||||
<title>
|
||||
داناک | اطلاعیه
|
||||
</title>
|
||||
</Helmet>
|
||||
<div className='flex justify-between'>
|
||||
<div>
|
||||
{t('announcement.text_announcement')}
|
||||
|
||||
@@ -2,17 +2,17 @@ import { FC, Fragment, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '../../components/Input'
|
||||
import StatusWithText from '../../components/StatusWithText'
|
||||
import { clx } from '../../helpers/utils'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { clx, buildPath } from '../../helpers/utils'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { Pages } from '../../config/Pages'
|
||||
import { useGetAnnoncement } from './hooks/useAnnoncementData'
|
||||
import { AnnoncementItemType } from './types/AnnoncementTypes'
|
||||
import moment from 'moment-jalaali'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
import AnouncementSkeleton from './components/AnouncementSkeleton'
|
||||
|
||||
const AnnouncementtList: FC = () => {
|
||||
|
||||
const { slug } = useParams()
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation('global')
|
||||
const [search, setSearch] = useState<string>('')
|
||||
@@ -21,7 +21,7 @@ const AnnouncementtList: FC = () => {
|
||||
|
||||
const handleChange = (item: AnnoncementItemType) => {
|
||||
if (window.innerWidth < 1280) {
|
||||
navigate(Pages.announcement.detail + item.announcement.id)
|
||||
navigate(buildPath(Pages.announcement.detail + item.announcement.id, slug))
|
||||
} else {
|
||||
setItemSelected(item)
|
||||
}
|
||||
@@ -29,11 +29,6 @@ const AnnouncementtList: FC = () => {
|
||||
|
||||
return (
|
||||
<div className='mt-4 text-sm'>
|
||||
<Helmet>
|
||||
<title>
|
||||
داناک | اطلاعیه ها
|
||||
</title>
|
||||
</Helmet>
|
||||
<div className='text-base'>
|
||||
{t('announcement.announcements')}
|
||||
</div>
|
||||
|
||||
@@ -8,12 +8,11 @@ import { useAuthStore } from '../store/AuthStore'
|
||||
import Error from '../../../components/Error'
|
||||
import Button from '../../../components/Button'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../../../config/Pages'
|
||||
import { isEmail } from '../../../config/func'
|
||||
import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { ArrowLeft } from 'iconsax-react'
|
||||
import { toast } from '../../../components/Toast'
|
||||
import { buildPath } from '../../../helpers/utils'
|
||||
const LoginStep1: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
@@ -98,17 +97,10 @@ const LoginStep1: FC = () => {
|
||||
<p className='text-description'>
|
||||
{t('auth.have_account')}
|
||||
</p>
|
||||
<Link to={Pages.auth.register}>
|
||||
<Link to={buildPath('auth/register')}>
|
||||
<div>{t('auth.register')}</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<a href='https://accounts.danakcorp.com' target='_blank' className='mt-4 flex gap-1 text-sm justify-center text-description'>
|
||||
<div>
|
||||
{t('auth.old_version')}
|
||||
</div>
|
||||
<ArrowLeft size={20} color='black' />
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import * as Yup from 'yup'
|
||||
import { useAuthStore } from '../store/AuthStore'
|
||||
import Error from '../../../components/Error'
|
||||
import Button from '../../../components/Button'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../../../config/Pages'
|
||||
import ArrowLeftIcon from '../../../assets/images/arrow-left.svg'
|
||||
import { useLoginWithPassword } from '../hooks/useAuthData'
|
||||
@@ -83,11 +82,6 @@ const LoginStep3: FC = () => {
|
||||
}
|
||||
|
||||
</div>
|
||||
<Link to={Pages.auth.forgotPassword}>
|
||||
<div className='mt-4 flex justify-end text-sm'>
|
||||
{t('auth.forgot_password')}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
label={t('auth.login')}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import { Add, Calendar, CallCalling, Location, Map1, MessageMinus, Profile, Setting2, Sms } from 'iconsax-react'
|
||||
import { FC } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useGetMyCompany } from '../home/hooks/useHomeData'
|
||||
import moment from 'moment-jalaali'
|
||||
import Button from '../../components/Button'
|
||||
import { buildPath } from '../../helpers/utils'
|
||||
import { Pages } from '../../config/Pages'
|
||||
import MyProduct from './components/MyProduct'
|
||||
const CompanyDetail: FC = () => {
|
||||
|
||||
const { data } = useGetMyCompany()
|
||||
|
||||
if (data?.data?.company) {
|
||||
return (
|
||||
<div className='mt-4 pb-20'>
|
||||
<img
|
||||
src={data?.data?.company?.coverImageUrl}
|
||||
className='w-full h-[220px] rounded-xl'
|
||||
/>
|
||||
<div className='p-6 bg-white rounded-xl -mt-4 relative'>
|
||||
<div className='flex gap-3'>
|
||||
<img
|
||||
src={data?.data?.company?.profileImageUrl}
|
||||
className='size-16 rounded-xl object-cover'
|
||||
/>
|
||||
<div className='flex-1 items-center flex justify-between '>
|
||||
<div className='text-sm'>
|
||||
{data?.data?.company?.name}
|
||||
</div>
|
||||
<div className='flex items-center gap-1.5 text-xs'>
|
||||
<Map1
|
||||
size={20}
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
آدرس در نقشه
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-5 text-[10px]'>
|
||||
<div className='flex justify-between'>
|
||||
<div className='flex gap-1 items-center'>
|
||||
<Profile
|
||||
size={18}
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
مدیرعامل: {data?.data?.company?.user?.fullName}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-1 items-center'>
|
||||
<Setting2
|
||||
size={18}
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
صنعت : {data?.data?.company?.industry?.title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex justify-between mt-4'>
|
||||
<div className='flex gap-1 items-center'>
|
||||
<CallCalling
|
||||
size={18}
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
شماره تماس : {data?.data?.company?.user?.phone}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-1 items-center'>
|
||||
<MessageMinus
|
||||
size={18}
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
شناسه ملی : {data?.data?.company?.identificationNumber}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex justify-between mt-4'>
|
||||
<div className='flex gap-1 items-center'>
|
||||
<Sms
|
||||
size={18}
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
ایمیل : {data?.data?.company?.user?.email}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-1 items-center'>
|
||||
<Calendar
|
||||
size={18}
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
تاریخ تاسیس : {moment(data?.data?.company?.dateOfEstablishment, 'jYYYY/jMM/jDD').format('jYYYY/jMM/jDD')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2 mt-4'>
|
||||
<Location
|
||||
size={18}
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
آدرس : {data?.data?.company?.address}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MyProduct />
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className='flex flex-col justify-center items-center h-full gap-4 py-8'>
|
||||
<div className='text-gray-600 text-center'>
|
||||
<p className='text-lg font-medium mb-2'>شما هنوز شرکتی ثبت نکردید</p>
|
||||
<p className='text-sm text-gray-500'>برای ثبت شرکت خود، روی دکمه زیر کلیک کنید</p>
|
||||
</div>
|
||||
<Link to={buildPath(Pages.request)}>
|
||||
<Button
|
||||
className='w-fit px-5'
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Add size={20} color='white' />
|
||||
<span>ثبت درخواست شرکت</span>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default CompanyDetail
|
||||
@@ -0,0 +1,68 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useGetMyCompany } from '../../home/hooks/useHomeData'
|
||||
import { ProductItemType } from '../types/CompanyTypes'
|
||||
const MyProduct: FC = () => {
|
||||
|
||||
const { data } = useGetMyCompany()
|
||||
const [activeTab, setActiveTab] = useState<'products' | 'services' | 'gallery'>('products')
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mt-6 flex justify-center gap-2 items-center'>
|
||||
<div className={`flex cursor-pointer h-8 bg-white px-6 items-center rounded-full text-xs border-2 border-white pt-[1px] ${activeTab === 'products' ? 'bg-opacity-100' : 'bg-opacity-45'}`} onClick={() => setActiveTab('products')}>
|
||||
محصولات
|
||||
</div>
|
||||
<div className={`flex cursor-pointer h-8 bg-white px-6 items-center rounded-full text-xs border-2 border-white pt-[1px] ${activeTab === 'services' ? 'bg-opacity-100' : 'bg-opacity-45'}`} onClick={() => setActiveTab('services')}>
|
||||
خدمات
|
||||
</div>
|
||||
{/* <div className={`flex cursor-pointer h-8 bg-white px-6 items-center rounded-full text-xs border-2 border-white pt-[1px] ${activeTab === 'gallery' ? 'bg-opacity-100' : 'bg-opacity-45'}`} onClick={() => setActiveTab('gallery')}>
|
||||
گالری
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
{
|
||||
activeTab === 'products' && (
|
||||
<div className='flex gap-2 flex-wrap mt-6'>
|
||||
{
|
||||
data?.data?.company?.products?.map((item: ProductItemType) => (
|
||||
<div className='flex-1 gap-2.5 bg-white min-w-[28%] h-20 flex items-center p-2 rounded-xl'>
|
||||
<div className='size-12 rounded-xl overflow-hidden'>
|
||||
<img src={item.imageUrl} alt={item.title} className='w-full h-full object-cover' />
|
||||
</div>
|
||||
<div className='text-xs'>
|
||||
<div>
|
||||
{item.title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
activeTab === 'services' && (
|
||||
<div className='flex gap-2 flex-wrap mt-6'>
|
||||
{
|
||||
data?.data?.company?.services?.map((item: ProductItemType) => (
|
||||
<div className='flex-1 gap-2.5 bg-white min-w-[28%] h-20 flex items-center p-2 rounded-xl'>
|
||||
<div className='size-12 rounded-xl overflow-hidden'>
|
||||
<img src={item.imageUrl} alt={item.title} className='w-full h-full object-cover' />
|
||||
</div>
|
||||
<div className='text-xs'>
|
||||
<div>
|
||||
{item.title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MyProduct
|
||||
+163
-133
@@ -1,134 +1,164 @@
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '../../components/Input'
|
||||
import Textarea from '../../components/Textarea'
|
||||
import UploadBox from '../../components/UploadBox'
|
||||
import Button from '../../components/Button'
|
||||
import Select from '../../components/Select'
|
||||
import { TickSquare } from 'iconsax-react'
|
||||
|
||||
const AddCriticisms: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
|
||||
return (
|
||||
|
||||
<div className='mt-4 text-sm'>
|
||||
<div>
|
||||
{t('notif.add_notification')}
|
||||
</div>
|
||||
|
||||
<div className='flex gap-6 xl:mt-8 mt-5'>
|
||||
<div className='flex-1 bg-white py-8 xl:px-10 px-5 rounded-3xl'>
|
||||
<div>
|
||||
{t('criticisms.submit_your_criticisms')}
|
||||
</div>
|
||||
|
||||
<div className='mt-3 text-xs text-description'>
|
||||
{t('criticisms.description_criticisms')}
|
||||
</div>
|
||||
|
||||
<div className='mt-10'>
|
||||
<Input
|
||||
label={t('notif.title')}
|
||||
placeholder={t('criticisms.enter_your_subject')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className='mt-7'>
|
||||
<Select
|
||||
items={[
|
||||
{ value: '1', label: 'گروه' },
|
||||
{ value: '2', label: 'فعال' },
|
||||
{ value: '3', label: 'غیر فعال' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className='mt-7'>
|
||||
<Select
|
||||
items={[
|
||||
{ value: '1', label: 'انتخاب زمان انتشار' },
|
||||
{ value: '2', label: 'فعال' },
|
||||
{ value: '3', label: 'غیر فعال' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div className='mt-7'>
|
||||
<Textarea
|
||||
label={t('notif.group')}
|
||||
placeholder={t('criticisms.enter_description')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-7'>
|
||||
<UploadBox
|
||||
label={t('attachment')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='mt-20 flex justify-end'>
|
||||
<Button
|
||||
label={t('ticket.send')}
|
||||
className='max-w-[100px]'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div className='bg-white w-sidebar hidden xl:block py-10 px-5 h-fit rounded-3xl'>
|
||||
<div className='text-sm'>
|
||||
{t('ticket.title_hint')}
|
||||
</div>
|
||||
<div className='mt-6'>
|
||||
<div className='flex items-start gap-2 border-b pb-5'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-start gap-2 mt-6'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '../../components/Input'
|
||||
import Textarea from '../../components/Textarea'
|
||||
import UploadBox from '../../components/UploadBox'
|
||||
import Button from '../../components/Button'
|
||||
import { TickCircle, TickSquare } from 'iconsax-react'
|
||||
import { useMultiUpload } from '../ticket/hooks/useTicketData'
|
||||
import { useFormik } from 'formik'
|
||||
import { CreateCriticismsTypes } from './types/CriticismsTypes'
|
||||
import * as Yup from 'yup'
|
||||
import { toast } from '../../components/Toast'
|
||||
import { ErrorType } from '../../helpers/types'
|
||||
import { useCreateCriticisms } from './hooks/useCriticismsData'
|
||||
|
||||
const AddCriticisms: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [files, setFiles] = useState<File[]>([])
|
||||
const multiUpload = useMultiUpload()
|
||||
const createCriticisms = useCreateCriticisms()
|
||||
|
||||
const formik = useFormik<CreateCriticismsTypes>({
|
||||
initialValues: {
|
||||
content: '',
|
||||
files: [],
|
||||
title: '',
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
title: Yup.string().required(t('errors.required')),
|
||||
content: Yup.string().required(t('errors.required')),
|
||||
}),
|
||||
onSubmit: async (values) => {
|
||||
if (files.length > 0) {
|
||||
const images = new FormData()
|
||||
files.forEach(file => {
|
||||
images.append('files', file)
|
||||
})
|
||||
await multiUpload.mutateAsync(images, {
|
||||
onSuccess: async (data) => {
|
||||
values.files = await data.data.map((item: { url: string }) => item?.url)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
|
||||
} else {
|
||||
delete values.files
|
||||
}
|
||||
|
||||
createCriticisms.mutate(values, {
|
||||
onSuccess: () => {
|
||||
toast(t('success'), 'success')
|
||||
formik.resetForm()
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 1000);
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='mt-4 text-sm'>
|
||||
<div>
|
||||
{t('criticisms.criticisms')}
|
||||
</div>
|
||||
|
||||
<div className='flex gap-6 xl:mt-8 mt-5'>
|
||||
<div className='flex-1 bg-white py-8 xl:px-10 px-5 rounded-3xl'>
|
||||
<div>
|
||||
{t('criticisms.submit_your_criticisms')}
|
||||
</div>
|
||||
|
||||
<div className='mt-3 text-xs text-description'>
|
||||
{t('criticisms.description_criticisms')}
|
||||
</div>
|
||||
|
||||
<div className='mt-10'>
|
||||
<Input
|
||||
label={t('criticisms.subject')}
|
||||
placeholder={t('criticisms.enter_your_subject')}
|
||||
{...formik.getFieldProps('title')}
|
||||
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-7'>
|
||||
<Textarea
|
||||
label={t('criticisms.description')}
|
||||
placeholder={t('criticisms.enter_description')}
|
||||
{...formik.getFieldProps('content')}
|
||||
error_text={formik.touched.content && formik.errors.content ? formik.errors.content : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-7'>
|
||||
<UploadBox
|
||||
label={t('attachment')}
|
||||
isMultiple
|
||||
onChange={setFiles}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='mt-20 flex justify-end'>
|
||||
<Button
|
||||
className='xl:max-w-[100px]'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={createCriticisms.isPending || multiUpload.isPending}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle size={20} color='white' />
|
||||
<div>
|
||||
{t('ticket.send')}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='bg-white w-sidebar hidden xl:block py-10 px-5 h-fit rounded-3xl'>
|
||||
<div className='text-sm'>
|
||||
انتقادات و پیشنهادهای شما برای ما راه گشاست:
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<div className='flex items-start gap-2 border-b pb-5'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
در صورتیکه از دپارتمان خاصی انتقادی دارید لطفا در متن توضیحات مشخص نمایید تا ما راحت تر بتوانیم نقد شما را بررسی کنیم.
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
نظرات ارزشمند شما به ما کمک میکند تا بهتر بتوانید خدمات و محصولاتمان را مطابق خواسته و نیاز شما تنظیم کنیم ، از این رو خواهشمندیم نظرات و نقد های ارزشمند خود را برای ما ارسال نمایید.
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-start gap-2 mt-6'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
تمامی نظرات توسط داناک بررسی و پردازش شده و در صورت نیاز شما را در جریان پردازش اطلاعات قرار خواهیم داد، ممکن است همکاران ما در واحد تجربه مشتری با شما تماس گرفته و جزییات بیشتری را پیرامون نقد و نظر شما جویا شوند.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddCriticisms
|
||||
@@ -0,0 +1,10 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { CreateCriticismsTypes } from "../types/CriticismsTypes";
|
||||
import * as api from "../service/CriticismsService";
|
||||
|
||||
export const useCreateCriticisms = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: CreateCriticismsTypes) =>
|
||||
api.createCriticisms(variables),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import axios from "../../../config/axios";
|
||||
import { CreateCriticismsTypes } from "../types/CriticismsTypes";
|
||||
|
||||
export const createCriticisms = async (params: CreateCriticismsTypes) => {
|
||||
const { data } = await axios.post(`/criticisms`, params);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export type CreateCriticismsTypes = {
|
||||
title: string;
|
||||
content: string;
|
||||
files?: string[];
|
||||
};
|
||||
@@ -2,7 +2,6 @@ import { Building, CloseCircle, InfoCircle, TickCircle } from 'iconsax-react'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { buildPath, clx } from '../../helpers/utils'
|
||||
import StatusWithText from '../../components/StatusWithText'
|
||||
import TitleSeeAll from '../../components/TitleSeeAll'
|
||||
import { useGetCompanies, useGetIndustry, useGetCompanyRequest } from './hooks/useHomeData'
|
||||
import { CompanyItemType, IndustryItemType } from './types/HomeTypes'
|
||||
@@ -55,15 +54,15 @@ const Home: FC = () => {
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className='mt-6'>
|
||||
{/* <div className='mt-6'>
|
||||
<TitleSeeAll
|
||||
title={t('home.last_announcement')}
|
||||
link='/'
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
|
||||
<div className='mt-4'>
|
||||
{/* <div className='mt-4'>
|
||||
<div className={clx(
|
||||
'p-4 rounded-2xl',
|
||||
'bg-[#F8F9FB]'
|
||||
@@ -94,12 +93,11 @@ const Home: FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className='mt-6'>
|
||||
<TitleSeeAll
|
||||
title={t('home.companies')}
|
||||
link='/'
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
getIndustry,
|
||||
getCompanyDetail,
|
||||
getCompanyRequest,
|
||||
getMyCompany,
|
||||
} from "../service/HomeService";
|
||||
|
||||
export const useGetSlugId = () => {
|
||||
@@ -39,5 +40,13 @@ export const useGetCompanyRequest = () => {
|
||||
return useQuery({
|
||||
queryKey: ["company-request"],
|
||||
queryFn: () => getCompanyRequest(),
|
||||
enabled: !!localStorage.getItem(import.meta.env.VITE_TOKEN_NAME),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetMyCompany = () => {
|
||||
return useQuery({
|
||||
queryKey: ["my-company"],
|
||||
queryFn: () => getMyCompany(),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -26,6 +26,11 @@ export const getCompanyDetail = async (id: string) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getMyCompany = async () => {
|
||||
const { data } = await axios.get(`/companies/user`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getCompanyRequest = async () => {
|
||||
const { data } = await axios.get(`/companies/requests/user`);
|
||||
return data;
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Select from '../../components/Select'
|
||||
import Input from '../../components/Input'
|
||||
import LevelingCart from '../../components/LevelingCard'
|
||||
import germanyFly from '../../assets/images/germany.png'
|
||||
import CourseCard from '../../components/CourseCard'
|
||||
import book from '../../assets/images/books.jpg'
|
||||
const AdmitCard: FC = () => {
|
||||
const { t } = useTranslation('global')
|
||||
return (
|
||||
<div className='mt-4 min-h-[500px]'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div>
|
||||
{t('myCourses.myCourses')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='flex justify-between items-end gap-2 xl:items-end'>
|
||||
<div className='mt-6 xl:mt-0 max-w-[420px] xl:w-[300px] w-full'>
|
||||
<Input
|
||||
variant='search'
|
||||
placeholder={t('myCourses.search')}
|
||||
className='bg-white w-full'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<div className='w-[100px] xl:w-[200px]'>
|
||||
<Select
|
||||
items={[
|
||||
{
|
||||
label: 'همه',
|
||||
value: 'active'
|
||||
}
|
||||
]}
|
||||
className='bg-white border'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex overflow-auto gap-4 mt-8 hide-scroll-bar'>
|
||||
<LevelingCart
|
||||
image={germanyFly}
|
||||
title='سطح A1'
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-6 pb-[120px]'>
|
||||
<h2>{t('myCourses.myCourses')}</h2>
|
||||
<div className='flex flex-wrap gap-2 mt-2'>
|
||||
<div className='min-w-[40%] flex-1 lg:min-w-[175px]'>
|
||||
<CourseCard
|
||||
image={book}
|
||||
teacher='حمید ضرغامی'
|
||||
time='20'
|
||||
price='1.000.000'
|
||||
title='دوره 504 واژه'
|
||||
/>
|
||||
</div>
|
||||
<div className='min-w-[40%] flex-1 lg:min-w-[175px]'>
|
||||
<CourseCard
|
||||
image={book}
|
||||
teacher='حمید ضرغامی'
|
||||
time='20'
|
||||
price='1.000.000'
|
||||
title='دوره 504 واژه'
|
||||
/>
|
||||
</div>
|
||||
<div className='min-w-[40%] flex-1 lg:min-w-[175px]'>
|
||||
<CourseCard
|
||||
image={book}
|
||||
teacher='حمید ضرغامی'
|
||||
time='20'
|
||||
price='1.000.000'
|
||||
title='دوره 504 واژه'
|
||||
/>
|
||||
</div>
|
||||
<div className='min-w-[40%] flex-1 lg:min-w-[175px]'>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default AdmitCard
|
||||
@@ -1,113 +1,174 @@
|
||||
import { Notification } from 'iconsax-react'
|
||||
import { FC, useState } from 'react'
|
||||
import XIcon from '../../assets/images/close-circle.svg'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { clx } from '../../helpers/utils'
|
||||
import StatusCircle from '../../components/StatusCircle'
|
||||
import { useOutsideClick } from '../../hooks/useOutSideClick'
|
||||
|
||||
import { Notification } from 'iconsax-react';
|
||||
import { FC, useState } from 'react';
|
||||
import XIcon from '../../assets/images/close-circle.svg';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { clx } from '../../helpers/utils';
|
||||
import StatusCircle from '../../components/StatusCircle';
|
||||
import { useOutsideClick } from '../../hooks/useOutSideClick';
|
||||
import { useGetNotification, useReadAll } from './hooks/useNotificationData';
|
||||
import { NotificationItemType, NotificationTypeEnum } from './types/NotificationTypes';
|
||||
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 '../../components/Toast';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Pages } from '../../config/Pages';
|
||||
import { ErrorType } from '../../helpers/types';
|
||||
const Notifications: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation('global');
|
||||
const ref = useOutsideClick(() => setShowModal(false));
|
||||
const [activeTab, setActiveTab] = useState<'read' | 'unread'>('unread');
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
const { data, fetchNextPage, hasNextPage, refetch } = useGetNotification(activeTab);
|
||||
const readAll = useReadAll()
|
||||
const posts = data?.pages.flatMap((page) => page.data?.notifications) || [];
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const ref = useOutsideClick(() => setShowModal(false))
|
||||
const [activeTab, setActiveTab] = useState<'all' | 'read' | 'unread'>('all')
|
||||
const [showModal, setShowModal] = useState<boolean>(false)
|
||||
const handleAllRead = () => {
|
||||
readAll.mutate(undefined, {
|
||||
onSuccess: () => {
|
||||
refetch()
|
||||
toast(t('success'), 'success')
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleRedirect = (type: NotificationTypeEnum) => {
|
||||
switch (type) {
|
||||
case NotificationTypeEnum.USER_LOGIN:
|
||||
break;
|
||||
case NotificationTypeEnum.ANNOUNCEMENT:
|
||||
navigate(Pages.announcement.list)
|
||||
break;
|
||||
case NotificationTypeEnum.WALLET_CHARGE:
|
||||
navigate(Pages.transactions)
|
||||
break;
|
||||
case NotificationTypeEnum.WALLET_DEDUCTION:
|
||||
navigate(Pages.transactions)
|
||||
break;
|
||||
case NotificationTypeEnum.BILL_INVOICE_REMINDER:
|
||||
navigate(Pages.receipts.index)
|
||||
break;
|
||||
case NotificationTypeEnum.BILL_INVOICE:
|
||||
navigate(Pages.receipts.index)
|
||||
break;
|
||||
case NotificationTypeEnum.CREATE_INVOICE:
|
||||
navigate(Pages.receipts.index)
|
||||
break;
|
||||
case NotificationTypeEnum.CREATE_SERVICE:
|
||||
navigate(Pages.services.other)
|
||||
break;
|
||||
case NotificationTypeEnum.UNBLOCK_SERVICE:
|
||||
navigate(Pages.services.other)
|
||||
break;
|
||||
case NotificationTypeEnum.BLOCK_SERVICE:
|
||||
navigate(Pages.services.other)
|
||||
break;
|
||||
case NotificationTypeEnum.ANSWER_TICKET:
|
||||
navigate(Pages.ticket.list)
|
||||
break;
|
||||
case NotificationTypeEnum.CREATE_TICKET:
|
||||
navigate(Pages.ticket.list)
|
||||
break;
|
||||
default:
|
||||
break
|
||||
}
|
||||
setShowModal(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<Notification onClick={() => setShowModal(!showModal)} size={18} color='black' />
|
||||
<div onClick={() => setShowModal(!showModal)} className='relative cursor-pointer'>
|
||||
<Notification size={18} color="black" />
|
||||
{/* <div className="absolute top-0 right-0 -mt-1 -mr-1 rounded-full bg-red-500 text-white text-[8px] size-3 flex pt-0.5 pl-[1px] justify-center items-center">
|
||||
{getDashboard.data?.data?.notificationCount}
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
{
|
||||
showModal && (
|
||||
<div className='xl:h-[calc(100%-110px)] h-[70%] p-6 z-50 xl:w-[300px] w-full xl:left-7 left-0 xl:top-[90px] top-0 overflow-y-auto xl:rounded-3xl rounded-b-3xl rounded-t-none fixed modalGlass3 '>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div>
|
||||
{t('notif.natification')}
|
||||
</div>
|
||||
<div className='size-7 rounded-full bg-white bg-opacity-35 flex justify-center items-center'>
|
||||
<img src={XIcon} alt='close' className='w-4 h-4' onClick={() => setShowModal(false)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex h-8 border border-white border-opacity-35 text-xs rounded-lg overflow-hidden'>
|
||||
<div onClick={() => setActiveTab('all')} className={clx(
|
||||
'flex-1 border-l cursor-pointer text-white border-white border-opacity-70 flex justify-center items-center',
|
||||
activeTab === 'all' ? 'bg-white bg-opacity-70 text-black' : ''
|
||||
)}>
|
||||
همه
|
||||
</div>
|
||||
<div onClick={() => setActiveTab('unread')} className={clx(
|
||||
'flex-1 border-l cursor-pointer text-white border-white border-opacity-70 bg-transparent flex justify-center items-center',
|
||||
activeTab === 'unread' ? 'bg-white bg-opacity-70 text-black' : ''
|
||||
)}>
|
||||
خوانده نشده
|
||||
</div>
|
||||
<div onClick={() => setActiveTab('read')} className={clx(
|
||||
'flex-1 cursor-pointer text-white border-white bg-transparent flex justify-center items-center',
|
||||
activeTab === 'read' ? 'bg-white bg-opacity-70 text-black' : ''
|
||||
)}>
|
||||
خوانده شده
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex flex-col gap-4 text-xs'>
|
||||
<div className='bg-white h-fit gap-4 items-start rounded-xl bg-opacity-60 p-3 flex'>
|
||||
<div className='mt-1'>
|
||||
<StatusCircle color='#00BA4B' />
|
||||
</div>
|
||||
<div className=''>
|
||||
<div className='truncate'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی
|
||||
</div>
|
||||
<div className='mt-2 flex gap-2 text-[10px] text-description'>
|
||||
<div>۲ ساعت پیش</div>
|
||||
<div className='flex gap-1 items-center'>
|
||||
<StatusCircle color='#8C90A3' />
|
||||
<div>دیزاین</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='bg-white h-fit gap-4 items-start rounded-xl bg-opacity-60 p-3 flex'>
|
||||
<div className='mt-1'>
|
||||
<StatusCircle color='#00BA4B' />
|
||||
</div>
|
||||
<div className=''>
|
||||
<div className='truncate'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی
|
||||
</div>
|
||||
<div className='mt-2 flex gap-2 text-[10px] text-description'>
|
||||
<div>۲ ساعت پیش</div>
|
||||
<div className='flex gap-1 items-center'>
|
||||
<StatusCircle color='#8C90A3' />
|
||||
<div>دیزاین</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='bg-white h-fit gap-4 items-start rounded-xl bg-opacity-60 p-3 flex'>
|
||||
<div className='mt-1'>
|
||||
<StatusCircle color='#00BA4B' />
|
||||
</div>
|
||||
<div className=''>
|
||||
<div className='truncate'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی
|
||||
</div>
|
||||
<div className='mt-2 flex gap-2 text-[10px] text-description'>
|
||||
<div>۲ ساعت پیش</div>
|
||||
<div className='flex gap-1 items-center'>
|
||||
<StatusCircle color='#8C90A3' />
|
||||
<div>دیزاین</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showModal && (
|
||||
<div id='notificationsContainer' className="xl:h-[calc(100%-110px)] h-[70%] p-6 z-50 xl:w-[300px] w-full xl:left-7 left-0 xl:top-[90px] top-0 overflow-y-auto xl:rounded-3xl rounded-b-3xl rounded-t-none fixed modalGlass3 ">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>{t('notif.natification')}</div>
|
||||
<div className="size-7 rounded-full bg-white bg-opacity-35 flex justify-center items-center">
|
||||
<img src={XIcon} alt="close" className="w-4 h-4" onClick={() => setShowModal(false)} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Notifications
|
||||
|
||||
|
||||
<div className="mt-6 flex h-8 border border-white border-opacity-35 text-xs rounded-lg overflow-hidden">
|
||||
<div
|
||||
onClick={() => setActiveTab('unread')}
|
||||
className={clx(
|
||||
'flex-1 border-l cursor-pointer text-white border-white border-opacity-70 bg-transparent flex justify-center items-center',
|
||||
activeTab === 'unread' ? 'bg-white bg-opacity-70 text-black' : ''
|
||||
)}
|
||||
>
|
||||
خوانده نشده
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setActiveTab('read')}
|
||||
className={clx(
|
||||
'flex-1 cursor-pointer text-white border-white bg-transparent flex justify-center items-center',
|
||||
activeTab === 'read' ? 'bg-white bg-opacity-70 text-black' : ''
|
||||
)}
|
||||
>
|
||||
خوانده شده
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex mt-4 justify-end'>
|
||||
<Button
|
||||
isLoading={readAll.isPending}
|
||||
onClick={handleAllRead}
|
||||
className={clx(
|
||||
'flex-1 border bg-transparent text-xs h-7 rounded-md cursor-pointer text-white border-white border-opacity-70 flex justify-center items-center',
|
||||
)}
|
||||
>
|
||||
{t('notif.all_read')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-col gap-4 text-xs overflow-auto">
|
||||
<InfiniteScroll
|
||||
dataLength={posts.length}
|
||||
next={fetchNextPage}
|
||||
hasMore={hasNextPage}
|
||||
loader={<div className='flex justify-center'><MoonLoader color="black" size={20} /></div>}
|
||||
scrollableTarget="notificationsContainer"
|
||||
>
|
||||
{
|
||||
posts.map((item: NotificationItemType) => (
|
||||
<div onClick={() => handleRedirect(item.type)} className="bg-white cursor-pointer h-fit gap-4 items-start rounded-xl bg-opacity-60 p-3 mb-4 flex" key={item.id}>
|
||||
{
|
||||
!item.isRead &&
|
||||
<div className="mt-1">
|
||||
<StatusCircle color="#00BA4B" />
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
<div className="truncate max-w-[200px]">{item.message}</div>
|
||||
<div className="mt-2 flex gap-2 text-[10px] text-description">
|
||||
<div>{timeAgo(item.createdAt)}</div>
|
||||
<div className="flex gap-1 items-center">
|
||||
<StatusCircle color="#8C90A3" />
|
||||
<div>{item.title}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notifications;
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as api from "../service/NotificationService";
|
||||
import { useInfiniteQuery, useMutation } from "@tanstack/react-query";
|
||||
|
||||
export const useGetNotification = (status: "all" | "read" | "unread") => {
|
||||
return useInfiniteQuery({
|
||||
queryKey: ["notifications", status],
|
||||
queryFn: ({ pageParam = 1 }) => api.getNotifications(pageParam, status), // فراخوانی API برای گرفتن دادهها
|
||||
initialPageParam: 1, // صفحه اول به طور پیشفرض
|
||||
getNextPageParam: (lastPage) => {
|
||||
const currentPage = lastPage.data?.pager.page;
|
||||
const totalPages = lastPage.data?.pager.totalPages;
|
||||
|
||||
return currentPage < totalPages ? currentPage + 1 : undefined;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useReadAll = () => {
|
||||
return useMutation({
|
||||
mutationFn: (_) => api.readAll(),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import axios from "../../../config/axios";
|
||||
|
||||
export const getNotifications = async (
|
||||
page: number,
|
||||
status: "all" | "read" | "unread"
|
||||
) => {
|
||||
let query = ``;
|
||||
if (status !== "all") {
|
||||
query = `&isRead=${status === "read" ? 1 : 0}`;
|
||||
}
|
||||
const { data } = await axios.get(`/notifications?page=${page}${query}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const readAll = async () => {
|
||||
const { data } = await axios.patch(`/notifications/read-all`);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
export type NotificationItemType = {
|
||||
id: string;
|
||||
title: string;
|
||||
message: string;
|
||||
createdAt: string;
|
||||
isRead: boolean;
|
||||
type: NotificationTypeEnum;
|
||||
};
|
||||
|
||||
export enum NotificationTypeEnum {
|
||||
USER_LOGIN = "USER_LOGIN",
|
||||
ANNOUNCEMENT = "ANNOUNCEMENT",
|
||||
|
||||
// Finance category
|
||||
WALLET_CHARGE = "WALLET_CHARGE",
|
||||
WALLET_DEDUCTION = "WALLET_DEDUCTION",
|
||||
|
||||
//Invoice
|
||||
BILL_INVOICE_REMINDER = "BILL_INVOICE_REMINDER",
|
||||
BILL_INVOICE = "BILL_INVOICE",
|
||||
CREATE_INVOICE = "CREATE_INVOICE",
|
||||
|
||||
// Service category
|
||||
CREATE_SERVICE = "CREATE_SERVICE",
|
||||
UNBLOCK_SERVICE = "UNBLOCK_SERVICE",
|
||||
BLOCK_SERVICE = "BLOCK_SERVICE",
|
||||
|
||||
// Support category
|
||||
ANSWER_TICKET = "ANSWER_TICKET",
|
||||
CREATE_TICKET = "CREATE_TICKET",
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { CloseCircle, InfoCircle, TickCircle } from 'iconsax-react'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { NumberFormat } from '../../config/func'
|
||||
import Button from '../../components/Button'
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import moment from 'moment-jalaali'
|
||||
import { Pages } from '../../config/Pages'
|
||||
import { buildPath } from '../../helpers/utils'
|
||||
|
||||
const CallBack: FC = () => {
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { t } = useTranslation('global')
|
||||
const { slug } = useParams()
|
||||
|
||||
const params = {
|
||||
status: searchParams.get('status') as 'PENDING' | 'FAILED' | 'COMPLETED',
|
||||
id: searchParams.get('id'),
|
||||
date: searchParams.get('date'),
|
||||
amount: searchParams.get('amount'),
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className='flex mt-10 flex-col justify-center items-center flex-1 h-full'>
|
||||
{
|
||||
params.status === 'COMPLETED' ?
|
||||
<TickCircle
|
||||
size={64}
|
||||
color={'#00712D'}
|
||||
/>
|
||||
:
|
||||
params.status === 'FAILED' ?
|
||||
<CloseCircle
|
||||
size={64}
|
||||
color='#D52903'
|
||||
/>
|
||||
:
|
||||
<InfoCircle
|
||||
size={64}
|
||||
color='#FFA800'
|
||||
/>
|
||||
}
|
||||
|
||||
<div className='mt-7 text-xl'>
|
||||
{
|
||||
params.status === 'COMPLETED' ?
|
||||
t('callback.title_success')
|
||||
:
|
||||
params.status === 'FAILED' ?
|
||||
t('callback.failed')
|
||||
:
|
||||
t('callback.pending')
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='mt-7'>
|
||||
{
|
||||
params.status === 'COMPLETED' ?
|
||||
t('callback.desc_success')
|
||||
:
|
||||
params.status === 'FAILED' ?
|
||||
t('callback.desc_failed')
|
||||
:
|
||||
t('callback.desc_pending')
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='mt-16 xl:min-w-[560px] bg-[#FBFBFD] p-8 text-sm'>
|
||||
<div className='flex justify-between'>
|
||||
<div>
|
||||
{t('callback.order_number')}
|
||||
</div>
|
||||
<div className='text-left'>
|
||||
{params.id}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex justify-between mt-6'>
|
||||
<div>
|
||||
{t('callback.amount')}
|
||||
</div>
|
||||
<div>
|
||||
{NumberFormat(Number(params.amount)) + ' ' + t('toman')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex justify-between mt-6'>
|
||||
<div>
|
||||
{t('callback.date')}
|
||||
</div>
|
||||
<div>
|
||||
{moment(params.date).format('jYYYY/jMM/jDD')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex xl:max-w-[560px] w-full gap-2.5'>
|
||||
<Button
|
||||
label={t('callback.home')}
|
||||
className='w-full mt-8 bg-transparent border border-black text-black'
|
||||
onClick={() => navigate(buildPath(Pages.dashboard, slug))}
|
||||
/>
|
||||
<Button
|
||||
label={t('callback.invoice')}
|
||||
className='w-full mt-8'
|
||||
onClick={() => navigate(buildPath(Pages.receipts.index, slug))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CallBack
|
||||
@@ -1,102 +0,0 @@
|
||||
import { useState } from 'react'
|
||||
import book from '../../assets/images/books.jpg'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tabs from '../../components/Tabs'
|
||||
import { MoneyTick, MoneyTime } from 'iconsax-react'
|
||||
import Input from '../../components/Input'
|
||||
import Button from '../../components/Button'
|
||||
import RadioGroup from '../../components/RadioGroup'
|
||||
import bank from '../../assets/images/bank.png'
|
||||
const PaymentDetails = () => {
|
||||
const [activeTab, setActiveTab] = useState<string>('unpaid')
|
||||
const { t } = useTranslation('global')
|
||||
return (
|
||||
<div className='pb-[120px]'>
|
||||
<div className="rounded-[32px] bg-white py-6 flex items-center gap-4 px-6">
|
||||
<img src={book} className='w-[95px] h-[95px] rounded-2xl' alt="" />
|
||||
<div className='space-y-2'>
|
||||
<h2 className='text-[14px]'>دوره مقدماتی</h2>
|
||||
<div className='space-y-[2px]'>
|
||||
<p className='text-[10px] text-description'>1 شنبه - 2 شنبه، ساعت 2 تا 4 عصر</p>
|
||||
<div className='flex items-center gap-1 text-[13px]'>
|
||||
<p className='text-description'>پیش پرداخت :</p>
|
||||
<p>500,000 تومان</p>
|
||||
</div>
|
||||
<div className='flex items-center gap-1 text-[13px]'>
|
||||
<p className='text-description'>کل مبلغ دوره :</p>
|
||||
<p>500,000 تومان</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-6 w-full'>
|
||||
<Tabs
|
||||
isFullWidth
|
||||
active={activeTab}
|
||||
items={[
|
||||
{
|
||||
icon: <MoneyTime color={activeTab === 'unpaid' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('payment.prePay'),
|
||||
value: 'unpaid'
|
||||
},
|
||||
{
|
||||
icon: <MoneyTick color={activeTab === 'not_confimed' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('payment.fullPay'),
|
||||
value: 'not_confimed'
|
||||
}
|
||||
]}
|
||||
onChange={setActiveTab}
|
||||
/>
|
||||
</div>
|
||||
<div className="rounded-[32px] bg-white py-6 mt-6 gap-4 px-6">
|
||||
<h2 className='text-[14px]'>{t('payment.fullPay')}</h2>
|
||||
<div className='flex items-end mt-4 gap-2 justify-between'>
|
||||
<Input placeholder='کد تخفیف' />
|
||||
<Button className='bg-white text-xs text-black border border-black max-w-[80px]' label='اعمال' />
|
||||
</div>
|
||||
<div className='flex items-center text-xs gap-3 my-6'>
|
||||
<p className='text-description'>مبلغ قابل پرداخت</p>
|
||||
<div className='border-b border-dashed border-description flex-1'></div>
|
||||
<p>1,000,000 تومان</p>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<RadioGroup
|
||||
items={[
|
||||
{
|
||||
label: t('استفاده از موجودی کیف پول'),
|
||||
value: '3month'
|
||||
},
|
||||
{
|
||||
label: t('پرداخت مستقیم'),
|
||||
value: 'year'
|
||||
}
|
||||
]}
|
||||
onChange={() => ''}
|
||||
selected={''}
|
||||
/>
|
||||
</div>
|
||||
<div className='text-xs gap-3 my-6'>
|
||||
<h2 className='text-[14px]'>روش پرداخت</h2>
|
||||
<div className='grid grid-cols-2 gap-2 mt-4'>
|
||||
<div className='border border-black rounded-2xl flex items-center justify-center'>
|
||||
<div className='text-center flex flex-col items-center space-y-1 py-4'>
|
||||
<img src={bank} className='h-[50px] w-[50px]' alt="" />
|
||||
<p>بانک پارسیان</p>
|
||||
<p className='text-description text-[10px]'>تمام کارت های عضو شتاب</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className='border border-black rounded-2xl flex items-center justify-center'>
|
||||
<div className='text-center flex flex-col items-center space-y-1 py-4'>
|
||||
<img src={bank} className='h-[50px] w-[50px]' alt="" />
|
||||
<p>بانک پارسیان</p>
|
||||
<p className='text-description text-[10px]'>تمام کارت های عضو شتاب</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaymentDetails
|
||||
@@ -2,22 +2,18 @@ import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useApplyDiscount, useGetInvoiceDetail, useRemoveDiscount } from './hooks/useReceiptData'
|
||||
import { ApplyDiscountType, ReceiptDetailItemType } from './types/ReceiptTypes'
|
||||
import { ApplyDiscountType, GatewayItemType, ReceiptDetailItemType } from './types/ReceiptTypes'
|
||||
import { NumberFormat } from '../../config/func'
|
||||
import moment from 'moment-jalaali'
|
||||
import LegalInfo from './components/LegalInfo'
|
||||
import RealInfo from './components/RealInfo'
|
||||
import RegisterInfo from './components/RegisterInfo'
|
||||
import { clx } from '../../helpers/utils'
|
||||
import Button from '../../components/Button'
|
||||
import ApproveInvoice from './components/ApproveInvoice'
|
||||
import PayInvoice from './components/PayInvoice'
|
||||
import Input from '../../components/Input'
|
||||
import { useGetProfile } from '../profile/hooks/useProfileData'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../helpers/types'
|
||||
// import PDFGenerator from '../../components/PDFGenerator'
|
||||
// import { useGetGetWays } from '../wallet/hooks/useWalletData'
|
||||
import { useGetGetWays } from './hooks/useReceiptData'
|
||||
// import { GatewayItemType } from '../wallet/types/WalletTypes'
|
||||
|
||||
const ReceiptsDetail: FC = () => {
|
||||
@@ -26,11 +22,10 @@ const ReceiptsDetail: FC = () => {
|
||||
const { t } = useTranslation('global')
|
||||
const [code, setCode] = useState<string>('')
|
||||
const [gateWayId, setGateWayId] = useState<string>('')
|
||||
const getProfile = useGetProfile()
|
||||
const getInvoce = useGetInvoiceDetail(id ? id : '')
|
||||
const applyDiscount = useApplyDiscount()
|
||||
const removeDiscount = useRemoveDiscount()
|
||||
// const getWays = useGetGetWays()
|
||||
const getWays = useGetGetWays()
|
||||
|
||||
const handleApplyDiscount = () => {
|
||||
const params: ApplyDiscountType = {
|
||||
@@ -60,13 +55,13 @@ const ReceiptsDetail: FC = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// useEffect(() => {
|
||||
useEffect(() => {
|
||||
|
||||
// if (getWays.data) {
|
||||
// setGateWayId(getWays.data?.data?.paymentGateways?.[0]?.id)
|
||||
// }
|
||||
if (getWays.data) {
|
||||
setGateWayId(getWays.data?.data?.paymentGateways?.[0]?.id)
|
||||
}
|
||||
|
||||
// }, [getWays.data])
|
||||
}, [getWays.data])
|
||||
|
||||
|
||||
return (
|
||||
@@ -299,26 +294,23 @@ const ReceiptsDetail: FC = () => {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
{/* {
|
||||
getInvoce.data?.data?.remainingToCharge > 0 &&
|
||||
<div className='flex mt-6 justify-center gap-2'>
|
||||
{
|
||||
getWays.data?.data?.paymentGateways?.map((item: GatewayItemType) => {
|
||||
return (
|
||||
<div key={item.id} className={clx(
|
||||
'size-[72px] rounded-xl border border-border flex flex-col justify-center items-center',
|
||||
gateWayId === item.id && 'border-black'
|
||||
)}>
|
||||
<img src={item.logoUrl} className='w-8' />
|
||||
<div className='mt-1 text-[10px]'>
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
} */}
|
||||
<div className='flex mt-6 justify-center gap-2'>
|
||||
{
|
||||
getWays.data?.data?.paymentGateways?.map((item: GatewayItemType) => {
|
||||
return (
|
||||
<div key={item.id} className={clx(
|
||||
'size-[72px] rounded-xl border border-border flex flex-col justify-center items-center',
|
||||
gateWayId === item.id && 'border-black'
|
||||
)}>
|
||||
<img src={item.logoUrl} className='w-8' />
|
||||
<div className='mt-1 text-[10px]'>
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { NumberFormat } from '../../config/func'
|
||||
import { ReceiptItemType } from './types/ReceiptTypes'
|
||||
import moment from 'moment-jalaali'
|
||||
import { InvoiceStatus } from './types/ReceiptTypes'
|
||||
|
||||
import { buildPath } from '../../helpers/utils'
|
||||
|
||||
|
||||
const ReceiptsList: FC = () => {
|
||||
@@ -111,7 +111,7 @@ const ReceiptsList: FC = () => {
|
||||
<Td text={t(`receip.${item.status}`)} />
|
||||
<Td text={item.status === 'PAID' ? moment(item.paidAt).format('jYYYY-jMM-jDD') : t('receip.not_paid')} />
|
||||
<Td text={''}>
|
||||
<Link to={Pages.receipts.detail + item.id}>
|
||||
<Link to={buildPath(Pages.receipts.detail + item.id)}>
|
||||
<Eye size={20} color='black' />
|
||||
</Link>
|
||||
</Td>
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
import { FC, Fragment } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGetLegalInfo } from '../../financial/hooks/useFinancialData'
|
||||
import { useGetProfile } from '../../profile/hooks/useProfileData'
|
||||
|
||||
|
||||
const LegalInfo: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { data: d, isSuccess } = useGetLegalInfo()
|
||||
const getProfile = useGetProfile()
|
||||
|
||||
const data = d?.data?.legalUser
|
||||
|
||||
if (isSuccess)
|
||||
return (
|
||||
<Fragment>
|
||||
<div className='mt-8 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.type_person')}
|
||||
</div>
|
||||
<div>
|
||||
{
|
||||
t('receip.company')
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.representativeـname')}
|
||||
</div>
|
||||
<div>
|
||||
{getProfile.data?.data?.user?.firstName + ' ' + getProfile.data?.data?.user?.lastName}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.company_name')}
|
||||
</div>
|
||||
<div>
|
||||
{data.registrationName}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.registration_number')}
|
||||
</div>
|
||||
<div>
|
||||
{data.registrationCode}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 border-t pt-7 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.tel_company')}
|
||||
</div>
|
||||
<div>
|
||||
{data.phone}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.national_code')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.nationalIdentity}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.postal_code')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.address?.postalCode}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.state')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.address?.city?.province?.name}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.city')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.address?.city?.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.address')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.address?.fullAddress}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default LegalInfo
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC, Fragment, useState } from 'react'
|
||||
import Button from '../../../components/Button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGetInvoiceDetail, usePayInvoice } from '../hooks/useReceiptData'
|
||||
import { useGetInvoiceDetail, usePayInvoice, usePayment } from '../hooks/useReceiptData'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import ModalConfrim from '../../../components/ModalConfrim'
|
||||
// import { usePayment } from '../../wallet/hooks/useWalletData'
|
||||
@@ -19,7 +19,7 @@ const PayInvoice: FC<Props> = ({ id, refetch, gateWayId }) => {
|
||||
const [showModal, setShowModal] = useState<boolean>(false)
|
||||
const getInvoce = useGetInvoiceDetail(id)
|
||||
const payInvoice = usePayInvoice()
|
||||
// const payment = usePayment()
|
||||
const payment = usePayment()
|
||||
|
||||
|
||||
const handleConfrim = () => {
|
||||
@@ -36,22 +36,23 @@ const PayInvoice: FC<Props> = ({ id, refetch, gateWayId }) => {
|
||||
}
|
||||
|
||||
const handleChartWallet = () => {
|
||||
// payment.mutate({ amount: getInvoce.data?.data?.remainingToCharge, gatewayId: gateWayId, invoiceId: id }, {
|
||||
// onSuccess: (data) => {
|
||||
// window.location.href = data?.data?.redirectUrl
|
||||
// toast(t('wallet.transfer_to_getway'), 'success')
|
||||
// },
|
||||
// onError: (error: ErrorType) => {
|
||||
// toast(error.response?.data?.error.message[0], 'error')
|
||||
// }
|
||||
// })
|
||||
payment.mutate({ gatewayId: gateWayId, invoiceId: id }, {
|
||||
onSuccess: (data) => {
|
||||
window.location.href = data?.data?.redirectUrl
|
||||
toast(t('wallet.transfer_to_getway'), 'success')
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error.message[0], 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
label={getInvoce.data?.data?.remainingToCharge > 0 ? t('receip.charge_wallet') : t('receip.pay')}
|
||||
onClick={() => setShowModal(true)}
|
||||
label={t('receip.pay')}
|
||||
onClick={handleChartWallet}
|
||||
isLoading={payment.isPending}
|
||||
/>
|
||||
|
||||
<ModalConfrim
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import { FC, Fragment } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGetRealInfo } from '../../financial/hooks/useFinancialData'
|
||||
|
||||
|
||||
|
||||
const RealInfo: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { data: d } = useGetRealInfo()
|
||||
|
||||
const data = d?.data?.realUser
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className='mt-8 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.type_person')}
|
||||
</div>
|
||||
<div>
|
||||
{t('receip.real_person')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.fullName')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.firstName + ' ' + data?.lastName}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('financial.father_name')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.fatherName}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('financial.sex')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.gender === 'MALE' ? 'مرد' : 'زن'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 border-t pt-7 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('financial.phone')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.phone}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.postal_code')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.address?.postalCode}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.state')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.address?.city?.province?.name}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.city')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.address?.city?.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.address')}
|
||||
</div>
|
||||
<div>
|
||||
{data?.address?.fullAddress}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default RealInfo
|
||||
@@ -1,13 +1,9 @@
|
||||
import { FC, Fragment } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FinancialDataType } from '../../financial/types/FinancialTypes'
|
||||
import { InfoCircle } from 'iconsax-react'
|
||||
import Button from '../../../components/Button'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../../../config/Pages'
|
||||
|
||||
type Props = {
|
||||
data: FinancialDataType
|
||||
data: any
|
||||
}
|
||||
|
||||
const RegisterInfo: FC<Props> = ({ data }) => {
|
||||
@@ -25,14 +21,7 @@ const RegisterInfo: FC<Props> = ({ data }) => {
|
||||
/>
|
||||
<div className='text-center xl:text-right'>{t('receip.compelete_financal_info_error')}</div>
|
||||
|
||||
<div className='flex-1 flex xl:justify-end'>
|
||||
<Link to={Pages.financial}>
|
||||
<Button
|
||||
label={t('receip.compelete_financal_info')}
|
||||
className='w-fit whitespace-nowrap px-4 h-7 text-[10px]'
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className='mt-8 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||
<div className='flex gap-2'>
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as api from "../service/ReceiptService";
|
||||
import {
|
||||
ApplyDiscountType,
|
||||
ApproveInvoiceType,
|
||||
PaymentType,
|
||||
RemoveDiscountType,
|
||||
RequestApproveInvoiceType,
|
||||
} from "../types/ReceiptTypes";
|
||||
@@ -60,3 +61,16 @@ export const useRemoveDiscount = () => {
|
||||
api.removeDiscount(variables.id),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetGetWays = () => {
|
||||
return useQuery({
|
||||
queryKey: ["ways"],
|
||||
queryFn: () => api.getGetWays(),
|
||||
});
|
||||
};
|
||||
|
||||
export const usePayment = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: PaymentType) => api.payment(variables),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import axios from "../../../config/axios";
|
||||
import {
|
||||
ApplyDiscountType,
|
||||
ApproveInvoiceType,
|
||||
PaymentType,
|
||||
RequestApproveInvoiceType,
|
||||
} from "../types/ReceiptTypes";
|
||||
|
||||
@@ -45,3 +46,13 @@ export const removeDiscount = async (id: string) => {
|
||||
const { data } = await axios.post(`/invoices/${id}/cancel-discount`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getGetWays = async () => {
|
||||
const { data } = await axios.get(`/payments/gateways`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const payment = async (params: PaymentType) => {
|
||||
const { data } = await axios.post(`/payments/checkout`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -58,3 +58,20 @@ export type ApplyDiscountType = {
|
||||
export type RemoveDiscountType = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type GatewayItemType = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
logoUrl: string;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
nameFa: string;
|
||||
};
|
||||
|
||||
export type PaymentType = {
|
||||
amount?: number;
|
||||
gatewayId: string;
|
||||
invoiceId?: string;
|
||||
};
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -1,123 +0,0 @@
|
||||
import { ArrowRight, MoneyRecive, MoneySend } from 'iconsax-react'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Td from '../../components/Td'
|
||||
import Detail from './components/Detail'
|
||||
|
||||
const TransactionList: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div>
|
||||
{t('transaction.transaction')}
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex xl:gap-6 gap-1 justify-center'>
|
||||
<div className='bg-white flex-1 rounded-2xl xl:p-6 py-2 px-4 xl:max-w-[253px]'>
|
||||
<div className='flex xl:flex-row flex-col gap-2 items-center'>
|
||||
<div className='size-8 bg-[#EEF0F7] rounded-full flex justify-center items-center'>
|
||||
<MoneySend size={18} color='black' />
|
||||
</div>
|
||||
<div className='xl:text-xs text-[10px] text-[#101828]'>
|
||||
{t('transaction.totao_deposit')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-2 flex xl:flex-row flex-col gap-2 items-center'>
|
||||
<div className='text-[#101828] xl:text-xs text-[10px]'>
|
||||
۱۴۰,۰۰۰,۰۰۰ ریال
|
||||
</div>
|
||||
<div className='flex gap gap-0.5 px-2 py-1 bg-green-100 rounded-lg text-[10px] text-success'>
|
||||
<ArrowRight size={12} color='#00BA4B' className='-rotate-45' />
|
||||
<div>۳۰٪</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='bg-white flex-1 rounded-2xl xl:p-6 py-2 px-4 xl:max-w-[253px]'>
|
||||
<div className='flex xl:flex-row flex-col gap-2 items-center'>
|
||||
<div className='size-8 bg-[#EEF0F7] rounded-full flex justify-center items-center'>
|
||||
<MoneyRecive size={18} color='black' />
|
||||
</div>
|
||||
<div className='xl:text-xs text-[10px] text-[#101828]'>
|
||||
{t('transaction.total_withdrawal')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-2 flex xl:flex-row flex-col gap-2 items-center'>
|
||||
<div className='text-[#101828] xl:text-xs text-[10px]'>
|
||||
۱۴۰,۰۰۰,۰۰۰ ریال
|
||||
</div>
|
||||
<div className='flex gap gap-0.5 px-2 py-1 bg-green-100 rounded-lg text-[10px] text-success'>
|
||||
<ArrowRight size={12} color='#00BA4B' className='-rotate-45' />
|
||||
<div>۳۰٪</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='bg-white flex-1 rounded-2xl xl:p-6 py-2 px-4 xl:max-w-[253px]'>
|
||||
<div className='flex xl:flex-row flex-col gap-2 items-center'>
|
||||
<div className='size-8 bg-[#EEF0F7] rounded-full flex xl:flex-row flex-col justify-center items-center'>
|
||||
<MoneyRecive size={18} color='black' />
|
||||
</div>
|
||||
<div className='xl:text-xs text-[10px] text-[#101828]'>
|
||||
{t('transaction.balance')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-2 flex xl:flex-row flex-col gap-2 items-center'>
|
||||
<div className='text-[#101828] xl:text-xs text-[10px]'>
|
||||
۱۴۰,۰۰۰,۰۰۰ ریال
|
||||
</div>
|
||||
<div className='flex gap gap-0.5 px-2 py-1 text-red-400 rounded-lg text-[10px] bg-red-100'>
|
||||
<ArrowRight size={12} color='red' className='rotate-45' />
|
||||
<div>۳۰٪</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
||||
<table className='w-full text-sm '>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={t('ticket.number')} />
|
||||
<Td text={t('ticket.title')} />
|
||||
<Td text={t('ticket.team')} />
|
||||
<Td text={t('ticket.date')} />
|
||||
<Td text={t('ticket.status')} />
|
||||
<Td text={t('ticket.priority')} />
|
||||
<Td text={t('ticket.detail')} />
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className='tr'>
|
||||
<Td text={t('ticket.number')} />
|
||||
<Td text={t('ticket.title')} />
|
||||
<Td text={t('ticket.team')} />
|
||||
<Td text={t('ticket.date')} />
|
||||
<Td text={t('ticket.status')} />
|
||||
<Td text={t('ticket.priority')} />
|
||||
<Td text={t('ticket.detail')} />
|
||||
<Td text={''}>
|
||||
<Detail
|
||||
/>
|
||||
</Td>
|
||||
</tr>
|
||||
<tr className='tr'>
|
||||
<Td text={t('ticket.number')} />
|
||||
<Td text={t('ticket.title')} />
|
||||
<Td text={t('ticket.team')} />
|
||||
<Td text={t('ticket.date')} />
|
||||
<Td text={t('ticket.status')} />
|
||||
<Td text={t('ticket.priority')} />
|
||||
<Td text={t('ticket.detail')} />
|
||||
<Td text={''} />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TransactionList
|
||||
@@ -1,177 +0,0 @@
|
||||
import { Calendar, CardPos, Document, DocumentDownload, DollarCircle, Eye, NoteText, Printer, Profile, Receipt, Status, Wallet1 } from 'iconsax-react'
|
||||
import { FC, useState } from 'react'
|
||||
import DefaulModal from '../../../components/DefaulModal'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import StatusCircle from '../../../components/StatusCircle'
|
||||
import Button from '../../../components/Button'
|
||||
|
||||
const Detail: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [showModal, setShowModal] = useState<boolean>(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Eye onClick={() => setShowModal(true)} className='cursor-pointer' size={20} color='black' />
|
||||
|
||||
<DefaulModal
|
||||
open={showModal}
|
||||
close={() => setShowModal(false)}
|
||||
isHeader
|
||||
title_header={`${t('transaction.transaction2')} ۱۲۳۴۵۵`}
|
||||
>
|
||||
<div className='my-10'>
|
||||
<div className='flex lg:flex-row flex-col '>
|
||||
<div className='flex-1 px-6 h-10 flex justify-between items-center'>
|
||||
<div className='flex gap-2 text-description'>
|
||||
<CardPos size={14} color='#8C90A3' />
|
||||
<div>
|
||||
{t('transaction.type_deposit')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
واریز شتابی
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 mt-4 px-6 h-10 flex justify-between items-center'>
|
||||
<div className='flex gap-2 text-description'>
|
||||
<Status size={14} color='#8C90A3' />
|
||||
<div>
|
||||
{t('transaction.status')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<StatusCircle
|
||||
color='#00BA4B'
|
||||
/>
|
||||
<div>{t('transaction.success_paid')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex lg:flex-row flex-col'>
|
||||
<div className='flex-1 mt-4 px-6 h-10 flex justify-between items-center'>
|
||||
<div className='flex gap-2 text-description'>
|
||||
<Wallet1 size={14} color='#8C90A3' />
|
||||
<div>
|
||||
{t('transaction.type_getway')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
آنلاین بانک سامان
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 mt-4 px-6 h-10 flex justify-between items-center'>
|
||||
<div className='flex gap-2 text-description'>
|
||||
<DollarCircle size={14} color='#8C90A3' />
|
||||
<div>
|
||||
{t('transaction.amount')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2 items-center'>
|
||||
۱۰,۰۰۰,۰۰۰ ریال
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex lg:flex-row flex-col '>
|
||||
<div className='flex-1 mt-4 px-6 h-10 flex justify-between items-center'>
|
||||
<div className='flex gap-2 text-description'>
|
||||
<Profile size={14} color='#8C90A3' />
|
||||
<div>
|
||||
{t('transaction.doer')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
مهرداد مظفری
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 mt-4 px-6 h-10 flex justify-between items-center'>
|
||||
<div className='flex gap-2 text-description'>
|
||||
<Calendar size={14} color='#8C90A3' />
|
||||
<div>
|
||||
{t('transaction.date')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2 items-center'>
|
||||
۱۴۰۳/۰۹/۳۰
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex lg:flex-row flex-col '>
|
||||
<div className='flex-1 mt-4 px-6 h-10 flex justify-between items-center'>
|
||||
<div className='flex gap-2 text-description'>
|
||||
<Document size={14} color='#8C90A3' />
|
||||
<div>
|
||||
{t('transaction.tracking_number')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
bbd704c-۱۲۳۴۵۶
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 mt-4 px-6 h-10 flex justify-between items-center'>
|
||||
<div className='flex gap-2 text-description'>
|
||||
<Receipt size={14} color='#8C90A3' />
|
||||
<div>
|
||||
{t('transaction.order_number')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2 items-center'>
|
||||
bbd704c-۱۲۳۴۵۶
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex lg:flex-row flex-col gap-8'>
|
||||
<div className='flex-1 mt-4 px-6 h-10 flex justify-between items-center'>
|
||||
<div className='flex gap-2 text-description'>
|
||||
<NoteText size={14} color='#8C90A3' />
|
||||
<div>
|
||||
{t('transaction.description_transaction')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
-
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='border-t pt-8 flex justify-end border-white border-opacity-40'>
|
||||
<div className='flex gap-5'>
|
||||
<Button
|
||||
className='bg-white bg-opacity-35 w-[170px] text-description'
|
||||
>
|
||||
<div className='flex gap-2 items-center justify-center'>
|
||||
<Printer
|
||||
size={20}
|
||||
color='#8C90A3'
|
||||
/>
|
||||
<div>
|
||||
{t('transaction.print')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Button>
|
||||
<Button
|
||||
className='bg-black w-[170px] '
|
||||
>
|
||||
<div className='flex gap-2 items-center justify-center'>
|
||||
<DocumentDownload
|
||||
size={20}
|
||||
color='white'
|
||||
/>
|
||||
<div>
|
||||
{t('transaction.download')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DefaulModal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Detail
|
||||
+8
-6
@@ -9,12 +9,10 @@ import Footer from "../shared/Footer"
|
||||
import GradeRecordes from "../pages/grade/records"
|
||||
import Certificate from "../pages/certificate/List"
|
||||
import AdmitCard from "../pages/admitCard/List"
|
||||
import MyCourses from "../pages/my-courses/List"
|
||||
import Payment from "../pages/payment/Details"
|
||||
import Payment from "../pages/payment/CallBack"
|
||||
import Wallet from "../pages/wallet/Wallet"
|
||||
import ReceiptsDetail from "../pages/receipts/Detail"
|
||||
import ReportCardList from "../pages/ReportCard/List"
|
||||
import TransactionList from "../pages/transaction/List"
|
||||
import Profile from "../pages/profile/Profile"
|
||||
import CompanyDetail from "../pages/company/Detail"
|
||||
import { useEffect } from "react"
|
||||
@@ -25,6 +23,9 @@ import Request from "../pages/request/Request"
|
||||
import TicketList from "../pages/ticket/TicketList"
|
||||
import TicketCreate from "../pages/ticket/CreateTicket"
|
||||
import TicketDetail from "../pages/ticket/Detail"
|
||||
import AnnouncementtList from "../pages/annoncement/List"
|
||||
import AnnouncementDetail from "../pages/annoncement/Detail"
|
||||
import MyCompany from "../pages/company/MyCompany"
|
||||
const MainRouter = () => {
|
||||
const { slug } = useParams()
|
||||
const getSlugId = useGetSlugId()
|
||||
@@ -43,7 +44,7 @@ const MainRouter = () => {
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error.message[0])
|
||||
window.location.href = 'https://danakcorp.com'
|
||||
// window.location.href = 'https://danakcorp.com'
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -62,13 +63,12 @@ const MainRouter = () => {
|
||||
<Route path="dashboard" element={<Home />} />
|
||||
<Route path="receipts" element={<ReceiptsList />} />
|
||||
<Route path="receipts/:id" element={<ReceiptsDetail />} />
|
||||
<Route path="company/my" element={<MyCompany />} />
|
||||
<Route path="company/:id" element={<CompanyDetail />} />
|
||||
<Route path="criticisms" element={<AddCriticisms />} />
|
||||
<Route path="grade/records" element={<GradeRecordes />} />
|
||||
<Route path="transactions" element={<TransactionList />} />
|
||||
<Route path="certificate" element={<Certificate />} />
|
||||
<Route path="admitCard" element={<AdmitCard />} />
|
||||
<Route path="myCourses" element={<MyCourses />} />
|
||||
<Route path="payment" element={<Payment />} />
|
||||
<Route path="wallet" element={<Wallet />} />
|
||||
<Route path="reportCard/list" element={<ReportCardList />} />
|
||||
@@ -77,6 +77,8 @@ const MainRouter = () => {
|
||||
<Route path="tickets" element={<TicketList />} />
|
||||
<Route path="tickets/create" element={<TicketCreate />} />
|
||||
<Route path="tickets/messages/:id" element={<TicketDetail />} />
|
||||
<Route path="announcement" element={<AnnouncementtList />} />
|
||||
<Route path="announcement/:id" element={<AnnouncementDetail />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+25
-16
@@ -1,42 +1,49 @@
|
||||
import { Buildings, Buildings2, Home2, Messages3, NotificationStatus } from 'iconsax-react'
|
||||
import { Buildings, Home2, Messages3, NotificationStatus, Receipt21 } from 'iconsax-react'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pages } from '../config/Pages'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { Link, useParams, useLocation } from 'react-router-dom'
|
||||
|
||||
const Footer: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { slug } = useParams()
|
||||
const location = useLocation()
|
||||
|
||||
const isActive = (path: string) => {
|
||||
return location.pathname.includes(path)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='xl:hidden'>
|
||||
<div className='h-[60px] '></div>
|
||||
<div className='fixed bottom-2 z-10 right-3 left-3 bg-white h-[60px] rounded-2xl flex justify-between items-center px-3'>
|
||||
<Link to={`/${slug}/${Pages.dashboard}`}>
|
||||
<div className='text-description w-[70px] flex flex-col items-center gap-1.5'>
|
||||
<div className={`w-[70px] flex flex-col items-center gap-1.5 ${isActive(Pages.dashboard) ? 'text-black' : 'text-description'}`}>
|
||||
<Home2
|
||||
className='size-5'
|
||||
color='#8C90A3'
|
||||
color={isActive(Pages.dashboard) ? 'black' : '#8C90A3'}
|
||||
variant={isActive(Pages.dashboard) ? 'Bold' : 'Linear'}
|
||||
/>
|
||||
<div className='text-[10px]'>
|
||||
{t('footer.home')}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link to={`/${slug}/${Pages.services.other}`}>
|
||||
<div className='text-description w-[70px] flex flex-col items-center gap-1.5'>
|
||||
<Buildings2
|
||||
<Link to={`/${slug}/${Pages.receipts.index}`}>
|
||||
<div className={`w-[70px] flex flex-col items-center gap-1.5 ${isActive(Pages.receipts.index) ? 'text-black' : 'text-description'}`}>
|
||||
<Receipt21
|
||||
className='size-5'
|
||||
color='#8C90A3'
|
||||
color={isActive(Pages.receipts.index) ? 'black' : '#8C90A3'}
|
||||
variant={isActive(Pages.receipts.index) ? 'Bold' : 'Linear'}
|
||||
/>
|
||||
<div className='text-[10px]'>
|
||||
{t('footer.companies')}
|
||||
{t('footer.invoice')}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link to={`/${slug}/${Pages.services.mine}`}>
|
||||
<div className='text-description w-[70px] flex flex-col items-center gap-1.5'>
|
||||
<Link to={`/${slug}/${Pages.company.my}`}>
|
||||
<div className={`w-[70px] flex flex-col items-center gap-1.5 ${isActive(Pages.company.my) ? 'text-black' : 'text-description'}`}>
|
||||
<div className='bg-white p-1 rounded-full -mt-7'>
|
||||
<div className='bg-black flex justify-center items-center size-10 rounded-full'>
|
||||
<Buildings
|
||||
@@ -45,16 +52,17 @@ const Footer: FC = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-[10px] '>
|
||||
<div className='text-[10px]'>
|
||||
{t('footer.my_company')}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link to={`/${slug}/${Pages.announcement.list}`}>
|
||||
<div className='text-description w-[70px] flex flex-col items-center gap-1.5'>
|
||||
<div className={`w-[70px] flex flex-col items-center gap-1.5 ${isActive(Pages.announcement.list) ? 'text-black' : 'text-description'}`}>
|
||||
<NotificationStatus
|
||||
className='size-5'
|
||||
color='#8C90A3'
|
||||
color={isActive(Pages.announcement.list) ? 'black' : '#8C90A3'}
|
||||
variant={isActive(Pages.announcement.list) ? 'Bold' : 'Linear'}
|
||||
/>
|
||||
<div className='text-[10px]'>
|
||||
{t('footer.announcements')}
|
||||
@@ -63,10 +71,11 @@ const Footer: FC = () => {
|
||||
</Link>
|
||||
|
||||
<Link to={`/${slug}/${Pages.ticket.list}`}>
|
||||
<div className='text-description w-[70px] flex flex-col items-center gap-1.5'>
|
||||
<div className={`w-[70px] flex flex-col items-center gap-1.5 ${isActive(Pages.ticket.list) ? 'text-black' : 'text-description'}`}>
|
||||
<Messages3
|
||||
className='size-5'
|
||||
color='#8C90A3'
|
||||
color={isActive(Pages.ticket.list) ? 'black' : '#8C90A3'}
|
||||
variant={isActive(Pages.ticket.list) ? 'Bold' : 'Linear'}
|
||||
/>
|
||||
<div className='text-[10px]'>
|
||||
{t('footer.tickets')}
|
||||
|
||||
+66
-21
@@ -1,21 +1,31 @@
|
||||
import { FC } from 'react'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import Input from '../components/Input'
|
||||
import { ArrowDown2, Element3, HambergerMenu, Wallet } from 'iconsax-react'
|
||||
import AvatarImage from '../assets/images/Avatar.png'
|
||||
import { ArrowDown2, CloseCircle, HambergerMenu, Logout } from 'iconsax-react'
|
||||
import AvatarImage from '../assets/images/avatar_image.png'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { Link, useLocation } 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'
|
||||
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
|
||||
import SideBarItem from './SideBarItem'
|
||||
|
||||
const Header: FC = () => {
|
||||
|
||||
const location = useLocation();
|
||||
const [popoverKey, setPopoverKey] = useState(0);
|
||||
const { t } = useTranslation('global')
|
||||
const { setOpenSidebar, openSidebar } = useSharedStore()
|
||||
const { slug } = useParams()
|
||||
const { data } = useGetProfile()
|
||||
|
||||
useEffect(() => {
|
||||
setPopoverKey((prevKey) => prevKey + 1);
|
||||
}, [location.pathname]);
|
||||
|
||||
return (
|
||||
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
|
||||
|
||||
<div className='min-w-[270px] hidden xl:block'>
|
||||
<Input
|
||||
variant='search'
|
||||
@@ -26,23 +36,58 @@ const Header: FC = () => {
|
||||
<HambergerMenu size={24} color='black' />
|
||||
</div>
|
||||
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */}
|
||||
<div className='flex xl:gap-6 gap-3 items-center'>
|
||||
<Element3 color='black' className='xl:size-[18px] size-4' />
|
||||
<Link to={`/${slug}/${Pages.wallet}`}>
|
||||
<Wallet className='xl:size-[18px] size-4' color='black' />
|
||||
</Link>
|
||||
<div className='flex xl:gap-6 gap-4 items-center'>
|
||||
<Notifications />
|
||||
<div className='flex gap-2 items-center'>
|
||||
<div className='size-7 rounded-full bg-description overflow-hidden'>
|
||||
<Link to={`/${slug}/${Pages.profile}`}>
|
||||
<img src={AvatarImage} className='size-full object-cover' />
|
||||
</Link>
|
||||
</div>
|
||||
<div className='xl:flex hidden gap-1 items-center'>
|
||||
<div className='text-xs'>مهرداد مظفری</div>
|
||||
<ArrowDown2 size={14} color='#8C90A3' />
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
data && (
|
||||
<Popover className="relative" key={popoverKey}>
|
||||
<PopoverButton >
|
||||
<div className='flex gap-2 items-center mt-2.5'>
|
||||
<div className='size-6 rounded-full bg-description overflow-hidden'>
|
||||
<img src={data?.data?.user?.profilePic ? data?.data?.user?.profilePic : AvatarImage} className='size-full object-cover' />
|
||||
</div>
|
||||
<div className='xl:flex hidden gap-1 items-center'>
|
||||
<div className='text-xs'>
|
||||
{data?.data?.user?.firstName + ' ' + data?.data?.user?.lastName}
|
||||
</div>
|
||||
<ArrowDown2 size={14} color='#8C90A3' />
|
||||
</div>
|
||||
</div>
|
||||
</PopoverButton>
|
||||
|
||||
<PopoverPanel style={{ minHeight: window.innerWidth < 1140 ? window.innerHeight : undefined }} anchor="bottom" className="flex xl:ml-6 overflow-auto flex-col gap-3 bg-white boxShadow xl:mt-7 z-30 py-4 text-xs rounded-2.5 xl:w-[300px] -mt-[50px] w-full fixed xl:h-fit h-full shadow-md">
|
||||
<div className='absolute xl:hidden top-6 left-6'>
|
||||
<CloseCircle onClick={() => setPopoverKey((prevKey) => prevKey + 1)} size={20} color='black' />
|
||||
</div>
|
||||
<Link to={Pages.profile} className='flex flex-col gap-2 items-center justify-center border-b border-[#EAEDF5] pb-4'>
|
||||
<div className='size-14 rounded-full overflow-hidden'>
|
||||
<img src={data?.data?.user?.profilePic ? data?.data?.user?.profilePic : AvatarImage} className='size-full object-cover' />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{data?.data?.user?.firstName + ' ' + data?.data?.user?.lastName}
|
||||
</div>
|
||||
|
||||
<div className='text-description'>
|
||||
{data?.data?.user?.email}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className='px-6 -mt-[2px]'>
|
||||
<SideBarItem
|
||||
icon={<Logout color={'black'} size={20} />}
|
||||
title={t('sidebar.logout')}
|
||||
isActive
|
||||
link={Pages.setting}
|
||||
isWithoutLine
|
||||
isLogout
|
||||
/>
|
||||
</div>
|
||||
|
||||
</PopoverPanel>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
+2
-17
@@ -1,7 +1,7 @@
|
||||
import { FC } from 'react'
|
||||
import LogoImage from '../assets/images/logo.svg'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Card, DocumentText, Home2, Logout, Messages3, NotificationStatus, Setting2, Teacher, Receipt21 } from 'iconsax-react'
|
||||
import { DocumentText, Home2, Logout, Messages3, NotificationStatus, Setting2, Receipt21 } from 'iconsax-react'
|
||||
import SideBarItem from './SideBarItem'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Pages } from '../config/Pages'
|
||||
@@ -55,14 +55,6 @@ const SideBar: FC = () => {
|
||||
link={Pages.receipts.index}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<SideBarItem
|
||||
icon={<Card variant={isActive('transactions') ? 'Bold' : 'Outline'} color={isActive('transactions') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.transactions')}
|
||||
isActive={isActive('transactions')}
|
||||
link={Pages.transactions}
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-10 px-12 text-header text-sm font-normal'>
|
||||
{t('sidebar.other')}
|
||||
@@ -91,14 +83,6 @@ const SideBar: FC = () => {
|
||||
link={Pages.criticisms}
|
||||
/>
|
||||
</div>
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<SideBarItem
|
||||
icon={<Teacher variant={isActive('learning') ? 'Bold' : 'Outline'} color={isActive('learning') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.learning')}
|
||||
isActive={isActive('learning')}
|
||||
link={Pages.learning}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex-1 items-end mt-14'>
|
||||
<div className='text-xs text-[#8C90A3]'>
|
||||
<SideBarItem
|
||||
@@ -114,6 +98,7 @@ const SideBar: FC = () => {
|
||||
title={t('sidebar.logout')}
|
||||
isActive={isActive('logout')}
|
||||
link={`#`}
|
||||
isLogout
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+22
-18
@@ -1,43 +1,47 @@
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { clx } from '../helpers/utils'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { buildPath, clx } from '../helpers/utils'
|
||||
import { Pages } from '../config/Pages'
|
||||
import { useLogout } from '../pages/auth/hooks/useAuthData'
|
||||
import { removeToken } from '../config/func'
|
||||
import { removeRefreshToken } from '../config/func'
|
||||
|
||||
type Props = {
|
||||
icon: ReactNode,
|
||||
title: string,
|
||||
isActive: boolean,
|
||||
link: string, // مقدار مثل: "dashboard" یا "wallet"
|
||||
link: string,
|
||||
isLogout?: boolean,
|
||||
isWithoutLine?: boolean
|
||||
}
|
||||
|
||||
const SideBarItem: FC<Props> = (props: Props) => {
|
||||
const { slug } = useParams(); // دریافت slug از URL
|
||||
const fullLink = props.isLogout ? Pages.auth.login : `/${slug}/${props.link.replace(/^\//, '')}`;
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem(import.meta.env.VITE_TOKEN_NAME)
|
||||
const logout = useLogout()
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout.mutateAsync()
|
||||
removeToken()
|
||||
removeRefreshToken()
|
||||
window.location.href = Pages.auth.login
|
||||
}
|
||||
|
||||
// alert(slug)
|
||||
|
||||
return (
|
||||
<Link
|
||||
onClick={props.isLogout ? handleLogout : undefined}
|
||||
to={fullLink}
|
||||
className='flex text-xs gap-9 mt-4'
|
||||
>
|
||||
<div className={clx(
|
||||
'w-1 bg-black h-6',
|
||||
!props.isActive && 'invisible'
|
||||
)}></div>
|
||||
<Link onClick={props.isLogout ? handleLogout : undefined} to={buildPath(props.link)} className='flex text-xs gap-9 mt-4'>
|
||||
{
|
||||
!props.isWithoutLine &&
|
||||
<div className={clx(
|
||||
'w-1 bg-black h-6',
|
||||
!props.isActive && 'invisible'
|
||||
)}></div>
|
||||
}
|
||||
<div className='flex gap-3 items-center'>
|
||||
{props.icon}
|
||||
<div className={props.isActive ? 'text-black' : ''}>
|
||||
{props.title}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user