landing + single company + auth
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
VITE_TOKEN_NAME = 'dzone_token'
|
||||
VITE_REFRESH_TOKEN_NAME = 'dzone_refresh_token'
|
||||
VITE_BASE_URL = 'https://dzone-api.danakcorp.com'
|
||||
# VITE_BASE_URL = 'http://192.168.1.109:4000'
|
||||
# VITE_BASE_URL = 'https://dzone-api.danakcorp.com'
|
||||
VITE_BASE_URL = 'http://192.168.1.113:4001'
|
||||
|
||||
VITE_SLUG_ID = 'slug_id'
|
||||
Generated
+61
@@ -16,12 +16,14 @@
|
||||
"i18next": "^24.2.1",
|
||||
"iconsax-react": "^0.0.8",
|
||||
"js-cookie": "^3.0.5",
|
||||
"moment-jalaali": "^0.10.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-i18next": "^15.4.0",
|
||||
"react-loading": "^2.0.3",
|
||||
"react-multi-date-picker": "^4.5.2",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"react-router-dom": "^7.1.3",
|
||||
"react-toastify": "^11.0.3",
|
||||
"swiper": "^11.2.1",
|
||||
@@ -32,6 +34,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/moment-jalaali": "^0.7.9",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/swiper": "^5.4.3",
|
||||
@@ -1864,6 +1867,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/moment-jalaali": {
|
||||
"version": "0.7.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/moment-jalaali/-/moment-jalaali-0.7.9.tgz",
|
||||
"integrity": "sha512-gsDOoAzRnCfQTbfdlUrCvX6R0wIto6CvwfvV2C3j4qJLK+DEiTK8Rl/xlOCBO9C6qeUfX8oyZ2UfjnXJTOvHSA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"moment": ">=2.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||
@@ -3516,6 +3529,12 @@
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jalaali-js": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/jalaali-js/-/jalaali-js-1.2.8.tgz",
|
||||
"integrity": "sha512-Jl/EwY84JwjW2wsWqeU4pNd22VNQ7EkjI36bDuLw31wH98WQW4fPjD0+mG7cdCK+Y8D6s9R3zLiQ3LaKu6bD8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "1.21.7",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||
@@ -3776,6 +3795,38 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/moment-jalaali": {
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/moment-jalaali/-/moment-jalaali-0.10.4.tgz",
|
||||
"integrity": "sha512-/eD0HeyvATznb5iE0G1BHjKRZAFEpJ9ZNUkcHwXhNgt1WJJVVzHD7+uDmqzZWVFLdbGme2gvIXKb3ezDYOXcZA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jalaali-js": "^1.2.7",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.46"
|
||||
}
|
||||
},
|
||||
"node_modules/moment-timezone": {
|
||||
"version": "0.5.48",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
|
||||
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"moment": "^2.29.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -4370,6 +4421,16 @@
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-otp-input": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-otp-input/-/react-otp-input-3.1.1.tgz",
|
||||
"integrity": "sha512-bjPavgJ0/Zmf/AYi4onj8FbH93IjeD+e8pWwxIJreDEWsU1ILR5fs8jEJmMGWSBe/yyvPP6X/W6Mk9UkOCkTPw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.6 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": ">=16.8.6 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"i18next": "^24.2.1",
|
||||
"iconsax-react": "^0.0.8",
|
||||
"js-cookie": "^3.0.5",
|
||||
"moment-jalaali": "^0.10.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.3.5",
|
||||
@@ -35,6 +36,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/moment-jalaali": "^0.7.9",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/swiper": "^5.4.3",
|
||||
|
||||
+8
-36
@@ -1,18 +1,16 @@
|
||||
import MainRouter from './router/Main'
|
||||
import i18next from 'i18next'
|
||||
import { I18nextProvider } from 'react-i18next'
|
||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
||||
import 'swiper/swiper-bundle.css';
|
||||
import FaJson from './langs/fa.json'
|
||||
import { useState } from 'react';
|
||||
import { Pages } from './config/Pages';
|
||||
import { getRefreshToken, getToken, removeRefreshToken, removeToken, setRefreshToken, setToken } from './config/func';
|
||||
import { useEffect } from 'react';
|
||||
import AuthRouter from './router/Auth';
|
||||
import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { IApiErrorRepsonse } from './types/error.types';
|
||||
import { refreshToken } from './pages/auth/service/AuthService';
|
||||
import ToastContainer from './components/Toast';
|
||||
import { getRefreshToken, removeRefreshToken, removeToken, setRefreshToken, setToken } from './config/func';
|
||||
|
||||
i18next.init({
|
||||
interpolation: { escapeValue: false },
|
||||
lng: 'fa',
|
||||
@@ -30,23 +28,6 @@ declare global {
|
||||
}
|
||||
|
||||
function App() {
|
||||
|
||||
const [isLogin, setIsLogin] = useState<'checking' | 'isLogin' | 'isNotLogin'>('checking')
|
||||
useEffect(() => {
|
||||
const checkLogin = async () => {
|
||||
const token = await getToken();
|
||||
if (token || true) {
|
||||
setIsLogin('isLogin')
|
||||
} else {
|
||||
setIsLogin('isNotLogin')
|
||||
if (window.location.href.split('auth').length === 1) {
|
||||
window.location.href = Pages.auth.login
|
||||
}
|
||||
}
|
||||
}
|
||||
checkLogin();
|
||||
}, []);
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
queryCache: new QueryCache({
|
||||
onError: async (error: IApiErrorRepsonse) => {
|
||||
@@ -105,7 +86,6 @@ function App() {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false
|
||||
// staleTime: 86400000 // 1 day in milliseconds
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -115,19 +95,10 @@ function App() {
|
||||
<BrowserRouter>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<I18nextProvider i18n={i18next}>
|
||||
{
|
||||
isLogin === 'checking' ?
|
||||
null
|
||||
:
|
||||
isLogin === 'isLogin' ?
|
||||
<Routes>
|
||||
<Route path="/:slug/*" element={<MainRouter />} />
|
||||
<Route path="/" element={<Navigate to="/fa/dashboard" replace />} />
|
||||
<Route path="/auth/*" element={<AuthRouter />} />
|
||||
</Routes>
|
||||
:
|
||||
<AuthRouter />
|
||||
}
|
||||
<Routes>
|
||||
<Route path="/:slug/auth/*" element={<AuthRouter />} />
|
||||
<Route path="/:slug/*" element={<MainRouter />} />
|
||||
</Routes>
|
||||
<ToastContainer />
|
||||
</I18nextProvider>
|
||||
</QueryClientProvider>
|
||||
@@ -135,4 +106,5 @@ function App() {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
||||
+3
-4
@@ -1,8 +1,7 @@
|
||||
export const Pages = {
|
||||
auth: {
|
||||
login: "auth/login",
|
||||
register: "auth/register",
|
||||
forgotPassword: "auth/forgot",
|
||||
login: "login",
|
||||
register: "register",
|
||||
},
|
||||
dashboard: "dashboard",
|
||||
services: {
|
||||
@@ -25,7 +24,7 @@ export const Pages = {
|
||||
detail: "announcement/",
|
||||
},
|
||||
company: {
|
||||
detail: "company/detail/",
|
||||
detail: "company/",
|
||||
},
|
||||
grade: {
|
||||
records: "grade-records",
|
||||
|
||||
+3
-1
@@ -16,8 +16,10 @@ axiosInstance.interceptors.response.use(
|
||||
axiosInstance.interceptors.request.use(async function (config) {
|
||||
const tokenValue = await getToken();
|
||||
const token = tokenValue ? tokenValue : "";
|
||||
|
||||
const slug = localStorage.getItem(import.meta.env.VITE_SLUG_ID);
|
||||
const slug_parse = slug ? JSON.parse(slug) : null;
|
||||
config.headers.Authorization = "Bearer " + token;
|
||||
config.headers["x-business-id"] = slug_parse?.danakSubscriptionId;
|
||||
return config;
|
||||
});
|
||||
|
||||
|
||||
+2
-3
@@ -32,7 +32,7 @@ export const timeAgo = (date: string | Date): string => {
|
||||
return "همین الان";
|
||||
};
|
||||
|
||||
export const getToken = () => {
|
||||
export const getToken = async () => {
|
||||
return Cookie.get(import.meta.env.VITE_TOKEN_NAME);
|
||||
};
|
||||
|
||||
@@ -40,8 +40,7 @@ export const getRefreshToken = () => {
|
||||
return Cookie.get(import.meta.env.VITE_REFRESH_TOKEN_NAME);
|
||||
};
|
||||
|
||||
const isProduction = import.meta.env.MODE === "production";
|
||||
const domain = isProduction ? ".danakcorp.com" : undefined;
|
||||
const domain = window.location.hostname;
|
||||
|
||||
export const setToken = (token: string) => {
|
||||
Cookie.set(import.meta.env.VITE_TOKEN_NAME, token, { domain });
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
/**
|
||||
* Concatenates truthy classes into a space-separated string.
|
||||
@@ -9,3 +10,12 @@ import { twMerge } from "tailwind-merge";
|
||||
export const clx = (...classes: (string | boolean | undefined)[]): string => {
|
||||
return twMerge(classes.filter(Boolean).join(" "));
|
||||
};
|
||||
|
||||
export const buildPath = (path: string, slug2?: string): string => {
|
||||
if (slug2) {
|
||||
return `/${slug2}/${path}`;
|
||||
} else {
|
||||
const { slug } = useParams() as { slug: string };
|
||||
return `/${slug}/${path}`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { FC, useEffect } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { getToken } from '../config/func'
|
||||
import { buildPath } from '../helpers/utils'
|
||||
|
||||
// تایپ برای کامپوننتهای React
|
||||
type ComponentType = FC<any>
|
||||
|
||||
// HOC برای محافظت از کامپوننتهای نیازمند لاگین
|
||||
const withAuth = (WrappedComponent: ComponentType): ComponentType => {
|
||||
// کامپوننت جدید که لاگین را چک میکند
|
||||
const WithAuthComponent: FC<any> = (props) => {
|
||||
const navigate = useNavigate()
|
||||
const { slug } = useParams()
|
||||
|
||||
useEffect(() => {
|
||||
const checkAuth = async () => {
|
||||
const token = await getToken()
|
||||
if (!token) {
|
||||
const loginPath = buildPath('auth/login', slug)
|
||||
navigate(loginPath)
|
||||
}
|
||||
}
|
||||
|
||||
checkAuth()
|
||||
}, [navigate, slug])
|
||||
|
||||
// کامپوننت اصلی را با props خودش رندر کن
|
||||
return <WrappedComponent {...props} />
|
||||
}
|
||||
|
||||
// نام نمایشی کامپوننت را برای دیباگ بهتر تنظیم کن
|
||||
WithAuthComponent.displayName = `WithAuth(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`
|
||||
|
||||
return WithAuthComponent
|
||||
}
|
||||
|
||||
export default withAuth
|
||||
@@ -12,6 +12,7 @@ import { Pages } from '../../../config/Pages'
|
||||
import { isEmail } from '../../../config/func'
|
||||
import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { ArrowLeft } from 'iconsax-react'
|
||||
import { toast } from '../../../components/Toast'
|
||||
const LoginStep1: FC = () => {
|
||||
|
||||
@@ -101,6 +102,13 @@ const LoginStep1: FC = () => {
|
||||
<div>{t('auth.register')}</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<a href='https://accounts.danakcorp.com' target='_blank' className='mt-4 flex gap-1 text-sm justify-center text-description'>
|
||||
<div>
|
||||
{t('auth.old_version')}
|
||||
</div>
|
||||
<ArrowLeft size={20} color='black' />
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,13 +13,16 @@ import { useLoginWithOtp, useOtpVerify } from '../hooks/useAuthData'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { setRefreshToken } from '../../../config/func'
|
||||
import { setToken } from '../../../config/func'
|
||||
import { getRedirectUrl } from '../../../config/func'
|
||||
import { toast } from '../../../components/Toast'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { buildPath } from '../../../helpers/utils'
|
||||
|
||||
const LoginStep2: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { phone, setStepLogin } = useAuthStore()
|
||||
const { value, reset } = useCountDown(2, true)
|
||||
const { slug } = useParams()
|
||||
const otpVerify = useOtpVerify()
|
||||
const loginWithOtp = useLoginWithOtp()
|
||||
|
||||
@@ -41,13 +44,7 @@ const LoginStep2: FC = () => {
|
||||
onSuccess(data) {
|
||||
setToken(data?.data?.accessToken?.token)
|
||||
setRefreshToken(data?.data?.refreshToken?.token)
|
||||
// Check if there's a redirect parameter in the URL
|
||||
const redirectUrl = getRedirectUrl();
|
||||
if (redirectUrl) {
|
||||
window.location.href = redirectUrl;
|
||||
return; // Prevent the default navigation
|
||||
}
|
||||
window.location.href = Pages.dashboard
|
||||
window.location.href = buildPath(Pages.dashboard, slug)
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast(error?.response?.data?.error?.message[0], 'error')
|
||||
|
||||
@@ -11,11 +11,11 @@ import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../../../config/Pages'
|
||||
import ArrowLeftIcon from '../../../assets/images/arrow-left.svg'
|
||||
import { useLoginWithPassword } from '../hooks/useAuthData'
|
||||
import { toast } from '../../../components/Toast'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
import { setRefreshToken } from '../../../config/func'
|
||||
import { setToken } from '../../../config/func'
|
||||
import { getRedirectUrl } from '../../../config/func'
|
||||
import { toast } from '../../../components/Toast'
|
||||
|
||||
const LoginStep3: FC = () => {
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useAuthStore } from '../store/AuthStore'
|
||||
import Error from '../../../components/Error'
|
||||
import Button from '../../../components/Button'
|
||||
import { useCheckHasAccountRegister, useLoginWithOtp } from '../hooks/useAuthData'
|
||||
import { toast } from '../../../components/Toast'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../../helpers/types'
|
||||
|
||||
const RegisterStep1: FC = () => {
|
||||
@@ -33,13 +33,12 @@ const RegisterStep1: FC = () => {
|
||||
setStepLogin(2)
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast(error?.response?.data?.error?.message[0], 'error')
|
||||
toast.error(error?.response?.data?.error?.message[0])
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
return (
|
||||
<div className='flex-1'>
|
||||
|
||||
|
||||
@@ -13,12 +13,14 @@ import { toast } from 'react-toastify'
|
||||
import { Pages } from '../../../config/Pages'
|
||||
import { setToken } from '../../../config/func'
|
||||
import { setRefreshToken } from '../../../config/func'
|
||||
import { getRedirectUrl } from '../../../config/func'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
const RegisterStep2: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { phone } = useAuthStore()
|
||||
const register = useRegister()
|
||||
const { slug } = useParams()
|
||||
|
||||
const formik = useFormik<RegisterType>({
|
||||
initialValues: {
|
||||
@@ -51,12 +53,7 @@ const RegisterStep2: FC = () => {
|
||||
onSuccess(data) {
|
||||
setToken(data?.data?.accessToken?.token)
|
||||
setRefreshToken(data?.data?.refreshToken?.token)
|
||||
const redirectUrl = getRedirectUrl();
|
||||
if (redirectUrl) {
|
||||
window.location.href = redirectUrl;
|
||||
return; // Prevent the default navigation
|
||||
}
|
||||
window.location.href = Pages.dashboard
|
||||
window.location.href = `/${slug}/${Pages.dashboard}`
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast.error(error?.response?.data?.error?.message[0])
|
||||
@@ -65,6 +62,7 @@ const RegisterStep2: FC = () => {
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className='mt-5 rowTwoInput'>
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import { Calendar, CallCalling, Location, Map1, MessageMinus, Profile, Setting2, Sms } from 'iconsax-react'
|
||||
import { FC } from 'react'
|
||||
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useGetCompanyDetail } from '../home/hooks/useHomeData'
|
||||
import moment from 'moment-jalaali'
|
||||
import Products from './components/Products'
|
||||
const CompanyDetail: FC = () => {
|
||||
|
||||
const { id } = useParams()
|
||||
const { data } = useGetCompanyDetail(id || '')
|
||||
|
||||
return (
|
||||
<div className='mt-4 pb-20'>
|
||||
<img
|
||||
src='https://picsum.photos/200/300'
|
||||
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='https://picsum.photos/200/300'
|
||||
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
|
||||
@@ -38,7 +45,7 @@ const CompanyDetail: FC = () => {
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
مدیرعامل: مهرداد مظفری
|
||||
مدیرعامل: {data?.data?.company?.user?.fullName}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-1 items-center'>
|
||||
@@ -47,7 +54,7 @@ const CompanyDetail: FC = () => {
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
صنعت : نفت و گاز
|
||||
صنعت : {data?.data?.company?.industry?.title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,7 +65,7 @@ const CompanyDetail: FC = () => {
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
شماره تماس : ۰۸۶۳۲۳۴۵۶۷۸
|
||||
شماره تماس : {data?.data?.company?.user?.phone}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-1 items-center'>
|
||||
@@ -67,7 +74,7 @@ const CompanyDetail: FC = () => {
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
شناسه ملی : ۱۲۳۴
|
||||
شناسه ملی : {data?.data?.company?.identificationNumber}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,7 +85,7 @@ const CompanyDetail: FC = () => {
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
ایمیل : info@example.com
|
||||
ایمیل : {data?.data?.company?.user?.email}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-1 items-center'>
|
||||
@@ -87,7 +94,7 @@ const CompanyDetail: FC = () => {
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
تاریخ تاسیس : ۱۳۸۹
|
||||
تاریخ تاسیس : {moment(data?.data?.company?.dateOfEstablishment, 'jYYYY/jMM/jDD').format('jYYYY/jMM/jDD')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,50 +105,13 @@ const CompanyDetail: FC = () => {
|
||||
color='black'
|
||||
/>
|
||||
<div>
|
||||
آدرس : لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ
|
||||
آدرس : {data?.data?.company?.address}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex justify-center gap-2 items-center'>
|
||||
<div className='flex h-8 bg-white px-6 items-center rounded-full text-xs border-2 border-white pt-[1px]'>
|
||||
محصولات
|
||||
</div>
|
||||
<div className='flex h-8 bg-white px-6 items-center rounded-full text-xs border-2 border-white bg-opacity-45 pt-[1px]'>
|
||||
خدمات
|
||||
</div>
|
||||
<div className='flex h-8 bg-white px-6 items-center rounded-full text-xs border-2 border-white bg-opacity-45 pt-[1px]'>
|
||||
گالری
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2 flex-wrap mt-6'>
|
||||
<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 bg-red-100'></div>
|
||||
<div className='text-xs'>
|
||||
<div>
|
||||
شیر صنعتی
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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 bg-red-100'></div>
|
||||
<div className='text-xs'>
|
||||
<div>
|
||||
شیر صنعتی
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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 bg-red-100'></div>
|
||||
<div className='text-xs'>
|
||||
<div>
|
||||
شیر صنعتی
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Products />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useGetCompanyDetail } from '../../home/hooks/useHomeData'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { ProductItemType } from '../types/CompanyTypes'
|
||||
const Products: FC = () => {
|
||||
|
||||
const { id } = useParams()
|
||||
const { data } = useGetCompanyDetail(id || '')
|
||||
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 Products
|
||||
@@ -0,0 +1,5 @@
|
||||
export type ProductItemType = {
|
||||
id: string;
|
||||
imageUrl: string;
|
||||
title: string;
|
||||
};
|
||||
+133
-170
@@ -1,171 +1,134 @@
|
||||
import { Building, InfoCircle } from 'iconsax-react'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { clx } from '../../helpers/utils'
|
||||
import StatusWithText from '../../components/StatusWithText'
|
||||
import TitleSeeAll from '../../components/TitleSeeAll'
|
||||
|
||||
const Home: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='w-full h-10 flex gap-2 items-center px-4 rounded-xl bg-[#FCE7CE] text-[#FF7B00]'>
|
||||
<InfoCircle
|
||||
size={24}
|
||||
color='#FF7B00'
|
||||
/>
|
||||
<div className='text-xs'>
|
||||
درخواست شما برای ثبت شرکت، هنوز تایید نشده است
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<TitleSeeAll
|
||||
title={t('home.last_announcement')}
|
||||
link='/'
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='mt-4'>
|
||||
<div className={clx(
|
||||
'p-4 rounded-2xl',
|
||||
'bg-[#F8F9FB]'
|
||||
)}>
|
||||
<div className='flex justify-between'>
|
||||
<div className='leading-6 text-xs xl:text-[12.5px] max-w-[70%]'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ
|
||||
</div>
|
||||
|
||||
<div className='flex gap-1'>
|
||||
<StatusWithText
|
||||
variant='error'
|
||||
text={t('announcement.important')}
|
||||
/>
|
||||
<StatusWithText
|
||||
variant='success'
|
||||
text={t('announcement.new')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='m-2 flex justify-between items-center text-[11px]'>
|
||||
<div className='max-w-[70%] truncate text-description'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ
|
||||
</div>
|
||||
<div className='text-description'>
|
||||
۱۴۰۳/۰۹/۲۴
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<TitleSeeAll
|
||||
title={t('home.companies')}
|
||||
link='/'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2 mt-4 overflowX'>
|
||||
<div className='h-9 bg-white rounded-full px-5 flex gap-2 items-center'>
|
||||
<Building
|
||||
size={20}
|
||||
color='black'
|
||||
/>
|
||||
<div className='text-sm'>
|
||||
همه
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-9 bg-white rounded-full px-5 flex gap-2 items-center'>
|
||||
<Building
|
||||
size={20}
|
||||
color='black'
|
||||
/>
|
||||
<div className='text-sm'>
|
||||
همه
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-9 bg-white rounded-full px-5 flex gap-2 items-center'>
|
||||
<Building
|
||||
size={20}
|
||||
color='black'
|
||||
/>
|
||||
<div className='text-sm'>
|
||||
همه
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-9 bg-white rounded-full px-5 flex gap-2 items-center'>
|
||||
<Building
|
||||
size={20}
|
||||
color='black'
|
||||
/>
|
||||
<div className='text-sm'>
|
||||
همه
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-9 bg-white rounded-full px-5 flex gap-2 items-center'>
|
||||
<Building
|
||||
size={20}
|
||||
color='black'
|
||||
/>
|
||||
<div className='text-sm'>
|
||||
همه
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex gap-4 flex-wrap'>
|
||||
<div className='flex-1 gap-2.5 bg-white min-w-[40%] h-20 flex items-center p-2 rounded-xl'>
|
||||
<div className='size-[64px] rounded-xl bg-red-100'></div>
|
||||
<div className='text-xs'>
|
||||
<div>
|
||||
شرکت
|
||||
</div>
|
||||
<div className='text-description mt-2'>
|
||||
شهرک شماره ۱
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 gap-2.5 bg-white min-w-[40%] h-20 flex items-center p-2 rounded-xl'>
|
||||
<div className='size-[64px] rounded-xl bg-red-100'></div>
|
||||
<div className='text-xs'>
|
||||
<div>
|
||||
هنرمند
|
||||
</div>
|
||||
<div className='text-description mt-2'>
|
||||
درامز
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 gap-2.5 bg-white min-w-[40%] h-20 flex items-center p-2 rounded-xl'>
|
||||
<div className='size-[64px] rounded-xl bg-red-100'></div>
|
||||
<div className='text-xs'>
|
||||
<div>
|
||||
هنرمند
|
||||
</div>
|
||||
<div className='text-description mt-2'>
|
||||
درامز
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1 gap-2.5 bg-white min-w-[40%] h-20 flex items-center p-2 rounded-xl'>
|
||||
<div className='size-[64px] rounded-xl bg-red-100'></div>
|
||||
<div className='text-xs'>
|
||||
<div>
|
||||
هنرمند
|
||||
</div>
|
||||
<div className='text-description mt-2'>
|
||||
درامز
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
import { Building, InfoCircle } from 'iconsax-react'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { buildPath, clx } from '../../helpers/utils'
|
||||
import StatusWithText from '../../components/StatusWithText'
|
||||
import TitleSeeAll from '../../components/TitleSeeAll'
|
||||
import { useGetCompanies, useGetIndustry } from './hooks/useHomeData'
|
||||
import { CompanyItemType, IndustryItemType } from './types/HomeTypes'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../../config/Pages'
|
||||
const Home: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [activeIndustry, setActiveIndustry] = useState<string>('')
|
||||
const { data: industry } = useGetIndustry()
|
||||
const { data: companies } = useGetCompanies(activeIndustry)
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='w-full h-10 flex gap-2 items-center px-4 rounded-xl bg-[#FCE7CE] text-[#FF7B00]'>
|
||||
<InfoCircle
|
||||
size={24}
|
||||
color='#FF7B00'
|
||||
/>
|
||||
<div className='text-xs'>
|
||||
درخواست شما برای ثبت شرکت، هنوز تایید نشده است
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<TitleSeeAll
|
||||
title={t('home.last_announcement')}
|
||||
link='/'
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='mt-4'>
|
||||
<div className={clx(
|
||||
'p-4 rounded-2xl',
|
||||
'bg-[#F8F9FB]'
|
||||
)}>
|
||||
<div className='flex justify-between'>
|
||||
<div className='leading-6 text-xs xl:text-[12.5px] max-w-[70%]'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ
|
||||
</div>
|
||||
|
||||
<div className='flex gap-1'>
|
||||
<StatusWithText
|
||||
variant='error'
|
||||
text={t('announcement.important')}
|
||||
/>
|
||||
<StatusWithText
|
||||
variant='success'
|
||||
text={t('announcement.new')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='m-2 flex justify-between items-center text-[11px]'>
|
||||
<div className='max-w-[70%] truncate text-description'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ
|
||||
</div>
|
||||
<div className='text-description'>
|
||||
۱۴۰۳/۰۹/۲۴
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<TitleSeeAll
|
||||
title={t('home.companies')}
|
||||
link='/'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2 mt-4 overflowX'>
|
||||
<div className={clx(
|
||||
'h-9 bg-white rounded-full px-5 flex gap-2 items-center cursor-pointer',
|
||||
activeIndustry === '' ? 'bg-opacity-100' : 'bg-opacity-50'
|
||||
)} onClick={() => setActiveIndustry('')}>
|
||||
<Building
|
||||
size={20}
|
||||
color='black'
|
||||
/>
|
||||
<div className='text-sm'>
|
||||
همه
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
industry?.data?.industries?.map((item: IndustryItemType) => {
|
||||
return (
|
||||
<div key={item.id} className={clx(
|
||||
'h-9 bg-white rounded-full px-5 flex gap-2 items-center cursor-pointer',
|
||||
activeIndustry === item.id ? 'bg-opacity-100' : 'bg-opacity-50'
|
||||
)} onClick={() => setActiveIndustry(item.id)}>
|
||||
<img src={item.iconUrl} alt={item.title} className='size-5' />
|
||||
<div className='text-sm'>
|
||||
{item.title}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex gap-4 flex-wrap'>
|
||||
{
|
||||
companies?.data?.companies?.map((item: CompanyItemType) => {
|
||||
return (
|
||||
<Link to={buildPath(Pages.company.detail + item.id)} key={item.id} className='flex-1 gap-2.5 bg-white min-w-[40%] h-20 flex items-center p-2 rounded-xl'>
|
||||
<div className='size-[64px] rounded-xl overflow-hidden'>
|
||||
<img src={item.profileImageUrl} alt={item.name} className='size-full object-cover' />
|
||||
</div>
|
||||
<div className='text-xs'>
|
||||
<div>
|
||||
{item.name}
|
||||
</div>
|
||||
<div className='text-description mt-2'>
|
||||
{item.address}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
getCompanies,
|
||||
getIdWithSlug,
|
||||
getIndustry,
|
||||
getCompanyDetail,
|
||||
} from "../service/HomeService";
|
||||
|
||||
export const useGetSlugId = () => {
|
||||
return useMutation({
|
||||
mutationFn: (slug: string) => getIdWithSlug(slug),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIndustry = () => {
|
||||
return useQuery({
|
||||
queryKey: ["industry"],
|
||||
queryFn: () => getIndustry(),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetCompanies = (industryId: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["companies", industryId],
|
||||
queryFn: () => getCompanies(industryId),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetCompanyDetail = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["company", id],
|
||||
queryFn: () => getCompanyDetail(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import axios from "../../../config/axios";
|
||||
|
||||
export const getIdWithSlug = async (slug: string) => {
|
||||
const { data } = await axios.get(`/business/slug/${slug}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getIndustry = async () => {
|
||||
const { data } = await axios.get(`/industries/list/public`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getCompanies = async (industryId: string) => {
|
||||
const params = new URLSearchParams();
|
||||
if (industryId) {
|
||||
params.append("industryId", industryId);
|
||||
}
|
||||
const { data } = await axios.get(
|
||||
`/companies/list/public?${params.toString()}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getCompanyDetail = async (id: string) => {
|
||||
const { data } = await axios.get(`/companies/public/${id}`);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
export type IndustryItemType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
title: string;
|
||||
iconUrl: string;
|
||||
isActive: boolean;
|
||||
business: string;
|
||||
companiesCount: number;
|
||||
};
|
||||
|
||||
export type CompanyItemType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
identificationNumber: string;
|
||||
dateOfEstablishment: string;
|
||||
address: string;
|
||||
mapAddressLink: string;
|
||||
description: string;
|
||||
websiteUrl: string;
|
||||
profileImageUrl: string;
|
||||
coverImageUrl: string;
|
||||
isActive: boolean;
|
||||
status: string;
|
||||
industry: IndustryItemType;
|
||||
user: string;
|
||||
business: string;
|
||||
invoiceCount: number;
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import Select from '../../components/Select'
|
||||
import Textarea from '../../components/Textarea'
|
||||
import { useGetProfile } from './hooks/useProfileData'
|
||||
import Username from './components/Username'
|
||||
import withAuth from '../../hoc/withAuth'
|
||||
|
||||
const Profile: FC = () => {
|
||||
|
||||
@@ -171,4 +172,4 @@ const Profile: FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default Profile
|
||||
export default withAuth(Profile)
|
||||
+111
-111
@@ -1,112 +1,112 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tabs from '../../components/Tabs'
|
||||
import { Calendar, CloseCircle, Eye, FolderOpen, MoneyChange, WalletCheck, WalletRemove, WalletSearch } from 'iconsax-react'
|
||||
import Td from '../../components/Td'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../../config/Pages'
|
||||
|
||||
const ReceiptsList: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [activeTab, setActiveTab] = useState<string>('unpaid')
|
||||
|
||||
return (
|
||||
<div className='flex justify-center'>
|
||||
<div className='mt-4 w-full max-w-[1200px]'>
|
||||
<div>
|
||||
{t('receip.Language_learners_list')}
|
||||
</div>
|
||||
|
||||
<div className='mt-14'>
|
||||
<Tabs
|
||||
active={activeTab}
|
||||
items={[
|
||||
{
|
||||
icon: <WalletRemove color={activeTab === 'unpaid' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.dont_pay'),
|
||||
value: 'unpaid'
|
||||
},
|
||||
{
|
||||
icon: <WalletSearch color={activeTab === 'not_confimed' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.dont_confrim'),
|
||||
value: 'not_confimed'
|
||||
},
|
||||
{
|
||||
icon: <WalletCheck color={activeTab === 'paid' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.paid'),
|
||||
value: 'paid'
|
||||
},
|
||||
{
|
||||
icon: <CloseCircle color={activeTab === 'canceled' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.canceled'),
|
||||
value: 'canceled'
|
||||
},
|
||||
{
|
||||
icon: <MoneyChange color={activeTab === 'returned' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.returned'),
|
||||
value: 'returned'
|
||||
},
|
||||
{
|
||||
icon: <Calendar color={activeTab === 'time' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.its_time'),
|
||||
value: 'time'
|
||||
},
|
||||
{
|
||||
icon: <FolderOpen color={activeTab === 'archive' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.archive'),
|
||||
value: 'archive'
|
||||
},
|
||||
]}
|
||||
onChange={setActiveTab}
|
||||
/>
|
||||
</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={''}>
|
||||
<Link to={Pages.receipts.detail + '1'}>
|
||||
<Eye size={20} color='black' />
|
||||
</Link>
|
||||
</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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tabs from '../../components/Tabs'
|
||||
import { Calendar, CloseCircle, Eye, FolderOpen, MoneyChange, WalletCheck, WalletRemove, WalletSearch } from 'iconsax-react'
|
||||
import Td from '../../components/Td'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '../../config/Pages'
|
||||
|
||||
const ReceiptsList: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [activeTab, setActiveTab] = useState<string>('unpaid')
|
||||
|
||||
return (
|
||||
<div className='flex justify-center'>
|
||||
<div className='mt-4 w-full max-w-[1200px]'>
|
||||
<div>
|
||||
{t('receip.Language_learners_list')}
|
||||
</div>
|
||||
|
||||
<div className='mt-14'>
|
||||
<Tabs
|
||||
active={activeTab}
|
||||
items={[
|
||||
{
|
||||
icon: <WalletRemove color={activeTab === 'unpaid' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.dont_pay'),
|
||||
value: 'unpaid'
|
||||
},
|
||||
{
|
||||
icon: <WalletSearch color={activeTab === 'not_confimed' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.dont_confrim'),
|
||||
value: 'not_confimed'
|
||||
},
|
||||
{
|
||||
icon: <WalletCheck color={activeTab === 'paid' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.paid'),
|
||||
value: 'paid'
|
||||
},
|
||||
{
|
||||
icon: <CloseCircle color={activeTab === 'canceled' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.canceled'),
|
||||
value: 'canceled'
|
||||
},
|
||||
{
|
||||
icon: <MoneyChange color={activeTab === 'returned' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.returned'),
|
||||
value: 'returned'
|
||||
},
|
||||
{
|
||||
icon: <Calendar color={activeTab === 'time' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.its_time'),
|
||||
value: 'time'
|
||||
},
|
||||
{
|
||||
icon: <FolderOpen color={activeTab === 'archive' ? 'black' : '#8C90A3'} size={22} />,
|
||||
label: t('receip.archive'),
|
||||
value: 'archive'
|
||||
},
|
||||
]}
|
||||
onChange={setActiveTab}
|
||||
/>
|
||||
</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={''}>
|
||||
<Link to={Pages.receipts.detail + '1'}>
|
||||
<Eye size={20} color='black' />
|
||||
</Link>
|
||||
</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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReceiptsList
|
||||
@@ -5,6 +5,7 @@ import Tabs from '../../components/Tabs'
|
||||
import Online from './components/Online'
|
||||
import CardtoCard from './components/CardtoCard'
|
||||
import Sheba from './components/Sheba'
|
||||
import withAuth from '../../hoc/withAuth'
|
||||
|
||||
const Wallet: FC = () => {
|
||||
|
||||
@@ -90,4 +91,5 @@ const Wallet: FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default Wallet
|
||||
// استفاده از HOC با آپشنهای سفارشی
|
||||
export default withAuth(Wallet)
|
||||
+80
-53
@@ -1,54 +1,81 @@
|
||||
import Header from "../shared/Header"
|
||||
import SideBar from "../shared/SideBar"
|
||||
import '../assets/fonts/irancell/style.css'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
import Home from '../pages/home/Home'
|
||||
import ReceiptsList from '../pages/receipts/List'
|
||||
import AddCriticisms from '../pages/criticisms/Add'
|
||||
import Footer from "../shared/Footer"
|
||||
import GradeRecordes from "../pages/grade/records"
|
||||
import Certificate from "../pages/certificate/List"
|
||||
import AdmitCard from "../pages/admitCard/List"
|
||||
import MyCourses from "../pages/my-courses/List"
|
||||
import Payment from "../pages/payment/Details"
|
||||
import Wallet from "../pages/wallet/Wallet"
|
||||
import ReceiptsDetail from "../pages/receipts/Detail"
|
||||
import ReportCardList from "../pages/ReportCard/List"
|
||||
import TransactionList from "../pages/transaction/List"
|
||||
import Profile from "../pages/profile/Profile"
|
||||
import CompanyDetail from "../pages/company/Detail"
|
||||
|
||||
const MainRouter = () => {
|
||||
return (
|
||||
<div className='p-4 overflow-hidden'>
|
||||
<SideBar />
|
||||
<Header />
|
||||
<div className='flex-1 xl:ms-[269px] mt-[68px] xl:mt-[81px]'>
|
||||
<div className={`hide-scroll-bar overflow-auto w-[${window.innerWidth}] max-h-[calc(100vh-113px)]`}>
|
||||
<div>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="dashboard" element={<Home />} />
|
||||
<Route path="receipts" element={<ReceiptsList />} />
|
||||
<Route path="receipts/:id" element={<ReceiptsDetail />} />
|
||||
<Route path="company/:id" element={<CompanyDetail />} />
|
||||
<Route path="criticisms" element={<AddCriticisms />} />
|
||||
<Route path="grade/records" element={<GradeRecordes />} />
|
||||
<Route path="transactions" element={<TransactionList />} />
|
||||
<Route path="certificate" element={<Certificate />} />
|
||||
<Route path="admitCard" element={<AdmitCard />} />
|
||||
<Route path="myCourses" element={<MyCourses />} />
|
||||
<Route path="payment" element={<Payment />} />
|
||||
<Route path="wallet" element={<Wallet />} />
|
||||
<Route path="reportCard/list" element={<ReportCardList />} />
|
||||
<Route path="profile" element={<Profile />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
import Header from "../shared/Header"
|
||||
import SideBar from "../shared/SideBar"
|
||||
import '../assets/fonts/irancell/style.css'
|
||||
import { Route, Routes, useParams } from 'react-router-dom'
|
||||
import Home from '../pages/home/Home'
|
||||
import ReceiptsList from '../pages/receipts/List'
|
||||
import AddCriticisms from '../pages/criticisms/Add'
|
||||
import Footer from "../shared/Footer"
|
||||
import GradeRecordes from "../pages/grade/records"
|
||||
import Certificate from "../pages/certificate/List"
|
||||
import AdmitCard from "../pages/admitCard/List"
|
||||
import MyCourses from "../pages/my-courses/List"
|
||||
import Payment from "../pages/payment/Details"
|
||||
import Wallet from "../pages/wallet/Wallet"
|
||||
import ReceiptsDetail from "../pages/receipts/Detail"
|
||||
import ReportCardList from "../pages/ReportCard/List"
|
||||
import TransactionList from "../pages/transaction/List"
|
||||
import Profile from "../pages/profile/Profile"
|
||||
import CompanyDetail from "../pages/company/Detail"
|
||||
import { useEffect } from "react"
|
||||
import { useGetSlugId } from "../pages/home/hooks/useHomeData"
|
||||
import { ErrorType } from "../helpers/types"
|
||||
import { toast } from "react-toastify"
|
||||
const MainRouter = () => {
|
||||
const { slug } = useParams()
|
||||
const getSlugId = useGetSlugId()
|
||||
useEffect(() => {
|
||||
if (slug) {
|
||||
const slug_id = localStorage.getItem(import.meta.env.VITE_SLUG_ID)
|
||||
const slug_parse = slug_id ? JSON.parse(slug_id) : null
|
||||
if (slug_parse && slug_parse.slug === slug) {
|
||||
|
||||
return
|
||||
} else {
|
||||
getSlugId.mutate(slug, {
|
||||
onSuccess: (data) => {
|
||||
localStorage.setItem(import.meta.env.VITE_SLUG_ID, JSON.stringify(data?.data?.business))
|
||||
window.location.reload()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error.message[0])
|
||||
window.location.href = 'https://danakcorp.com'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [slug])
|
||||
|
||||
return (
|
||||
<div className='p-4 overflow-hidden'>
|
||||
<SideBar />
|
||||
<Header />
|
||||
<div className='flex-1 xl:ms-[269px] mt-[68px] xl:mt-[81px]'>
|
||||
<div className={`hide-scroll-bar overflow-auto w-[${window.innerWidth}] max-h-[calc(100vh-113px)]`}>
|
||||
<div>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="dashboard" element={<Home />} />
|
||||
<Route path="receipts" element={<ReceiptsList />} />
|
||||
<Route path="receipts/:id" element={<ReceiptsDetail />} />
|
||||
<Route path="company/:id" element={<CompanyDetail />} />
|
||||
<Route path="criticisms" element={<AddCriticisms />} />
|
||||
<Route path="grade/records" element={<GradeRecordes />} />
|
||||
<Route path="transactions" element={<TransactionList />} />
|
||||
<Route path="certificate" element={<Certificate />} />
|
||||
<Route path="admitCard" element={<AdmitCard />} />
|
||||
<Route path="myCourses" element={<MyCourses />} />
|
||||
<Route path="payment" element={<Payment />} />
|
||||
<Route path="wallet" element={<Wallet />} />
|
||||
<Route path="reportCard/list" element={<ReportCardList />} />
|
||||
<Route path="profile" element={<Profile />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MainRouter
|
||||
+50
-50
@@ -1,51 +1,51 @@
|
||||
import { FC } from 'react'
|
||||
import Input from '../components/Input'
|
||||
import { ArrowDown2, Element3, HambergerMenu, Wallet } from 'iconsax-react'
|
||||
import AvatarImage from '../assets/images/Avatar.png'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { Pages } from '../config/Pages'
|
||||
import Notifications from '../pages/notification/Notification'
|
||||
import { useSharedStore } from './store/sharedStore'
|
||||
|
||||
const Header: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { setOpenSidebar, openSidebar } = useSharedStore()
|
||||
const { slug } = useParams()
|
||||
|
||||
return (
|
||||
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
|
||||
<div className='min-w-[270px] hidden xl:block'>
|
||||
<Input
|
||||
variant='search'
|
||||
placeholder={t('header.search')}
|
||||
/>
|
||||
</div>
|
||||
<div onClick={() => setOpenSidebar(!openSidebar)} className='xl:hidden block'>
|
||||
<HambergerMenu size={24} color='black' />
|
||||
</div>
|
||||
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */}
|
||||
<div className='flex xl:gap-6 gap-3 items-center'>
|
||||
<Element3 color='black' className='xl:size-[18px] size-4' />
|
||||
<Link to={`/${slug}/${Pages.wallet}`}>
|
||||
<Wallet className='xl:size-[18px] size-4' color='black' />
|
||||
</Link>
|
||||
<Notifications />
|
||||
<div className='flex gap-2 items-center'>
|
||||
<div className='size-7 rounded-full bg-description overflow-hidden'>
|
||||
<Link to={`/${slug}/${Pages.profile}`}>
|
||||
<img src={AvatarImage} className='size-full object-cover' />
|
||||
</Link>
|
||||
</div>
|
||||
<div className='xl:flex hidden gap-1 items-center'>
|
||||
<div className='text-xs'>مهرداد مظفری</div>
|
||||
<ArrowDown2 size={14} color='#8C90A3' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
import { FC } from 'react'
|
||||
import Input from '../components/Input'
|
||||
import { ArrowDown2, Element3, HambergerMenu, Wallet } from 'iconsax-react'
|
||||
import AvatarImage from '../assets/images/Avatar.png'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { Pages } from '../config/Pages'
|
||||
import Notifications from '../pages/notification/Notification'
|
||||
import { useSharedStore } from './store/sharedStore'
|
||||
|
||||
const Header: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { setOpenSidebar, openSidebar } = useSharedStore()
|
||||
const { slug } = useParams()
|
||||
|
||||
return (
|
||||
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
|
||||
<div className='min-w-[270px] hidden xl:block'>
|
||||
<Input
|
||||
variant='search'
|
||||
placeholder={t('header.search')}
|
||||
/>
|
||||
</div>
|
||||
<div onClick={() => setOpenSidebar(!openSidebar)} className='xl:hidden block'>
|
||||
<HambergerMenu size={24} color='black' />
|
||||
</div>
|
||||
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */}
|
||||
<div className='flex xl:gap-6 gap-3 items-center'>
|
||||
<Element3 color='black' className='xl:size-[18px] size-4' />
|
||||
<Link to={`/${slug}/${Pages.wallet}`}>
|
||||
<Wallet className='xl:size-[18px] size-4' color='black' />
|
||||
</Link>
|
||||
<Notifications />
|
||||
<div className='flex gap-2 items-center'>
|
||||
<div className='size-7 rounded-full bg-description overflow-hidden'>
|
||||
<Link to={`/${slug}/${Pages.profile}`}>
|
||||
<img src={AvatarImage} className='size-full object-cover' />
|
||||
</Link>
|
||||
</div>
|
||||
<div className='xl:flex hidden gap-1 items-center'>
|
||||
<div className='text-xs'>مهرداد مظفری</div>
|
||||
<ArrowDown2 size={14} color='#8C90A3' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
+44
-44
@@ -1,45 +1,45 @@
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { clx } from '../helpers/utils'
|
||||
import { Pages } from '../config/Pages'
|
||||
|
||||
type Props = {
|
||||
icon: ReactNode,
|
||||
title: string,
|
||||
isActive: boolean,
|
||||
link: string, // مقدار مثل: "dashboard" یا "wallet"
|
||||
isLogout?: boolean,
|
||||
}
|
||||
|
||||
const SideBarItem: FC<Props> = (props: Props) => {
|
||||
const { slug } = useParams(); // دریافت slug از URL
|
||||
const fullLink = props.isLogout ? Pages.auth.login : `/${slug}/${props.link.replace(/^\//, '')}`;
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem(import.meta.env.VITE_TOKEN_NAME)
|
||||
window.location.href = Pages.auth.login
|
||||
}
|
||||
|
||||
// alert(slug)
|
||||
|
||||
return (
|
||||
<Link
|
||||
onClick={props.isLogout ? handleLogout : undefined}
|
||||
to={fullLink}
|
||||
className='flex text-xs gap-9 mt-4'
|
||||
>
|
||||
<div className={clx(
|
||||
'w-1 bg-black h-6',
|
||||
!props.isActive && 'invisible'
|
||||
)}></div>
|
||||
<div className='flex gap-3 items-center'>
|
||||
{props.icon}
|
||||
<div className={props.isActive ? 'text-black' : ''}>
|
||||
{props.title}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { clx } from '../helpers/utils'
|
||||
import { Pages } from '../config/Pages'
|
||||
|
||||
type Props = {
|
||||
icon: ReactNode,
|
||||
title: string,
|
||||
isActive: boolean,
|
||||
link: string, // مقدار مثل: "dashboard" یا "wallet"
|
||||
isLogout?: boolean,
|
||||
}
|
||||
|
||||
const SideBarItem: FC<Props> = (props: Props) => {
|
||||
const { slug } = useParams(); // دریافت slug از URL
|
||||
const fullLink = props.isLogout ? Pages.auth.login : `/${slug}/${props.link.replace(/^\//, '')}`;
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem(import.meta.env.VITE_TOKEN_NAME)
|
||||
window.location.href = Pages.auth.login
|
||||
}
|
||||
|
||||
// alert(slug)
|
||||
|
||||
return (
|
||||
<Link
|
||||
onClick={props.isLogout ? handleLogout : undefined}
|
||||
to={fullLink}
|
||||
className='flex text-xs gap-9 mt-4'
|
||||
>
|
||||
<div className={clx(
|
||||
'w-1 bg-black h-6',
|
||||
!props.isActive && 'invisible'
|
||||
)}></div>
|
||||
<div className='flex gap-3 items-center'>
|
||||
{props.icon}
|
||||
<div className={props.isActive ? 'text-black' : ''}>
|
||||
{props.title}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export default SideBarItem
|
||||
@@ -4,4 +4,6 @@ import { SharedStoreType } from "../types/SharedTypes";
|
||||
export const useSharedStore = create<SharedStoreType>((set) => ({
|
||||
openSidebar: false,
|
||||
setOpenSidebar: (value) => set({ openSidebar: value }),
|
||||
slug: "",
|
||||
setSlug: (value) => set({ slug: value }),
|
||||
}));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export type SharedStoreType = {
|
||||
openSidebar: boolean;
|
||||
setOpenSidebar: (value: boolean) => void;
|
||||
};
|
||||
|
||||
openSidebar: boolean;
|
||||
setOpenSidebar: (value: boolean) => void;
|
||||
slug: string;
|
||||
setSlug: (value: string) => void;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user