user in header
This commit is contained in:
Generated
+259
-3313
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@liara/cli": "^9.4.7",
|
"@headlessui/react": "^2.2.10",
|
||||||
"@radix-ui/react-switch": "^1.2.6",
|
"@radix-ui/react-switch": "^1.2.6",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
|
|||||||
+2
-1
@@ -2,7 +2,8 @@
|
|||||||
"sidebar": {
|
"sidebar": {
|
||||||
"menu": "منو",
|
"menu": "منو",
|
||||||
"home_page": "صفحه اصلی",
|
"home_page": "صفحه اصلی",
|
||||||
"catalog": "کاتالوگ"
|
"catalog": "کاتالوگ",
|
||||||
|
"logout": "خروج"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "جستجو",
|
"search": "جستجو",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { getToken } from "@/config/func";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import * as api from "../service/BusinessService";
|
import * as api from "../service/BusinessService";
|
||||||
|
|
||||||
@@ -16,3 +17,11 @@ export const useGetBusinessBySlug = (slug: string) => {
|
|||||||
enabled: !!slug,
|
enabled: !!slug,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetBusiness = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["mybusiness"],
|
||||||
|
queryFn: api.getBusiness,
|
||||||
|
enabled: !!getToken(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import axios from "@/config/axios";
|
import axios from "@/config/axios";
|
||||||
import type { CatalogResponseType } from "@/pages/catalogue/types/Types";
|
import type { CatalogResponseType } from "@/pages/catalogue/types/Types";
|
||||||
|
import type { BusinessResponseType } from "../types/Types";
|
||||||
|
|
||||||
export const getBusinessCatalog = async (slug: string) => {
|
export const getBusinessCatalog = async (slug: string) => {
|
||||||
const { data } = await axios.get<CatalogResponseType>(
|
const { data } = await axios.get<CatalogResponseType>(
|
||||||
@@ -12,3 +13,8 @@ export const getBusinessBySlug = async (slug: string) => {
|
|||||||
const { data } = await axios.get(`/public/business/${slug}`);
|
const { data } = await axios.get(`/public/business/${slug}`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getBusiness = async () => {
|
||||||
|
const { data } = await axios.get<BusinessResponseType>(`/admin/business/me`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import type { BaseResponse } from "@/shared/types/SharedTypes";
|
||||||
|
|
||||||
|
export type AdminType = {
|
||||||
|
business: string;
|
||||||
|
createdAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
firstName: string;
|
||||||
|
id: string;
|
||||||
|
lastName: string;
|
||||||
|
phone: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BusinessType = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
logoUrl: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
admin: AdminType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BusinessResponseType = BaseResponse<BusinessType>;
|
||||||
|
|
||||||
|
export const getAdminFullName = (admin?: AdminType | null): string =>
|
||||||
|
[admin?.firstName, admin?.lastName].filter(Boolean).join(" ");
|
||||||
+9
-71
@@ -1,11 +1,10 @@
|
|||||||
import Input from "@/components/Input";
|
import Input from "@/components/Input";
|
||||||
import { type FC, useEffect } from "react";
|
|
||||||
// import { ArrowDown2, CloseCircle, HambergerMenu, Logout, Wallet } from 'iconsax-react'
|
|
||||||
// import AvatarImage from '../assets/images/avatar_image.png'
|
|
||||||
import { Paths } from "@/config/Paths";
|
import { Paths } from "@/config/Paths";
|
||||||
import { clx } from "@/helpers/utils";
|
import { clx } from "@/helpers/utils";
|
||||||
|
import HeaderUserMenu from "@/shared/components/HeaderUserMenu";
|
||||||
import { useSharedStore } from "@/shared/store/sharedStore";
|
import { useSharedStore } from "@/shared/store/sharedStore";
|
||||||
import { Eye, HambergerMenu } from "iconsax-react";
|
import { Eye, HambergerMenu } from "iconsax-react";
|
||||||
|
import { type FC, useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link, useLocation } from "react-router-dom";
|
||||||
|
|
||||||
@@ -34,8 +33,6 @@ const Header: FC<HeaderProps> = ({
|
|||||||
hasMainSidebar = true,
|
hasMainSidebar = true,
|
||||||
sidebarSize = "default",
|
sidebarSize = "default",
|
||||||
}) => {
|
}) => {
|
||||||
// const location = useLocation();
|
|
||||||
// const [popoverKey, setPopoverKey] = useState(0);
|
|
||||||
const { t } = useTranslation("global");
|
const { t } = useTranslation("global");
|
||||||
const {
|
const {
|
||||||
setOpenSidebar,
|
setOpenSidebar,
|
||||||
@@ -45,22 +42,13 @@ const Header: FC<HeaderProps> = ({
|
|||||||
editorCatalogSlug,
|
editorCatalogSlug,
|
||||||
} = useSharedStore();
|
} = useSharedStore();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const editorPathPrefix = `${Paths.editor}/`;
|
const editorPathPrefix = `${Paths.editor}/`;
|
||||||
const isEditorRoute =
|
const isEditorRoute =
|
||||||
location.pathname.startsWith(editorPathPrefix) &&
|
location.pathname.startsWith(editorPathPrefix) &&
|
||||||
location.pathname.length > editorPathPrefix.length;
|
location.pathname.length > editorPathPrefix.length;
|
||||||
const viewerSlug = isEditorRoute ? (editorCatalogSlug ?? "") : "";
|
const viewerSlug = isEditorRoute ? (editorCatalogSlug ?? "") : "";
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// setPopoverKey((prevKey) => prevKey + 1);
|
|
||||||
// }, [location.pathname]);
|
|
||||||
|
|
||||||
// const handleLogout = () => {
|
|
||||||
// removeToken()
|
|
||||||
// removeRefreshToken()
|
|
||||||
// window.location.href = Pages.auth.login
|
|
||||||
// }
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSearch("");
|
setSearch("");
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -83,13 +71,15 @@ const Header: FC<HeaderProps> = ({
|
|||||||
onChangeSearchFinal={(value) => setSearch(value)}
|
onChangeSearchFinal={(value) => setSearch(value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
onClick={() => setOpenSidebar(!openSidebar)}
|
onClick={() => setOpenSidebar(!openSidebar)}
|
||||||
className="xl:hidden block"
|
className="xl:hidden block"
|
||||||
>
|
>
|
||||||
<HambergerMenu size={24} color="black" />
|
<HambergerMenu size={24} color="black" />
|
||||||
</div>
|
</button>
|
||||||
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */}
|
|
||||||
<div className="flex xl:gap-6 gap-4 items-center">
|
<div className="flex xl:gap-6 gap-4 items-center">
|
||||||
{isEditorRoute && viewerSlug ? (
|
{isEditorRoute && viewerSlug ? (
|
||||||
<Link
|
<Link
|
||||||
@@ -98,64 +88,12 @@ const Header: FC<HeaderProps> = ({
|
|||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
aria-label={t("header.open_viewer")}
|
aria-label={t("header.open_viewer")}
|
||||||
title={t("header.open_viewer")}
|
title={t("header.open_viewer")}
|
||||||
// onClick={requestViewerFullscreen}
|
|
||||||
>
|
>
|
||||||
<Eye size={20} color="black" />
|
<Eye size={20} color="black" />
|
||||||
</Link>
|
</Link>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* <Link className='xl:hidden' to={Paths.wallet}>
|
<HeaderUserMenu />
|
||||||
<Wallet className='xl:size-[18px] size-[17px]' color='#da2129' />
|
|
||||||
</Link> */}
|
|
||||||
{/* <Notifications /> */}
|
|
||||||
{/* {
|
|
||||||
data && (
|
|
||||||
<Popover className="relative" key={popoverKey}>
|
|
||||||
<PopoverButton >
|
|
||||||
<div className='flex gap-2 items-center mt-2.5'>
|
|
||||||
<div className='size-6 rounded-full bg-description overflow-hidden'>
|
|
||||||
<img src={data?.data?.user?.profilePic ? data?.data?.user?.profilePic : AvatarImage} className='size-full object-cover' />
|
|
||||||
</div>
|
|
||||||
<div className='xl:flex hidden gap-1 items-center'>
|
|
||||||
<div className='text-xs'>
|
|
||||||
{data?.data?.user?.firstName + ' ' + data?.data?.user?.lastName}
|
|
||||||
</div>
|
|
||||||
<ArrowDown2 size={14} color='#8C90A3' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverButton>
|
|
||||||
|
|
||||||
<PopoverPanel style={{ minHeight: window.innerWidth < 1140 ? window.innerHeight : undefined }} anchor="bottom" className="flex xl:ml-6 overflow-auto flex-col gap-3 bg-white boxShadow xl:mt-7 z-30 py-4 text-xs rounded-2.5 xl:w-[300px] -mt-[50px] w-full fixed xl:h-fit h-full shadow-md">
|
|
||||||
<div className='absolute xl:hidden top-6 left-6'>
|
|
||||||
<CloseCircle onClick={() => setPopoverKey((prevKey) => prevKey + 1)} size={20} color='#da2129' />
|
|
||||||
</div>
|
|
||||||
<Link to={Pages.profile} className='flex flex-col gap-2 items-center justify-center border-b border-[#EAEDF5] pb-4'>
|
|
||||||
<div className='size-14 rounded-full overflow-hidden'>
|
|
||||||
<img src={data?.data?.user?.profilePic ? data?.data?.user?.profilePic : AvatarImage} className='size-full object-cover' />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{data?.data?.user?.firstName + ' ' + data?.data?.user?.lastName}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='text-description'>
|
|
||||||
{data?.data?.user?.email}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div className='px-6 mt-2'>
|
|
||||||
<div onClick={() => handleLogout()} className='flex gap-2 items-center cursor-pointer'>
|
|
||||||
<Logout size={20} color='#da2129' />
|
|
||||||
<div>
|
|
||||||
{t('sidebar.logout')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</PopoverPanel>
|
|
||||||
</Popover>
|
|
||||||
)
|
|
||||||
} */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { removeRefreshToken, removeToken } from "@/config/func";
|
||||||
|
import { Paths } from "@/config/Paths";
|
||||||
|
import { useAuth } from "@/context/AuthContext";
|
||||||
|
import { useGetBusiness } from "@/pages/business/hooks/useBusinessData";
|
||||||
|
import {
|
||||||
|
getAdminFullName,
|
||||||
|
type BusinessType,
|
||||||
|
} from "@/pages/business/types/Types";
|
||||||
|
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||||
|
import { ArrowDown2, CloseCircle, Logout } from "iconsax-react";
|
||||||
|
import { useState, type FC } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
type BusinessLogoProps = {
|
||||||
|
business: BusinessType;
|
||||||
|
size?: "sm" | "lg";
|
||||||
|
};
|
||||||
|
|
||||||
|
const BusinessLogo: FC<BusinessLogoProps> = ({ business, size = "sm" }) => {
|
||||||
|
const initial = business.name.trim().charAt(0).toUpperCase();
|
||||||
|
const sizeClass = size === "lg" ? "size-14 text-xl" : "size-6 text-[11px]";
|
||||||
|
|
||||||
|
if (business.logoUrl) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${sizeClass} rounded-full overflow-hidden bg-description`}
|
||||||
|
>
|
||||||
|
<img src={business.logoUrl} className="size-full object-cover" alt="" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${sizeClass} rounded-full pt-[2px] bg-secondary flex items-center justify-center font-medium text-desc leading-none select-none`}
|
||||||
|
>
|
||||||
|
{initial}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeaderUserMenu: FC = () => {
|
||||||
|
const [popoverKey, setPopoverKey] = useState(0);
|
||||||
|
const { t } = useTranslation("global");
|
||||||
|
const { data: businessResponse } = useGetBusiness();
|
||||||
|
const { refreshAuth } = useAuth();
|
||||||
|
|
||||||
|
const business = businessResponse?.data;
|
||||||
|
const admin = business?.admin;
|
||||||
|
const adminName = getAdminFullName(admin);
|
||||||
|
|
||||||
|
if (!business) return null;
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
removeToken();
|
||||||
|
removeRefreshToken();
|
||||||
|
refreshAuth();
|
||||||
|
window.location.href = Paths.home;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeMobilePanel = () => setPopoverKey((key) => key + 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover className="relative" key={popoverKey}>
|
||||||
|
<PopoverButton className="flex items-center gap-2 outline-none">
|
||||||
|
<BusinessLogo business={business} />
|
||||||
|
<div className="hidden sm:flex items-center gap-1">
|
||||||
|
<span className="text-xs">{adminName}</span>
|
||||||
|
<ArrowDown2 size={14} color="#8C90A3" />
|
||||||
|
</div>
|
||||||
|
</PopoverButton>
|
||||||
|
|
||||||
|
<PopoverPanel
|
||||||
|
style={{
|
||||||
|
minHeight: window.innerWidth < 1140 ? window.innerHeight : undefined,
|
||||||
|
}}
|
||||||
|
anchor="bottom"
|
||||||
|
className="flex xl:ml-6 overflow-auto flex-col gap-3 bg-white boxShadow xl:mt-7 z-30 py-4 text-xs rounded-2.5 xl:w-[300px] -mt-[50px] w-full fixed xl:h-fit h-full shadow-md"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute xl:hidden top-6 left-6"
|
||||||
|
onClick={closeMobilePanel}
|
||||||
|
>
|
||||||
|
<CloseCircle size={20} color="#da2129" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
to={`${Paths.catalog.business}${business.slug}`}
|
||||||
|
className="flex flex-col gap-2 items-center justify-center border-b border-[#EAEDF5] pb-4"
|
||||||
|
>
|
||||||
|
<BusinessLogo business={business} size="lg" />
|
||||||
|
<div>{adminName}</div>
|
||||||
|
<div className="text-description">{business.name}</div>
|
||||||
|
{admin?.phone ? (
|
||||||
|
<div className="text-description">{admin.phone}</div>
|
||||||
|
) : null}
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className="px-6 mt-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="flex gap-2 items-center cursor-pointer"
|
||||||
|
>
|
||||||
|
<Logout size={20} color="#da2129" />
|
||||||
|
<span>{t("sidebar.logout")}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</PopoverPanel>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderUserMenu;
|
||||||
Reference in New Issue
Block a user