From 2fbdf05a257ae7992aff0570da0a8c7bb0defc69 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Mon, 19 May 2025 12:06:59 +0330 Subject: [PATCH] landing + single company + auth --- .env | 6 +- package-lock.json | 61 ++++ package.json | 2 + src/App.tsx | 44 +-- src/config/Pages.ts | 7 +- src/config/axios.ts | 4 +- src/config/func.ts | 5 +- src/helpers/utils.ts | 10 + src/hoc/withAuth.tsx | 38 +++ src/pages/auth/components/LoginStep1.tsx | 8 + src/pages/auth/components/LoginStep2.tsx | 13 +- src/pages/auth/components/LoginStep3.tsx | 2 +- src/pages/auth/components/RegisterStep1.tsx | 5 +- src/pages/auth/components/RegisterStep2.tsx | 12 +- src/pages/company/Detail.tsx | 68 ++--- src/pages/company/components/Products.tsx | 70 +++++ src/pages/company/types/CompanyTypes.ts | 5 + src/pages/home/Home.tsx | 303 +++++++++----------- src/pages/home/hooks/useHomeData.ts | 35 +++ src/pages/home/service/HomeService.ts | 27 ++ src/pages/home/types/HomeTypes.ts | 33 +++ src/pages/profile/Profile.tsx | 3 +- src/pages/receipts/List.tsx | 222 +++++++------- src/pages/wallet/Wallet.tsx | 4 +- src/router/Main.tsx | 133 +++++---- src/shared/Header.tsx | 100 +++---- src/shared/SideBarItem.tsx | 88 +++--- src/shared/store/sharedStore.ts | 2 + src/shared/types/SharedTypes.ts | 9 +- 29 files changed, 771 insertions(+), 548 deletions(-) create mode 100644 src/hoc/withAuth.tsx create mode 100644 src/pages/company/components/Products.tsx create mode 100644 src/pages/company/types/CompanyTypes.ts create mode 100644 src/pages/home/hooks/useHomeData.ts create mode 100644 src/pages/home/service/HomeService.ts create mode 100644 src/pages/home/types/HomeTypes.ts diff --git a/.env b/.env index 4a9dcd0..4251060 100644 --- a/.env +++ b/.env @@ -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' \ No newline at end of file +# VITE_BASE_URL = 'https://dzone-api.danakcorp.com' +VITE_BASE_URL = 'http://192.168.1.113:4001' + +VITE_SLUG_ID = 'slug_id' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5a62720..73aea6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index b3c111b..0fa1f01 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.tsx b/src/App.tsx index 2db8921..4186bfa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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() { - { - isLogin === 'checking' ? - null - : - isLogin === 'isLogin' ? - - } /> - } /> - } /> - - : - - } + + } /> + } /> + @@ -135,4 +106,5 @@ function App() { ) } + export default App diff --git a/src/config/Pages.ts b/src/config/Pages.ts index a9150ed..0d315c7 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -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", diff --git a/src/config/axios.ts b/src/config/axios.ts index 206a62e..3c86697 100644 --- a/src/config/axios.ts +++ b/src/config/axios.ts @@ -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; }); diff --git a/src/config/func.ts b/src/config/func.ts index baa7630..f9c9f9f 100644 --- a/src/config/func.ts +++ b/src/config/func.ts @@ -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 }); diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index aa728fe..76aed8d 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -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}`; + } +}; diff --git a/src/hoc/withAuth.tsx b/src/hoc/withAuth.tsx new file mode 100644 index 0000000..4aa8b23 --- /dev/null +++ b/src/hoc/withAuth.tsx @@ -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 + +// HOC برای محافظت از کامپوننت‌های نیازمند لاگین +const withAuth = (WrappedComponent: ComponentType): ComponentType => { + // کامپوننت جدید که لاگین را چک می‌کند + const WithAuthComponent: FC = (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 + } + + // نام نمایشی کامپوننت را برای دیباگ بهتر تنظیم کن + WithAuthComponent.displayName = `WithAuth(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})` + + return WithAuthComponent +} + +export default withAuth \ No newline at end of file diff --git a/src/pages/auth/components/LoginStep1.tsx b/src/pages/auth/components/LoginStep1.tsx index 7c0a5ef..62f11c3 100644 --- a/src/pages/auth/components/LoginStep1.tsx +++ b/src/pages/auth/components/LoginStep1.tsx @@ -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 = () => {
{t('auth.register')}
+ + +
+ {t('auth.old_version')} +
+ +
) } diff --git a/src/pages/auth/components/LoginStep2.tsx b/src/pages/auth/components/LoginStep2.tsx index 6bfaf06..4b49c14 100644 --- a/src/pages/auth/components/LoginStep2.tsx +++ b/src/pages/auth/components/LoginStep2.tsx @@ -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') diff --git a/src/pages/auth/components/LoginStep3.tsx b/src/pages/auth/components/LoginStep3.tsx index d0734f0..a8bb2e8 100644 --- a/src/pages/auth/components/LoginStep3.tsx +++ b/src/pages/auth/components/LoginStep3.tsx @@ -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 = () => { diff --git a/src/pages/auth/components/RegisterStep1.tsx b/src/pages/auth/components/RegisterStep1.tsx index d081410..9122f13 100644 --- a/src/pages/auth/components/RegisterStep1.tsx +++ b/src/pages/auth/components/RegisterStep1.tsx @@ -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 (
diff --git a/src/pages/auth/components/RegisterStep2.tsx b/src/pages/auth/components/RegisterStep2.tsx index 1fc51cf..826d1e1 100644 --- a/src/pages/auth/components/RegisterStep2.tsx +++ b/src/pages/auth/components/RegisterStep2.tsx @@ -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({ 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 (
diff --git a/src/pages/company/Detail.tsx b/src/pages/company/Detail.tsx index 6778da1..c33e297 100644 --- a/src/pages/company/Detail.tsx +++ b/src/pages/company/Detail.tsx @@ -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 (
- شرکت شماره ۱ + {data?.data?.company?.name}
{ color='black' />
- مدیرعامل: مهرداد مظفری + مدیرعامل: {data?.data?.company?.user?.fullName}
@@ -47,7 +54,7 @@ const CompanyDetail: FC = () => { color='black' />
- صنعت : نفت و گاز + صنعت : {data?.data?.company?.industry?.title}
@@ -58,7 +65,7 @@ const CompanyDetail: FC = () => { color='black' />
- شماره تماس : ۰۸۶۳۲۳۴۵۶۷۸ + شماره تماس : {data?.data?.company?.user?.phone}
@@ -67,7 +74,7 @@ const CompanyDetail: FC = () => { color='black' />
- شناسه ملی : ۱۲۳۴ + شناسه ملی : {data?.data?.company?.identificationNumber}
@@ -78,7 +85,7 @@ const CompanyDetail: FC = () => { color='black' />
- ایمیل : info@example.com + ایمیل : {data?.data?.company?.user?.email}
@@ -87,7 +94,7 @@ const CompanyDetail: FC = () => { color='black' />
- تاریخ تاسیس : ۱۳۸۹ + تاریخ تاسیس : {moment(data?.data?.company?.dateOfEstablishment, 'jYYYY/jMM/jDD').format('jYYYY/jMM/jDD')}
@@ -98,50 +105,13 @@ const CompanyDetail: FC = () => { color='black' />
- آدرس : لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ + آدرس : {data?.data?.company?.address}
-
-
- محصولات -
-
- خدمات -
-
- گالری -
-
- -
-
-
-
-
- شیر صنعتی -
-
-
-
-
-
-
- شیر صنعتی -
-
-
-
-
-
-
- شیر صنعتی -
-
-
-
+ ) } diff --git a/src/pages/company/components/Products.tsx b/src/pages/company/components/Products.tsx new file mode 100644 index 0000000..503e65b --- /dev/null +++ b/src/pages/company/components/Products.tsx @@ -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 ( +
+
+
setActiveTab('products')}> + محصولات +
+
setActiveTab('services')}> + خدمات +
+ {/*
setActiveTab('gallery')}> + گالری +
*/} +
+ + { + activeTab === 'products' && ( +
+ { + data?.data?.company?.products?.map((item: ProductItemType) => ( +
+
+ {item.title} +
+
+
+ {item.title} +
+
+
+ )) + } +
+ ) + } + + { + activeTab === 'services' && ( +
+ { + data?.data?.company?.services?.map((item: ProductItemType) => ( +
+
+ {item.title} +
+
+
+ {item.title} +
+
+
+ )) + } +
+ ) + } +
+ ) +} + +export default Products \ No newline at end of file diff --git a/src/pages/company/types/CompanyTypes.ts b/src/pages/company/types/CompanyTypes.ts new file mode 100644 index 0000000..9788cd0 --- /dev/null +++ b/src/pages/company/types/CompanyTypes.ts @@ -0,0 +1,5 @@ +export type ProductItemType = { + id: string; + imageUrl: string; + title: string; +}; diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 121d478..9ce8b89 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -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 ( -
-
- -
- درخواست شما برای ثبت شرکت، هنوز تایید نشده است -
-
- -
- -
- - -
-
-
-
- لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ -
- -
- - -
-
- -
-
- لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ -
-
- ۱۴۰۳/۰۹/۲۴ -
-
-
-
- -
- -
- -
-
- -
- همه -
-
-
- -
- همه -
-
-
- -
- همه -
-
-
- -
- همه -
-
-
- -
- همه -
-
-
- -
-
-
-
-
- شرکت -
-
- شهرک شماره ۱ -
-
-
-
-
-
-
- هنرمند -
-
- درامز -
-
-
-
-
-
-
- هنرمند -
-
- درامز -
-
-
-
-
-
-
- هنرمند -
-
- درامز -
-
-
-
- -
- ) -} - +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('') + const { data: industry } = useGetIndustry() + const { data: companies } = useGetCompanies(activeIndustry) + + return ( +
+
+ +
+ درخواست شما برای ثبت شرکت، هنوز تایید نشده است +
+
+ +
+ +
+ + +
+
+
+
+ لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ +
+ +
+ + +
+
+ +
+
+ لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ +
+
+ ۱۴۰۳/۰۹/۲۴ +
+
+
+
+ +
+ +
+ +
+
setActiveIndustry('')}> + +
+ همه +
+
+ { + industry?.data?.industries?.map((item: IndustryItemType) => { + return ( +
setActiveIndustry(item.id)}> + {item.title} +
+ {item.title} +
+
+ ) + }) + } +
+ +
+ { + companies?.data?.companies?.map((item: CompanyItemType) => { + return ( + +
+ {item.name} +
+
+
+ {item.name} +
+
+ {item.address} +
+
+ + ) + }) + } +
+ +
+ ) +} + export default Home \ No newline at end of file diff --git a/src/pages/home/hooks/useHomeData.ts b/src/pages/home/hooks/useHomeData.ts new file mode 100644 index 0000000..9217cc0 --- /dev/null +++ b/src/pages/home/hooks/useHomeData.ts @@ -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, + }); +}; diff --git a/src/pages/home/service/HomeService.ts b/src/pages/home/service/HomeService.ts new file mode 100644 index 0000000..6bc3790 --- /dev/null +++ b/src/pages/home/service/HomeService.ts @@ -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; +}; diff --git a/src/pages/home/types/HomeTypes.ts b/src/pages/home/types/HomeTypes.ts new file mode 100644 index 0000000..175105a --- /dev/null +++ b/src/pages/home/types/HomeTypes.ts @@ -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; +}; diff --git a/src/pages/profile/Profile.tsx b/src/pages/profile/Profile.tsx index e1cda59..676c263 100644 --- a/src/pages/profile/Profile.tsx +++ b/src/pages/profile/Profile.tsx @@ -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 \ No newline at end of file +export default withAuth(Profile) \ No newline at end of file diff --git a/src/pages/receipts/List.tsx b/src/pages/receipts/List.tsx index dfd8837..7c2cc9d 100644 --- a/src/pages/receipts/List.tsx +++ b/src/pages/receipts/List.tsx @@ -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('unpaid') - - return ( -
-
-
- {t('receip.Language_learners_list')} -
- -
- , - label: t('receip.dont_pay'), - value: 'unpaid' - }, - { - icon: , - label: t('receip.dont_confrim'), - value: 'not_confimed' - }, - { - icon: , - label: t('receip.paid'), - value: 'paid' - }, - { - icon: , - label: t('receip.canceled'), - value: 'canceled' - }, - { - icon: , - label: t('receip.returned'), - value: 'returned' - }, - { - icon: , - label: t('receip.its_time'), - value: 'time' - }, - { - icon: , - label: t('receip.archive'), - value: 'archive' - }, - ]} - onChange={setActiveTab} - /> -
- -
- - - - - - - - - - - - -
- - - - - - - -
- - - - - - - - - - -
- - - - - - - -
-
-
-
- ) -} - +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('unpaid') + + return ( +
+
+
+ {t('receip.Language_learners_list')} +
+ +
+ , + label: t('receip.dont_pay'), + value: 'unpaid' + }, + { + icon: , + label: t('receip.dont_confrim'), + value: 'not_confimed' + }, + { + icon: , + label: t('receip.paid'), + value: 'paid' + }, + { + icon: , + label: t('receip.canceled'), + value: 'canceled' + }, + { + icon: , + label: t('receip.returned'), + value: 'returned' + }, + { + icon: , + label: t('receip.its_time'), + value: 'time' + }, + { + icon: , + label: t('receip.archive'), + value: 'archive' + }, + ]} + onChange={setActiveTab} + /> +
+ +
+ + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ + + + + + + +
+
+
+
+ ) +} + export default ReceiptsList \ No newline at end of file diff --git a/src/pages/wallet/Wallet.tsx b/src/pages/wallet/Wallet.tsx index 8f8c390..560fd93 100644 --- a/src/pages/wallet/Wallet.tsx +++ b/src/pages/wallet/Wallet.tsx @@ -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 \ No newline at end of file +// استفاده از HOC با آپشن‌های سفارشی +export default withAuth(Wallet) \ No newline at end of file diff --git a/src/router/Main.tsx b/src/router/Main.tsx index d7d6378..47b280b 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -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 ( -
- -
-
-
-
- - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - -
-
-
-
-
- ) -} - +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 ( +
+ +
+
+
+
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
+
+
+
+
+ ) +} + export default MainRouter \ No newline at end of file diff --git a/src/shared/Header.tsx b/src/shared/Header.tsx index 32ad609..ed729e4 100644 --- a/src/shared/Header.tsx +++ b/src/shared/Header.tsx @@ -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 ( -
-
- -
-
setOpenSidebar(!openSidebar)} className='xl:hidden block'> - -
- {/* */} -
- - - - - -
-
- - - -
-
-
مهرداد مظفری
- -
-
-
-
- ) -} - +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 ( +
+
+ +
+
setOpenSidebar(!openSidebar)} className='xl:hidden block'> + +
+ {/* */} +
+ + + + + +
+
+ + + +
+
+
مهرداد مظفری
+ +
+
+
+
+ ) +} + export default Header \ No newline at end of file diff --git a/src/shared/SideBarItem.tsx b/src/shared/SideBarItem.tsx index 55363a6..5c9a57c 100644 --- a/src/shared/SideBarItem.tsx +++ b/src/shared/SideBarItem.tsx @@ -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) => { - 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 ( - -
-
- {props.icon} -
- {props.title} -
-
- - ) -} - +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) => { + 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 ( + +
+
+ {props.icon} +
+ {props.title} +
+
+ + ) +} + export default SideBarItem \ No newline at end of file diff --git a/src/shared/store/sharedStore.ts b/src/shared/store/sharedStore.ts index 4f202d3..815f1c5 100644 --- a/src/shared/store/sharedStore.ts +++ b/src/shared/store/sharedStore.ts @@ -4,4 +4,6 @@ import { SharedStoreType } from "../types/SharedTypes"; export const useSharedStore = create((set) => ({ openSidebar: false, setOpenSidebar: (value) => set({ openSidebar: value }), + slug: "", + setSlug: (value) => set({ slug: value }), })); diff --git a/src/shared/types/SharedTypes.ts b/src/shared/types/SharedTypes.ts index 9d48330..0fe5c42 100644 --- a/src/shared/types/SharedTypes.ts +++ b/src/shared/types/SharedTypes.ts @@ -1,5 +1,6 @@ export type SharedStoreType = { - openSidebar: boolean; - setOpenSidebar: (value: boolean) => void; - }; - \ No newline at end of file + openSidebar: boolean; + setOpenSidebar: (value: boolean) => void; + slug: string; + setSlug: (value: string) => void; +};