popover account + popover cart
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Popover({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
||||
}
|
||||
|
||||
function PopoverTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
||||
}
|
||||
|
||||
function PopoverContent({
|
||||
className,
|
||||
align = "center",
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||
return (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
data-slot="popover-content"
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function PopoverAnchor({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||
+2
-2
@@ -59,14 +59,14 @@ export const setRefreshToken = (refreshToken: string) => {
|
||||
localStorage.setItem(REFRESH_TOKEN_NAME, refreshToken);
|
||||
};
|
||||
|
||||
export const removeToken = () => {
|
||||
export const removeToken = async () => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
localStorage.removeItem(TOKEN_NAME);
|
||||
};
|
||||
|
||||
export const removeRefreshToken = () => {
|
||||
export const removeRefreshToken = async () => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
+6
-11
@@ -1,18 +1,18 @@
|
||||
'use client'
|
||||
import Input from '@/components/Input'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { DocumentText, Home, Profile, ShoppingCart, HambergerMenu } from 'iconsax-react'
|
||||
import { DocumentText, Home, Profile, HambergerMenu } from 'iconsax-react'
|
||||
import { FC, Fragment, useEffect, useState } from 'react'
|
||||
import Menu from './components/Menu'
|
||||
import { useSharedStore } from './store/sharedStore'
|
||||
import { getToken } from '@/config/func'
|
||||
import { useGetProfile } from '@/app/profile/hooks/useProfileData'
|
||||
import Account from './components/Account'
|
||||
import Cart from './components/Cart'
|
||||
|
||||
const Header: FC = () => {
|
||||
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
||||
const { setIsLogin } = useSharedStore()
|
||||
const { data: profile } = useGetProfile()
|
||||
|
||||
const handleSetIsLogin = async () => {
|
||||
const token = await getToken()
|
||||
@@ -49,11 +49,8 @@ const Header: FC = () => {
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-1 sm:gap-2 md:gap-6'>
|
||||
<div className='hidden md:flex h-10 border border-border py-3 px-4 rounded-[12px] items-center gap-2.5'>
|
||||
<Profile size={20} color='#333333' />
|
||||
{
|
||||
<div className='text-sm text-[#333333]'>{profile?.results?.info?.fullName ? profile?.results?.info?.fullName : 'حساب کاربری'}</div>
|
||||
}
|
||||
<div>
|
||||
<Account />
|
||||
</div>
|
||||
|
||||
<div className='md:hidden'>
|
||||
@@ -62,9 +59,7 @@ const Header: FC = () => {
|
||||
|
||||
<Separator className='hidden md:block' style={{ height: 25 }} orientation='vertical' />
|
||||
|
||||
<div className='h-10 p-3 rounded-[12px] border border-border flex items-center gap-2.5'>
|
||||
<ShoppingCart size={20} color='#333333' />
|
||||
</div>
|
||||
<Cart />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { useGetProfile } from '@/app/profile/hooks/useProfileData'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover"
|
||||
import { PRIMARY_COLOR } from '@/config/const'
|
||||
import { removeToken, removeRefreshToken } from '@/config/func'
|
||||
import { User, Heart, MessageText, Logout, SmsNotification, Profile } from 'iconsax-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
const Account = () => {
|
||||
|
||||
const { data: profile, isSuccess } = useGetProfile()
|
||||
const router = useRouter()
|
||||
|
||||
const handleLogout = async () => {
|
||||
await removeToken()
|
||||
await removeRefreshToken()
|
||||
router.push('/auth')
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
id: 'profile',
|
||||
title: 'پروفایل کاربری',
|
||||
icon: User,
|
||||
href: '/profile',
|
||||
isActive: false
|
||||
},
|
||||
{
|
||||
id: 'favorites',
|
||||
title: 'علاقهمندیها',
|
||||
icon: Heart,
|
||||
href: '/profile/favorite'
|
||||
},
|
||||
{
|
||||
id: 'comments',
|
||||
title: 'نظرات من',
|
||||
icon: SmsNotification,
|
||||
href: '/profile/comments'
|
||||
},
|
||||
{
|
||||
id: 'chat',
|
||||
title: 'گفتگو',
|
||||
icon: MessageText,
|
||||
href: '/profile/chat'
|
||||
},
|
||||
{
|
||||
id: 'logout',
|
||||
title: 'خروج از حساب',
|
||||
icon: Logout,
|
||||
onClick: handleLogout
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
|
||||
<PopoverTrigger asChild>
|
||||
|
||||
<div className='hidden md:flex h-10 border border-border py-3 cursor-pointer px-4 rounded-[12px] items-center gap-2.5'>
|
||||
<Profile size={20} color='#333333' />
|
||||
<div className='text-sm text-[#333333] '>{profile?.results?.info?.fullName ? profile?.results?.info?.fullName : 'حساب کاربری'}</div>
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
{
|
||||
isSuccess && (
|
||||
<PopoverContent className="w-[200px] z-[9999] p-0" style={{ left: 4 }}>
|
||||
<div className="py-2">
|
||||
{menuItems.map((item) => {
|
||||
const IconComponent = item.icon
|
||||
|
||||
if (item.id === 'logout') {
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={item.onClick}
|
||||
className="flex items-center gap-3 px-4 py-3 hover:bg-gray-50 transition-colors w-full text-right text-gray-700"
|
||||
>
|
||||
<IconComponent
|
||||
size={20}
|
||||
variant="Outline"
|
||||
color="#666"
|
||||
className="text-gray-700"
|
||||
/>
|
||||
<span className="text-sm font-medium">{item.title}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={item.href || '#'}
|
||||
className={`flex items-center gap-3 px-4 py-3 hover:bg-gray-50 transition-colors ${item.isActive ? 'text-primary' : 'text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<IconComponent
|
||||
size={20}
|
||||
variant="Outline"
|
||||
color={item.isActive ? PRIMARY_COLOR : '#666'}
|
||||
className={item.isActive ? 'text-primary' : 'text-gray-700'}
|
||||
/>
|
||||
<span className="text-sm font-medium">{item.title}</span>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
)
|
||||
}
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default Account
|
||||
@@ -0,0 +1,127 @@
|
||||
import { ShoppingCart, Trash } from 'iconsax-react'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover"
|
||||
import { useGetCart } from '@/app/cart/hooks/useCartData'
|
||||
import { useRemoveFromCart } from '@/app/cart/hooks/useCartData'
|
||||
import { CartItemType } from '@/app/cart/types/Types'
|
||||
import { NumberFormat } from '@/config/func'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const Cart = () => {
|
||||
const { data: cartData, isLoading } = useGetCart()
|
||||
const removeFromCartMutation = useRemoveFromCart()
|
||||
|
||||
const handleRemoveItem = async (productId: number, variantId: string) => {
|
||||
try {
|
||||
await removeFromCartMutation.mutateAsync({
|
||||
productId,
|
||||
variantId
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error removing item from cart:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const cart = cartData?.results?.cart
|
||||
const items = cart?.items || []
|
||||
const itemsCount = cart?.items_count || 0
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div className='h-10 p-3 rounded-[12px] border border-border flex items-center gap-2.5 relative'>
|
||||
<ShoppingCart size={20} color='#333333' />
|
||||
{itemsCount > 0 && (
|
||||
<div className='absolute -top-2 -right-2 bg-primary text-white text-xs rounded-full w-5 h-5 flex items-center justify-center'>
|
||||
{itemsCount}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[350px] ml-2 z-[9999] p-0 max-h-[600px] overflow-y-auto" style={{ left: 4 }}>
|
||||
<div className='p-4'>
|
||||
{/* Header */}
|
||||
{
|
||||
itemsCount > 0 && (
|
||||
<div className='text-primary text-sm font-medium mb-4'>
|
||||
{itemsCount} کالا در سبد خرید شما ثبت شده
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{/* Cart Items */}
|
||||
<div className='space-y-3 mb-4'>
|
||||
{isLoading ? (
|
||||
<div className='text-center py-4 text-gray-500'>در حال بارگذاری...</div>
|
||||
) : items.length > 0 ? (
|
||||
items.slice(0, 3).map((item: CartItemType) => (
|
||||
<CartItemCard
|
||||
key={`${item.product._id}-${item.variant._id}`}
|
||||
item={item}
|
||||
onRemove={() => handleRemoveItem(item.product._id, item.variant._id)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className='text-center py-4 text-gray-500'>سبد خرید شما خالی است</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* View Cart Button */}
|
||||
{itemsCount > 0 && (
|
||||
<Link href="/cart" className='block'>
|
||||
<Button className='w-full bg-primary hover:bg-primary/90 text-white rounded-lg py-3'>
|
||||
مشاهده سبد خرید
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
// Cart Item Card Component
|
||||
const CartItemCard = ({ item, onRemove }: { item: CartItemType, onRemove: () => void }) => {
|
||||
return (
|
||||
<div className='bg-white rounded-lg p-3 flex gap-3 shadow-sm border border-gray-100'>
|
||||
{/* Product Image */}
|
||||
<div className='flex-shrink-0'>
|
||||
<Image
|
||||
src={item.product.imagesUrl.cover}
|
||||
alt={item.product.title_fa}
|
||||
width={60}
|
||||
height={60}
|
||||
className='w-15 h-15 object-contain rounded'
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Product Details */}
|
||||
<div className='flex-1 min-w-0'>
|
||||
<div className='text-xs text-gray-500 mb-1'>
|
||||
{item.product.category?._id || 'دستهبندی'}
|
||||
</div>
|
||||
<div className='text-sm text-gray-800 mb-2 line-clamp-2'>
|
||||
{item.product.title_fa}
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='text-sm font-semibold text-gray-900'>
|
||||
{NumberFormat(item.variant.price.selling_price)} تومان
|
||||
</div>
|
||||
<button
|
||||
onClick={onRemove}
|
||||
className='p-1 hover:bg-red-100 rounded'
|
||||
>
|
||||
<Trash size={16} color='#AD3434' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Cart
|
||||
Reference in New Issue
Block a user