my ccompany + footer+ nofit + ...

This commit is contained in:
hamid zarghami
2025-05-21 15:54:02 +03:30
parent 70b7434a3e
commit 836c1168b2
50 changed files with 1137 additions and 5321 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
VITE_TOKEN_NAME = 'dzone_token' VITE_TOKEN_NAME = 'dzone_token'
VITE_REFRESH_TOKEN_NAME = 'dzone_refresh_token' VITE_REFRESH_TOKEN_NAME = 'dzone_refresh_token'
# VITE_BASE_URL = 'https://dzone-api.danakcorp.com' VITE_BASE_URL = 'https://api-dzone.danakcorp.com'
VITE_BASE_URL = 'http://192.168.1.113:4001' # VITE_BASE_URL = 'http://192.168.1.113:4001'
VITE_SLUG_ID = 'slug_id' VITE_SLUG_ID = 'slug_id'
+4 -8
View File
@@ -6,15 +6,11 @@ RUN apk add --no-cache tzdata
# Set the timezone to Asia/Tehran # Set the timezone to Asia/Tehran
RUN cp /usr/share/zoneinfo/Asia/Tehran /etc/localtime && echo "Asia/Tehran" > /etc/timezone 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 WORKDIR /build
COPY package*.json pnpm-lock.yaml* ./ COPY package*.json ./
RUN pnpm install --frozen-lockfile RUN npm ci --legacy-peer-deps
COPY . ./ COPY . ./
RUN pnpm run build RUN npm run build
FROM nginx:stable-alpine AS production-stage FROM nginx:stable-alpine AS production-stage
@@ -25,4 +21,4 @@ RUN cat /etc/nginx/conf.d/default.conf
EXPOSE 80 EXPOSE 80
CMD ["nginx", "-g", "daemon off;"] CMD ["nginx", "-g", "daemon off;"]
+43
View File
@@ -21,10 +21,13 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-dropzone": "^14.3.5", "react-dropzone": "^14.3.5",
"react-i18next": "^15.4.0", "react-i18next": "^15.4.0",
"react-infinite-scroll-component": "^6.1.0",
"react-loading": "^2.0.3", "react-loading": "^2.0.3",
"react-loading-skeleton": "^3.5.0",
"react-multi-date-picker": "^4.5.2", "react-multi-date-picker": "^4.5.2",
"react-otp-input": "^3.1.1", "react-otp-input": "^3.1.1",
"react-router-dom": "^7.1.3", "react-router-dom": "^7.1.3",
"react-spinners": "^0.17.0",
"react-toastify": "^11.0.3", "react-toastify": "^11.0.3",
"swiper": "^11.2.1", "swiper": "^11.2.1",
"tailwind-merge": "^2.6.0", "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": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -4407,6 +4422,15 @@
"react": ">=0.14.0" "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": { "node_modules/react-multi-date-picker": {
"version": "4.5.2", "version": "4.5.2",
"resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-4.5.2.tgz", "resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-4.5.2.tgz",
@@ -4479,6 +4503,16 @@
"react-dom": ">=18" "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": { "node_modules/react-toastify": {
"version": "11.0.3", "version": "11.0.3",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.3.tgz", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.3.tgz",
@@ -4964,6 +4998,15 @@
"node": ">=0.8" "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": { "node_modules/tiny-case": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
+4 -2
View File
@@ -23,10 +23,13 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-dropzone": "^14.3.5", "react-dropzone": "^14.3.5",
"react-i18next": "^15.4.0", "react-i18next": "^15.4.0",
"react-infinite-scroll-component": "^6.1.0",
"react-loading": "^2.0.3", "react-loading": "^2.0.3",
"react-loading-skeleton": "^3.5.0",
"react-multi-date-picker": "^4.5.2", "react-multi-date-picker": "^4.5.2",
"react-otp-input": "^3.1.1", "react-otp-input": "^3.1.1",
"react-router-dom": "^7.1.3", "react-router-dom": "^7.1.3",
"react-spinners": "^0.17.0",
"react-toastify": "^11.0.3", "react-toastify": "^11.0.3",
"swiper": "^11.2.1", "swiper": "^11.2.1",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
@@ -51,6 +54,5 @@
"typescript": "~5.6.2", "typescript": "~5.6.2",
"typescript-eslint": "^8.18.2", "typescript-eslint": "^8.18.2",
"vite": "^6.0.5" "vite": "^6.0.5"
}, }
"packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677"
} }
-4124
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -81,7 +81,7 @@ function App() {
// Clear tokens and redirect to login // Clear tokens and redirect to login
await removeToken(); await removeToken();
await removeRefreshToken(); await removeRefreshToken();
window.location.href = '/auth/login'; // window.location.href = '/auth/login';
} }
} }
} }
-43
View File
@@ -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
+63 -2
View File
@@ -1,7 +1,8 @@
import { FC, InputHTMLAttributes, useState } from 'react' import { FC, InputHTMLAttributes, useEffect, useState } from 'react'
import { clx } from '../helpers/utils'; import { clx } from '../helpers/utils';
import EyeIcon from '../assets/images/eye.svg' import EyeIcon from '../assets/images/eye.svg'
import { SearchNormal } from 'iconsax-react'; import { SearchNormal } from 'iconsax-react';
import Error from './Error';
type Variant = "floating_outlined" | "primary" | "search"; type Variant = "floating_outlined" | "primary" | "search";
@@ -14,11 +15,23 @@ type Props = {
unit?: string; unit?: string;
seprator?: boolean; seprator?: boolean;
isNotRequired?: boolean; isNotRequired?: boolean;
onChangeSearchFinal?: (value: string) => void;
} & InputHTMLAttributes<HTMLInputElement> } & 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 Input: FC<Props> = (props: Props) => {
const [formattedValue, setFormattedValue] = useState<string>(
props.value ? formatNumber(props.value as string) : ''
);
const [showPassword, setShowPassword] = useState<boolean>(false) const [showPassword, setShowPassword] = useState<boolean>(false)
const [search, setSearch] = useState<string>('')
const inputClass = clx( const inputClass = clx(
'w-full h-10 text-black block px-4 text-xs rounded-xl border border-border', '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 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 ( return (
<div className='w-full'> <div className='w-full'>
@@ -35,7 +86,10 @@ const Input: FC<Props> = (props: Props) => {
</label> </label>
<div className='w-full relative mt-1'> <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' && props.type === 'password' &&
@@ -46,6 +100,13 @@ const Input: FC<Props> = (props: Props) => {
props.variant === 'search' && props.variant === 'search' &&
<SearchNormal size={20} color='#8C90A3' className='absolute top-0 w-5 bottom-0 my-auto right-3' /> <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>
</div> </div>
) )
+13 -10
View File
@@ -5,7 +5,7 @@ import { Link } from 'react-router-dom'
type Props = { type Props = {
title: string, title: string,
link: string link?: string
} }
const TitleSeeAll: FC<Props> = (props: Props) => { const TitleSeeAll: FC<Props> = (props: Props) => {
@@ -17,15 +17,18 @@ const TitleSeeAll: FC<Props> = (props: Props) => {
<div> <div>
{props.title} {props.title}
</div> </div>
<Link to={props.link} className='flex items-center gap-2'> {
<div className='text-sm'> props.link &&
{t('home.see_all')} <Link to={props.link} className='flex items-center gap-2'>
</div> <div className='text-sm'>
<ArrowLeft {t('home.see_all')}
size={16} </div>
color='black' <ArrowLeft
/> size={16}
</Link> color='black'
/>
</Link>
}
</div> </div>
) )
} }
+1
View File
@@ -25,6 +25,7 @@ export const Pages = {
}, },
company: { company: {
detail: "company/", detail: "company/",
my: "company/my",
}, },
grade: { grade: {
records: "grade-records", records: "grade-records",
+4 -2
View File
@@ -8,7 +8,7 @@ import {
removeRefreshToken, removeRefreshToken,
} from "./func"; } from "./func";
import { refreshToken } from "../pages/auth/service/AuthService"; import { refreshToken } from "../pages/auth/service/AuthService";
// import { Pages } from "./Pages"; import { buildPath } from "../helpers/utils";
const axiosInstance = axios.create({ const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_BASE_URL, baseURL: import.meta.env.VITE_BASE_URL,
@@ -55,7 +55,9 @@ axiosInstance.interceptors.response.use(
console.error("Token refresh failed:", refreshError); console.error("Token refresh failed:", refreshError);
await removeToken(); await removeToken();
await removeRefreshToken(); 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); return Promise.reject(refreshError);
} finally { } finally {
window.isRefreshingToken = false; window.isRefreshingToken = false;
+25 -2
View File
@@ -141,7 +141,8 @@
"other_courses": "سایر دوره ها", "other_courses": "سایر دوره ها",
"my_services": "سرویس های من", "my_services": "سرویس های من",
"announcements": "اطلاعیه ها", "announcements": "اطلاعیه ها",
"tickets": "تیکت ها" "tickets": "تیکت ها",
"invoice": "صورتحساب ها"
}, },
"ticket": { "ticket": {
"tickets": "تیکت ها", "tickets": "تیکت ها",
@@ -354,7 +355,8 @@
"natification": "اعلان ها", "natification": "اعلان ها",
"add_notification": "درج اطلاعیه", "add_notification": "درج اطلاعیه",
"title": "عنوان", "title": "عنوان",
"group": "گروه" "group": "گروه",
"all_read": "همه خوانده شده"
}, },
"profile": { "profile": {
"account_user": "حساب کاربری", "account_user": "حساب کاربری",
@@ -418,5 +420,26 @@
"select_company": "انتخاب شرکت", "select_company": "انتخاب شرکت",
"register_date": "تاریخ ثبت نام", "register_date": "تاریخ ثبت نام",
"company": "شرکت" "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": " صورتحساب ها"
} }
} }
BIN
View File
Binary file not shown.
-6
View File
@@ -5,7 +5,6 @@ import { useNavigate, useParams } from 'react-router-dom'
import { ArrowLeft2 } from 'iconsax-react' import { ArrowLeft2 } from 'iconsax-react'
import { useGetAnnoncementDetail } from './hooks/useAnnoncementData' import { useGetAnnoncementDetail } from './hooks/useAnnoncementData'
import PageLoading from '../../components/PageLoading' import PageLoading from '../../components/PageLoading'
import { Helmet } from 'react-helmet-async'
const AnnouncementDetail: FC = () => { const AnnouncementDetail: FC = () => {
@@ -16,11 +15,6 @@ const AnnouncementDetail: FC = () => {
return ( return (
<div className='mt-4'> <div className='mt-4'>
<Helmet>
<title>
داناک | اطلاعیه
</title>
</Helmet>
<div className='flex justify-between'> <div className='flex justify-between'>
<div> <div>
{t('announcement.text_announcement')} {t('announcement.text_announcement')}
+4 -9
View File
@@ -2,17 +2,17 @@ import { FC, Fragment, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Input from '../../components/Input' import Input from '../../components/Input'
import StatusWithText from '../../components/StatusWithText' import StatusWithText from '../../components/StatusWithText'
import { clx } from '../../helpers/utils' import { clx, buildPath } from '../../helpers/utils'
import { useNavigate } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'
import { Pages } from '../../config/Pages' import { Pages } from '../../config/Pages'
import { useGetAnnoncement } from './hooks/useAnnoncementData' import { useGetAnnoncement } from './hooks/useAnnoncementData'
import { AnnoncementItemType } from './types/AnnoncementTypes' import { AnnoncementItemType } from './types/AnnoncementTypes'
import moment from 'moment-jalaali' import moment from 'moment-jalaali'
import { Helmet } from 'react-helmet-async'
import AnouncementSkeleton from './components/AnouncementSkeleton' import AnouncementSkeleton from './components/AnouncementSkeleton'
const AnnouncementtList: FC = () => { const AnnouncementtList: FC = () => {
const { slug } = useParams()
const navigate = useNavigate() const navigate = useNavigate()
const { t } = useTranslation('global') const { t } = useTranslation('global')
const [search, setSearch] = useState<string>('') const [search, setSearch] = useState<string>('')
@@ -21,7 +21,7 @@ const AnnouncementtList: FC = () => {
const handleChange = (item: AnnoncementItemType) => { const handleChange = (item: AnnoncementItemType) => {
if (window.innerWidth < 1280) { if (window.innerWidth < 1280) {
navigate(Pages.announcement.detail + item.announcement.id) navigate(buildPath(Pages.announcement.detail + item.announcement.id, slug))
} else { } else {
setItemSelected(item) setItemSelected(item)
} }
@@ -29,11 +29,6 @@ const AnnouncementtList: FC = () => {
return ( return (
<div className='mt-4 text-sm'> <div className='mt-4 text-sm'>
<Helmet>
<title>
داناک | اطلاعیه ها
</title>
</Helmet>
<div className='text-base'> <div className='text-base'>
{t('announcement.announcements')} {t('announcement.announcements')}
</div> </div>
+2 -10
View File
@@ -8,12 +8,11 @@ import { useAuthStore } from '../store/AuthStore'
import Error from '../../../components/Error' import Error from '../../../components/Error'
import Button from '../../../components/Button' import Button from '../../../components/Button'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Pages } from '../../../config/Pages'
import { isEmail } from '../../../config/func' import { isEmail } from '../../../config/func'
import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData' import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData'
import { ErrorType } from '../../../helpers/types' import { ErrorType } from '../../../helpers/types'
import { ArrowLeft } from 'iconsax-react'
import { toast } from '../../../components/Toast' import { toast } from '../../../components/Toast'
import { buildPath } from '../../../helpers/utils'
const LoginStep1: FC = () => { const LoginStep1: FC = () => {
const { t } = useTranslation('global') const { t } = useTranslation('global')
@@ -98,17 +97,10 @@ const LoginStep1: FC = () => {
<p className='text-description'> <p className='text-description'>
{t('auth.have_account')} {t('auth.have_account')}
</p> </p>
<Link to={Pages.auth.register}> <Link to={buildPath('auth/register')}>
<div>{t('auth.register')}</div> <div>{t('auth.register')}</div>
</Link> </Link>
</div> </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> </div>
) )
} }
-6
View File
@@ -7,7 +7,6 @@ import * as Yup from 'yup'
import { useAuthStore } from '../store/AuthStore' import { useAuthStore } from '../store/AuthStore'
import Error from '../../../components/Error' import Error from '../../../components/Error'
import Button from '../../../components/Button' import Button from '../../../components/Button'
import { Link } from 'react-router-dom'
import { Pages } from '../../../config/Pages' import { Pages } from '../../../config/Pages'
import ArrowLeftIcon from '../../../assets/images/arrow-left.svg' import ArrowLeftIcon from '../../../assets/images/arrow-left.svg'
import { useLoginWithPassword } from '../hooks/useAuthData' import { useLoginWithPassword } from '../hooks/useAuthData'
@@ -83,11 +82,6 @@ const LoginStep3: FC = () => {
} }
</div> </div>
<Link to={Pages.auth.forgotPassword}>
<div className='mt-4 flex justify-end text-sm'>
{t('auth.forgot_password')}
</div>
</Link>
<Button <Button
label={t('auth.login')} label={t('auth.login')}
+142
View File
@@ -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
View File
@@ -1,134 +1,164 @@
import { FC } from 'react' import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Input from '../../components/Input' import Input from '../../components/Input'
import Textarea from '../../components/Textarea' import Textarea from '../../components/Textarea'
import UploadBox from '../../components/UploadBox' import UploadBox from '../../components/UploadBox'
import Button from '../../components/Button' import Button from '../../components/Button'
import Select from '../../components/Select' import { TickCircle, TickSquare } from 'iconsax-react'
import { TickSquare } from 'iconsax-react' import { useMultiUpload } from '../ticket/hooks/useTicketData'
import { useFormik } from 'formik'
const AddCriticisms: FC = () => { import { CreateCriticismsTypes } from './types/CriticismsTypes'
import * as Yup from 'yup'
const { t } = useTranslation('global') import { toast } from '../../components/Toast'
import { ErrorType } from '../../helpers/types'
return ( import { useCreateCriticisms } from './hooks/useCriticismsData'
<div className='mt-4 text-sm'> const AddCriticisms: FC = () => {
<div>
{t('notif.add_notification')} const { t } = useTranslation('global')
</div> const [files, setFiles] = useState<File[]>([])
const multiUpload = useMultiUpload()
<div className='flex gap-6 xl:mt-8 mt-5'> const createCriticisms = useCreateCriticisms()
<div className='flex-1 bg-white py-8 xl:px-10 px-5 rounded-3xl'>
<div> const formik = useFormik<CreateCriticismsTypes>({
{t('criticisms.submit_your_criticisms')} initialValues: {
</div> content: '',
files: [],
<div className='mt-3 text-xs text-description'> title: '',
{t('criticisms.description_criticisms')} },
</div> validationSchema: Yup.object({
title: Yup.string().required(t('errors.required')),
<div className='mt-10'> content: Yup.string().required(t('errors.required')),
<Input }),
label={t('notif.title')} onSubmit: async (values) => {
placeholder={t('criticisms.enter_your_subject')} if (files.length > 0) {
/> const images = new FormData()
</div> files.forEach(file => {
images.append('files', file)
})
await multiUpload.mutateAsync(images, {
<div className='mt-7'> onSuccess: async (data) => {
<Select values.files = await data.data.map((item: { url: string }) => item?.url)
items={[ },
{ value: '1', label: 'گروه' }, onError: (error: ErrorType) => {
{ value: '2', label: 'فعال' }, toast(error.response?.data?.error?.message[0], 'error')
{ value: '3', label: 'غیر فعال' }, }
]} })
/>
</div> } else {
delete values.files
}
<div className='mt-7'> createCriticisms.mutate(values, {
<Select onSuccess: () => {
items={[ toast(t('success'), 'success')
{ value: '1', label: 'انتخاب زمان انتشار' }, formik.resetForm()
{ value: '2', label: 'فعال' }, setTimeout(() => {
{ value: '3', label: 'غیر فعال' }, window.location.reload()
]} }, 1000);
/> },
</div> onError: (error: ErrorType) => {
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
<div className='mt-7'> })
<Textarea
label={t('notif.group')} return (
placeholder={t('criticisms.enter_description')} <div className='mt-4 text-sm'>
/> <div>
</div> {t('criticisms.criticisms')}
</div>
<div className='mt-7'>
<UploadBox <div className='flex gap-6 xl:mt-8 mt-5'>
label={t('attachment')} <div className='flex-1 bg-white py-8 xl:px-10 px-5 rounded-3xl'>
/> <div>
</div> {t('criticisms.submit_your_criticisms')}
</div>
<div className='mt-20 flex justify-end'> <div className='mt-3 text-xs text-description'>
<Button {t('criticisms.description_criticisms')}
label={t('ticket.send')} </div>
className='max-w-[100px]'
/> <div className='mt-10'>
</div> <Input
</div> 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 className='bg-white w-sidebar hidden xl:block py-10 px-5 h-fit rounded-3xl'> </div>
<div className='text-sm'>
{t('ticket.title_hint')} <div className='mt-7'>
</div> <Textarea
<div className='mt-6'> label={t('criticisms.description')}
<div className='flex items-start gap-2 border-b pb-5'> placeholder={t('criticisms.enter_description')}
<div> {...formik.getFieldProps('content')}
<TickSquare size={20} color='black' variant='Bold' /> error_text={formik.touched.content && formik.errors.content ? formik.errors.content : ''}
</div> />
<div className='text-description text-xs leading-5'> </div>
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
</div> <div className='mt-7'>
</div> <UploadBox
<div className='flex items-start gap-2 mt-6 border-b pb-5'> label={t('attachment')}
<div> isMultiple
<TickSquare size={20} color='black' variant='Bold' /> onChange={setFiles}
</div> />
<div className='text-description text-xs leading-5'> </div>
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
</div>
</div> <div className='mt-20 flex justify-end'>
<div className='flex items-start gap-2 mt-6'> <Button
<div> className='xl:max-w-[100px]'
<TickSquare size={20} color='black' variant='Bold' /> onClick={() => formik.handleSubmit()}
</div> isLoading={createCriticisms.isPending || multiUpload.isPending}
<div className='text-description text-xs leading-5'> >
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است. <div className='flex gap-2 items-center'>
</div> <TickCircle size={20} color='white' />
</div> <div>
</div> {t('ticket.send')}
</div> </div>
</div>
</Button>
</div>
</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>
) <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 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[];
};
+4 -6
View File
@@ -2,7 +2,6 @@ import { Building, CloseCircle, InfoCircle, TickCircle } from 'iconsax-react'
import { FC, useState } from 'react' import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { buildPath, clx } from '../../helpers/utils' import { buildPath, clx } from '../../helpers/utils'
import StatusWithText from '../../components/StatusWithText'
import TitleSeeAll from '../../components/TitleSeeAll' import TitleSeeAll from '../../components/TitleSeeAll'
import { useGetCompanies, useGetIndustry, useGetCompanyRequest } from './hooks/useHomeData' import { useGetCompanies, useGetIndustry, useGetCompanyRequest } from './hooks/useHomeData'
import { CompanyItemType, IndustryItemType } from './types/HomeTypes' import { CompanyItemType, IndustryItemType } from './types/HomeTypes'
@@ -55,15 +54,15 @@ const Home: FC = () => {
</div> </div>
} }
<div className='mt-6'> {/* <div className='mt-6'>
<TitleSeeAll <TitleSeeAll
title={t('home.last_announcement')} title={t('home.last_announcement')}
link='/' link='/'
/> />
</div> </div> */}
<div className='mt-4'> {/* <div className='mt-4'>
<div className={clx( <div className={clx(
'p-4 rounded-2xl', 'p-4 rounded-2xl',
'bg-[#F8F9FB]' 'bg-[#F8F9FB]'
@@ -94,12 +93,11 @@ const Home: FC = () => {
</div> </div>
</div> </div>
</div> </div>
</div> </div> */}
<div className='mt-6'> <div className='mt-6'>
<TitleSeeAll <TitleSeeAll
title={t('home.companies')} title={t('home.companies')}
link='/'
/> />
</div> </div>
+9
View File
@@ -5,6 +5,7 @@ import {
getIndustry, getIndustry,
getCompanyDetail, getCompanyDetail,
getCompanyRequest, getCompanyRequest,
getMyCompany,
} from "../service/HomeService"; } from "../service/HomeService";
export const useGetSlugId = () => { export const useGetSlugId = () => {
@@ -39,5 +40,13 @@ export const useGetCompanyRequest = () => {
return useQuery({ return useQuery({
queryKey: ["company-request"], queryKey: ["company-request"],
queryFn: () => getCompanyRequest(), queryFn: () => getCompanyRequest(),
enabled: !!localStorage.getItem(import.meta.env.VITE_TOKEN_NAME),
});
};
export const useGetMyCompany = () => {
return useQuery({
queryKey: ["my-company"],
queryFn: () => getMyCompany(),
}); });
}; };
+5
View File
@@ -26,6 +26,11 @@ export const getCompanyDetail = async (id: string) => {
return data; return data;
}; };
export const getMyCompany = async () => {
const { data } = await axios.get(`/companies/user`);
return data;
};
export const getCompanyRequest = async () => { export const getCompanyRequest = async () => {
const { data } = await axios.get(`/companies/requests/user`); const { data } = await axios.get(`/companies/requests/user`);
return data; return data;
-85
View File
@@ -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
+165 -104
View File
@@ -1,113 +1,174 @@
import { Notification } from 'iconsax-react' import { Notification } from 'iconsax-react';
import { FC, useState } from 'react' import { FC, useState } from 'react';
import XIcon from '../../assets/images/close-circle.svg' import XIcon from '../../assets/images/close-circle.svg';
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next';
import { clx } from '../../helpers/utils' import { clx } from '../../helpers/utils';
import StatusCircle from '../../components/StatusCircle' import StatusCircle from '../../components/StatusCircle';
import { useOutsideClick } from '../../hooks/useOutSideClick' 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 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 handleAllRead = () => {
const ref = useOutsideClick(() => setShowModal(false)) readAll.mutate(undefined, {
const [activeTab, setActiveTab] = useState<'all' | 'read' | 'unread'>('all') onSuccess: () => {
const [showModal, setShowModal] = useState<boolean>(false) 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 ( return (
<div ref={ref}> <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 && (
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='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 className='flex justify-between items-center'> <div>{t('notif.natification')}</div>
<div> <div className="size-7 rounded-full bg-white bg-opacity-35 flex justify-center items-center">
{t('notif.natification')} <img src={XIcon} alt="close" className="w-4 h-4" onClick={() => setShowModal(false)} />
</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>
</div> </div>
</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",
}
+114
View File
@@ -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
-102
View File
@@ -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
+25 -33
View File
@@ -2,22 +2,18 @@ import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { useApplyDiscount, useGetInvoiceDetail, useRemoveDiscount } from './hooks/useReceiptData' 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 { NumberFormat } from '../../config/func'
import moment from 'moment-jalaali' 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 { clx } from '../../helpers/utils'
import Button from '../../components/Button' import Button from '../../components/Button'
import ApproveInvoice from './components/ApproveInvoice' import ApproveInvoice from './components/ApproveInvoice'
import PayInvoice from './components/PayInvoice' import PayInvoice from './components/PayInvoice'
import Input from '../../components/Input' import Input from '../../components/Input'
import { useGetProfile } from '../profile/hooks/useProfileData'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { ErrorType } from '../../helpers/types' import { ErrorType } from '../../helpers/types'
// import PDFGenerator from '../../components/PDFGenerator' // import PDFGenerator from '../../components/PDFGenerator'
// import { useGetGetWays } from '../wallet/hooks/useWalletData' import { useGetGetWays } from './hooks/useReceiptData'
// import { GatewayItemType } from '../wallet/types/WalletTypes' // import { GatewayItemType } from '../wallet/types/WalletTypes'
const ReceiptsDetail: FC = () => { const ReceiptsDetail: FC = () => {
@@ -26,11 +22,10 @@ const ReceiptsDetail: FC = () => {
const { t } = useTranslation('global') const { t } = useTranslation('global')
const [code, setCode] = useState<string>('') const [code, setCode] = useState<string>('')
const [gateWayId, setGateWayId] = useState<string>('') const [gateWayId, setGateWayId] = useState<string>('')
const getProfile = useGetProfile()
const getInvoce = useGetInvoiceDetail(id ? id : '') const getInvoce = useGetInvoiceDetail(id ? id : '')
const applyDiscount = useApplyDiscount() const applyDiscount = useApplyDiscount()
const removeDiscount = useRemoveDiscount() const removeDiscount = useRemoveDiscount()
// const getWays = useGetGetWays() const getWays = useGetGetWays()
const handleApplyDiscount = () => { const handleApplyDiscount = () => {
const params: ApplyDiscountType = { const params: ApplyDiscountType = {
@@ -60,13 +55,13 @@ const ReceiptsDetail: FC = () => {
}) })
} }
// useEffect(() => { useEffect(() => {
// if (getWays.data) { if (getWays.data) {
// setGateWayId(getWays.data?.data?.paymentGateways?.[0]?.id) setGateWayId(getWays.data?.data?.paymentGateways?.[0]?.id)
// } }
// }, [getWays.data]) }, [getWays.data])
return ( return (
@@ -299,26 +294,23 @@ const ReceiptsDetail: FC = () => {
/> />
</div> </div>
} }
{/* { <div className='flex mt-6 justify-center gap-2'>
getInvoce.data?.data?.remainingToCharge > 0 && {
<div className='flex mt-6 justify-center gap-2'> getWays.data?.data?.paymentGateways?.map((item: GatewayItemType) => {
{ return (
getWays.data?.data?.paymentGateways?.map((item: GatewayItemType) => { <div key={item.id} className={clx(
return ( 'size-[72px] rounded-xl border border-border flex flex-col justify-center items-center',
<div key={item.id} className={clx( gateWayId === item.id && 'border-black'
'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]'>
<img src={item.logoUrl} className='w-8' /> {item.name}
<div className='mt-1 text-[10px]'> </div>
{item.name} </div>
</div> )
</div> })
) }
}) </div>
}
</div>
} */}
</div> </div>
+2 -2
View File
@@ -10,7 +10,7 @@ import { NumberFormat } from '../../config/func'
import { ReceiptItemType } from './types/ReceiptTypes' import { ReceiptItemType } from './types/ReceiptTypes'
import moment from 'moment-jalaali' import moment from 'moment-jalaali'
import { InvoiceStatus } from './types/ReceiptTypes' import { InvoiceStatus } from './types/ReceiptTypes'
import { buildPath } from '../../helpers/utils'
const ReceiptsList: FC = () => { const ReceiptsList: FC = () => {
@@ -111,7 +111,7 @@ const ReceiptsList: FC = () => {
<Td text={t(`receip.${item.status}`)} /> <Td text={t(`receip.${item.status}`)} />
<Td text={item.status === 'PAID' ? moment(item.paidAt).format('jYYYY-jMM-jDD') : t('receip.not_paid')} /> <Td text={item.status === 'PAID' ? moment(item.paidAt).format('jYYYY-jMM-jDD') : t('receip.not_paid')} />
<Td text={''}> <Td text={''}>
<Link to={Pages.receipts.detail + item.id}> <Link to={buildPath(Pages.receipts.detail + item.id)}>
<Eye size={20} color='black' /> <Eye size={20} color='black' />
</Link> </Link>
</Td> </Td>
-118
View File
@@ -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
+14 -13
View File
@@ -1,7 +1,7 @@
import { FC, Fragment, useState } from 'react' import { FC, Fragment, useState } from 'react'
import Button from '../../../components/Button' import Button from '../../../components/Button'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useGetInvoiceDetail, usePayInvoice } from '../hooks/useReceiptData' import { useGetInvoiceDetail, usePayInvoice, usePayment } from '../hooks/useReceiptData'
import { ErrorType } from '../../../helpers/types' import { ErrorType } from '../../../helpers/types'
import ModalConfrim from '../../../components/ModalConfrim' import ModalConfrim from '../../../components/ModalConfrim'
// import { usePayment } from '../../wallet/hooks/useWalletData' // import { usePayment } from '../../wallet/hooks/useWalletData'
@@ -19,7 +19,7 @@ const PayInvoice: FC<Props> = ({ id, refetch, gateWayId }) => {
const [showModal, setShowModal] = useState<boolean>(false) const [showModal, setShowModal] = useState<boolean>(false)
const getInvoce = useGetInvoiceDetail(id) const getInvoce = useGetInvoiceDetail(id)
const payInvoice = usePayInvoice() const payInvoice = usePayInvoice()
// const payment = usePayment() const payment = usePayment()
const handleConfrim = () => { const handleConfrim = () => {
@@ -36,22 +36,23 @@ const PayInvoice: FC<Props> = ({ id, refetch, gateWayId }) => {
} }
const handleChartWallet = () => { const handleChartWallet = () => {
// payment.mutate({ amount: getInvoce.data?.data?.remainingToCharge, gatewayId: gateWayId, invoiceId: id }, { payment.mutate({ gatewayId: gateWayId, invoiceId: id }, {
// onSuccess: (data) => { onSuccess: (data) => {
// window.location.href = data?.data?.redirectUrl window.location.href = data?.data?.redirectUrl
// toast(t('wallet.transfer_to_getway'), 'success') toast(t('wallet.transfer_to_getway'), 'success')
// }, },
// onError: (error: ErrorType) => { onError: (error: ErrorType) => {
// toast(error.response?.data?.error.message[0], 'error') toast(error.response?.data?.error.message[0], 'error')
// } }
// }) })
} }
return ( return (
<Fragment> <Fragment>
<Button <Button
label={getInvoce.data?.data?.remainingToCharge > 0 ? t('receip.charge_wallet') : t('receip.pay')} label={t('receip.pay')}
onClick={() => setShowModal(true)} onClick={handleChartWallet}
isLoading={payment.isPending}
/> />
<ModalConfrim <ModalConfrim
-107
View File
@@ -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
+2 -13
View File
@@ -1,13 +1,9 @@
import { FC, Fragment } from 'react' import { FC, Fragment } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FinancialDataType } from '../../financial/types/FinancialTypes'
import { InfoCircle } from 'iconsax-react' import { InfoCircle } from 'iconsax-react'
import Button from '../../../components/Button'
import { Link } from 'react-router-dom'
import { Pages } from '../../../config/Pages'
type Props = { type Props = {
data: FinancialDataType data: any
} }
const RegisterInfo: FC<Props> = ({ data }) => { 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='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>
<div className='mt-8 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'> <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='flex gap-2'>
@@ -3,6 +3,7 @@ import * as api from "../service/ReceiptService";
import { import {
ApplyDiscountType, ApplyDiscountType,
ApproveInvoiceType, ApproveInvoiceType,
PaymentType,
RemoveDiscountType, RemoveDiscountType,
RequestApproveInvoiceType, RequestApproveInvoiceType,
} from "../types/ReceiptTypes"; } from "../types/ReceiptTypes";
@@ -60,3 +61,16 @@ export const useRemoveDiscount = () => {
api.removeDiscount(variables.id), 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 { import {
ApplyDiscountType, ApplyDiscountType,
ApproveInvoiceType, ApproveInvoiceType,
PaymentType,
RequestApproveInvoiceType, RequestApproveInvoiceType,
} from "../types/ReceiptTypes"; } from "../types/ReceiptTypes";
@@ -45,3 +46,13 @@ export const removeDiscount = async (id: string) => {
const { data } = await axios.post(`/invoices/${id}/cancel-discount`); const { data } = await axios.post(`/invoices/${id}/cancel-discount`);
return data; 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;
};
+17
View File
@@ -58,3 +58,20 @@ export type ApplyDiscountType = {
export type RemoveDiscountType = { export type RemoveDiscountType = {
id: string; 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;
};
BIN
View File
Binary file not shown.
-123
View File
@@ -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
-177
View File
@@ -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
View File
@@ -9,12 +9,10 @@ import Footer from "../shared/Footer"
import GradeRecordes from "../pages/grade/records" import GradeRecordes from "../pages/grade/records"
import Certificate from "../pages/certificate/List" import Certificate from "../pages/certificate/List"
import AdmitCard from "../pages/admitCard/List" import AdmitCard from "../pages/admitCard/List"
import MyCourses from "../pages/my-courses/List" import Payment from "../pages/payment/CallBack"
import Payment from "../pages/payment/Details"
import Wallet from "../pages/wallet/Wallet" import Wallet from "../pages/wallet/Wallet"
import ReceiptsDetail from "../pages/receipts/Detail" import ReceiptsDetail from "../pages/receipts/Detail"
import ReportCardList from "../pages/ReportCard/List" import ReportCardList from "../pages/ReportCard/List"
import TransactionList from "../pages/transaction/List"
import Profile from "../pages/profile/Profile" import Profile from "../pages/profile/Profile"
import CompanyDetail from "../pages/company/Detail" import CompanyDetail from "../pages/company/Detail"
import { useEffect } from "react" import { useEffect } from "react"
@@ -25,6 +23,9 @@ import Request from "../pages/request/Request"
import TicketList from "../pages/ticket/TicketList" import TicketList from "../pages/ticket/TicketList"
import TicketCreate from "../pages/ticket/CreateTicket" import TicketCreate from "../pages/ticket/CreateTicket"
import TicketDetail from "../pages/ticket/Detail" 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 MainRouter = () => {
const { slug } = useParams() const { slug } = useParams()
const getSlugId = useGetSlugId() const getSlugId = useGetSlugId()
@@ -43,7 +44,7 @@ const MainRouter = () => {
}, },
onError: (error: ErrorType) => { onError: (error: ErrorType) => {
toast.error(error.response?.data?.error.message[0]) 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="dashboard" element={<Home />} />
<Route path="receipts" element={<ReceiptsList />} /> <Route path="receipts" element={<ReceiptsList />} />
<Route path="receipts/:id" element={<ReceiptsDetail />} /> <Route path="receipts/:id" element={<ReceiptsDetail />} />
<Route path="company/my" element={<MyCompany />} />
<Route path="company/:id" element={<CompanyDetail />} /> <Route path="company/:id" element={<CompanyDetail />} />
<Route path="criticisms" element={<AddCriticisms />} /> <Route path="criticisms" element={<AddCriticisms />} />
<Route path="grade/records" element={<GradeRecordes />} /> <Route path="grade/records" element={<GradeRecordes />} />
<Route path="transactions" element={<TransactionList />} />
<Route path="certificate" element={<Certificate />} /> <Route path="certificate" element={<Certificate />} />
<Route path="admitCard" element={<AdmitCard />} /> <Route path="admitCard" element={<AdmitCard />} />
<Route path="myCourses" element={<MyCourses />} />
<Route path="payment" element={<Payment />} /> <Route path="payment" element={<Payment />} />
<Route path="wallet" element={<Wallet />} /> <Route path="wallet" element={<Wallet />} />
<Route path="reportCard/list" element={<ReportCardList />} /> <Route path="reportCard/list" element={<ReportCardList />} />
@@ -77,6 +77,8 @@ const MainRouter = () => {
<Route path="tickets" element={<TicketList />} /> <Route path="tickets" element={<TicketList />} />
<Route path="tickets/create" element={<TicketCreate />} /> <Route path="tickets/create" element={<TicketCreate />} />
<Route path="tickets/messages/:id" element={<TicketDetail />} /> <Route path="tickets/messages/:id" element={<TicketDetail />} />
<Route path="announcement" element={<AnnouncementtList />} />
<Route path="announcement/:id" element={<AnnouncementDetail />} />
</Routes> </Routes>
</div> </div>
</div> </div>
+25 -16
View File
@@ -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 { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Pages } from '../config/Pages' import { Pages } from '../config/Pages'
import { Link, useParams } from 'react-router-dom' import { Link, useParams, useLocation } from 'react-router-dom'
const Footer: FC = () => { const Footer: FC = () => {
const { t } = useTranslation('global') const { t } = useTranslation('global')
const { slug } = useParams() const { slug } = useParams()
const location = useLocation()
const isActive = (path: string) => {
return location.pathname.includes(path)
}
return ( return (
<div className='xl:hidden'> <div className='xl:hidden'>
<div className='h-[60px] '></div> <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'> <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}`}> <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 <Home2
className='size-5' className='size-5'
color='#8C90A3' color={isActive(Pages.dashboard) ? 'black' : '#8C90A3'}
variant={isActive(Pages.dashboard) ? 'Bold' : 'Linear'}
/> />
<div className='text-[10px]'> <div className='text-[10px]'>
{t('footer.home')} {t('footer.home')}
</div> </div>
</div> </div>
</Link> </Link>
<Link to={`/${slug}/${Pages.services.other}`}> <Link to={`/${slug}/${Pages.receipts.index}`}>
<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.receipts.index) ? 'text-black' : 'text-description'}`}>
<Buildings2 <Receipt21
className='size-5' className='size-5'
color='#8C90A3' color={isActive(Pages.receipts.index) ? 'black' : '#8C90A3'}
variant={isActive(Pages.receipts.index) ? 'Bold' : 'Linear'}
/> />
<div className='text-[10px]'> <div className='text-[10px]'>
{t('footer.companies')} {t('footer.invoice')}
</div> </div>
</div> </div>
</Link> </Link>
<Link to={`/${slug}/${Pages.services.mine}`}> <Link to={`/${slug}/${Pages.company.my}`}>
<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.company.my) ? 'text-black' : 'text-description'}`}>
<div className='bg-white p-1 rounded-full -mt-7'> <div className='bg-white p-1 rounded-full -mt-7'>
<div className='bg-black flex justify-center items-center size-10 rounded-full'> <div className='bg-black flex justify-center items-center size-10 rounded-full'>
<Buildings <Buildings
@@ -45,16 +52,17 @@ const Footer: FC = () => {
/> />
</div> </div>
</div> </div>
<div className='text-[10px] '> <div className='text-[10px]'>
{t('footer.my_company')} {t('footer.my_company')}
</div> </div>
</div> </div>
</Link> </Link>
<Link to={`/${slug}/${Pages.announcement.list}`}> <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 <NotificationStatus
className='size-5' className='size-5'
color='#8C90A3' color={isActive(Pages.announcement.list) ? 'black' : '#8C90A3'}
variant={isActive(Pages.announcement.list) ? 'Bold' : 'Linear'}
/> />
<div className='text-[10px]'> <div className='text-[10px]'>
{t('footer.announcements')} {t('footer.announcements')}
@@ -63,10 +71,11 @@ const Footer: FC = () => {
</Link> </Link>
<Link to={`/${slug}/${Pages.ticket.list}`}> <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 <Messages3
className='size-5' className='size-5'
color='#8C90A3' color={isActive(Pages.ticket.list) ? 'black' : '#8C90A3'}
variant={isActive(Pages.ticket.list) ? 'Bold' : 'Linear'}
/> />
<div className='text-[10px]'> <div className='text-[10px]'>
{t('footer.tickets')} {t('footer.tickets')}
+66 -21
View File
@@ -1,21 +1,31 @@
import { FC } from 'react' import { FC, useEffect, useState } from 'react'
import Input from '../components/Input' import Input from '../components/Input'
import { ArrowDown2, Element3, HambergerMenu, Wallet } from 'iconsax-react' import { ArrowDown2, CloseCircle, HambergerMenu, Logout } from 'iconsax-react'
import AvatarImage from '../assets/images/Avatar.png' import AvatarImage from '../assets/images/avatar_image.png'
import { useTranslation } from 'react-i18next' 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 { Pages } from '../config/Pages'
import Notifications from '../pages/notification/Notification' import Notifications from '../pages/notification/Notification'
import { useSharedStore } from './store/sharedStore' 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 Header: FC = () => {
const location = useLocation();
const [popoverKey, setPopoverKey] = useState(0);
const { t } = useTranslation('global') const { t } = useTranslation('global')
const { setOpenSidebar, openSidebar } = useSharedStore() const { setOpenSidebar, openSidebar } = useSharedStore()
const { slug } = useParams() const { data } = useGetProfile()
useEffect(() => {
setPopoverKey((prevKey) => prevKey + 1);
}, [location.pathname]);
return ( 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='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'> <div className='min-w-[270px] hidden xl:block'>
<Input <Input
variant='search' variant='search'
@@ -26,23 +36,58 @@ const Header: FC = () => {
<HambergerMenu size={24} color='black' /> <HambergerMenu size={24} color='black' />
</div> </div>
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */} {/* <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'> <div className='flex xl:gap-6 gap-4 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>
<Notifications /> <Notifications />
<div className='flex gap-2 items-center'> {
<div className='size-7 rounded-full bg-description overflow-hidden'> data && (
<Link to={`/${slug}/${Pages.profile}`}> <Popover className="relative" key={popoverKey}>
<img src={AvatarImage} className='size-full object-cover' /> <PopoverButton >
</Link> <div className='flex gap-2 items-center mt-2.5'>
</div> <div className='size-6 rounded-full bg-description overflow-hidden'>
<div className='xl:flex hidden gap-1 items-center'> <img src={data?.data?.user?.profilePic ? data?.data?.user?.profilePic : AvatarImage} className='size-full object-cover' />
<div className='text-xs'>مهرداد مظفری</div> </div>
<ArrowDown2 size={14} color='#8C90A3' /> <div className='xl:flex hidden gap-1 items-center'>
</div> <div className='text-xs'>
</div> {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>
</div> </div>
) )
+2 -17
View File
@@ -1,7 +1,7 @@
import { FC } from 'react' import { FC } from 'react'
import LogoImage from '../assets/images/logo.svg' import LogoImage from '../assets/images/logo.svg'
import { useTranslation } from 'react-i18next' 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 SideBarItem from './SideBarItem'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Pages } from '../config/Pages' import { Pages } from '../config/Pages'
@@ -55,14 +55,6 @@ const SideBar: FC = () => {
link={Pages.receipts.index} 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>
<div className='mt-10 px-12 text-header text-sm font-normal'> <div className='mt-10 px-12 text-header text-sm font-normal'>
{t('sidebar.other')} {t('sidebar.other')}
@@ -91,14 +83,6 @@ const SideBar: FC = () => {
link={Pages.criticisms} link={Pages.criticisms}
/> />
</div> </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='flex-1 items-end mt-14'>
<div className='text-xs text-[#8C90A3]'> <div className='text-xs text-[#8C90A3]'>
<SideBarItem <SideBarItem
@@ -114,6 +98,7 @@ const SideBar: FC = () => {
title={t('sidebar.logout')} title={t('sidebar.logout')}
isActive={isActive('logout')} isActive={isActive('logout')}
link={`#`} link={`#`}
isLogout
/> />
</div> </div>
</div> </div>
+22 -18
View File
@@ -1,43 +1,47 @@
import { FC, ReactNode } from 'react' import { FC, ReactNode } from 'react'
import { Link, useParams } from 'react-router-dom' import { Link } from 'react-router-dom'
import { clx } from '../helpers/utils' import { buildPath, clx } from '../helpers/utils'
import { Pages } from '../config/Pages' import { Pages } from '../config/Pages'
import { useLogout } from '../pages/auth/hooks/useAuthData'
import { removeToken } from '../config/func'
import { removeRefreshToken } from '../config/func'
type Props = { type Props = {
icon: ReactNode, icon: ReactNode,
title: string, title: string,
isActive: boolean, isActive: boolean,
link: string, // مقدار مثل: "dashboard" یا "wallet" link: string,
isLogout?: boolean, isLogout?: boolean,
isWithoutLine?: boolean
} }
const SideBarItem: FC<Props> = (props: Props) => { const SideBarItem: FC<Props> = (props: Props) => {
const { slug } = useParams(); // دریافت slug از URL
const fullLink = props.isLogout ? Pages.auth.login : `/${slug}/${props.link.replace(/^\//, '')}`;
const handleLogout = () => { const logout = useLogout()
localStorage.removeItem(import.meta.env.VITE_TOKEN_NAME)
const handleLogout = async () => {
await logout.mutateAsync()
removeToken()
removeRefreshToken()
window.location.href = Pages.auth.login window.location.href = Pages.auth.login
} }
// alert(slug)
return ( return (
<Link <Link onClick={props.isLogout ? handleLogout : undefined} to={buildPath(props.link)} className='flex text-xs gap-9 mt-4'>
onClick={props.isLogout ? handleLogout : undefined} {
to={fullLink} !props.isWithoutLine &&
className='flex text-xs gap-9 mt-4' <div className={clx(
> 'w-1 bg-black h-6',
<div className={clx( !props.isActive && 'invisible'
'w-1 bg-black h-6', )}></div>
!props.isActive && 'invisible' }
)}></div>
<div className='flex gap-3 items-center'> <div className='flex gap-3 items-center'>
{props.icon} {props.icon}
<div className={props.isActive ? 'text-black' : ''}> <div className={props.isActive ? 'text-black' : ''}>
{props.title} {props.title}
</div> </div>
</div> </div>
</Link> </Link>
) )
} }