Compare commits

6 Commits

Author SHA1 Message Date
morteza 3d12fe6c4d updat api url
deploy to danak / build_and_deploy (push) Has been cancelled
2026-07-01 22:24:40 +03:30
morteza ce10d3ea57 update api url
deploy to danak / build_and_deploy (push) Has been cancelled
2026-06-14 14:45:48 +03:30
morteza 0e2ce1210d remove tz
deploy to danak / build_and_deploy (push) Has been cancelled
2026-06-14 10:32:05 +03:30
hamid zarghami bba527c83f logo base url
deploy to danak / build_and_deploy (push) Has been cancelled
2026-02-24 14:36:13 +03:30
Mehdi Mehrmanesh 8bc5930bd6 add gitlab-ci file 2026-02-21 13:30:00 +03:30
hamid zarghami 38ffe6cd43 name and color 2026-02-21 10:14:46 +03:30
20 changed files with 97 additions and 171 deletions
-13
View File
@@ -1,13 +0,0 @@
node_modules
.next
.git
.gitignore
*.md
.env*
npm-debug.log*
.DS_Store
coverage
.cursor
.idea
.vscode
*.log
+3 -5
View File
@@ -3,7 +3,7 @@ name: deploy to danak
on: on:
push: push:
branches: branches:
- master - shinan
jobs: jobs:
build_and_deploy: build_and_deploy:
@@ -11,10 +11,8 @@ jobs:
env: env:
DANAK_SERVER: "https://captain.dev.danakcorp.com" DANAK_SERVER: "https://captain.dev.danakcorp.com"
APP_TOKEN: 230cbe758f711c2b12099fce48ef8d4f2212ac6dc89cb300e92a9b8a45133669 APP_TOKEN: 9d1dd69b08c1d77b36092597316c44288126c57f217d87308130c98d764bf891
APP_NAME: shop APP_NAME: shinan
# APP_TOKEN: 111ac60c637716478be8ab8b888fd20baba808a10b0fc0c18bf788766883ec9e
# APP_NAME: e-commerce-front
GITHUB_TOKEN: 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 GITHUB_TOKEN: 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
steps: steps:
+26 -39
View File
@@ -1,72 +1,59 @@
# syntax=docker/dockerfile:1
# ------------------------ # ------------------------
# Dependencies # Base image
# ------------------------ # ------------------------
FROM node:22-alpine AS deps FROM node:22-alpine AS base
RUN sed -i 's|https://dl-cdn.alpinelinux.org/alpine|https://mirror.arvancloud.ir/alpine|g' /etc/apk/repositories \
&& npm config set registry https://package-mirror.liara.ir/repository/npm/
ENV NPM_CONFIG_REGISTRY=https://package-mirror.liara.ir/repository/npm/
RUN apk add --no-cache libc6-compat git
WORKDIR /app WORKDIR /app
COPY package.json package-lock.json ./ # ------------------------
# Dependencies stage
# ------------------------
FROM base AS deps
RUN --mount=type=cache,target=/root/.npm \ RUN apk add --no-cache libc6-compat git
npm ci
COPY package*.json ./
RUN npm ci
# ------------------------ # ------------------------
# Builder # Builder stage
# ------------------------ # ------------------------
FROM node:22-alpine AS builder FROM base AS builder
RUN sed -i 's|https://dl-cdn.alpinelinux.org/alpine|https://mirror.arvancloud.ir/alpine|g' /etc/apk/repositories \
&& npm config set registry https://package-mirror.liara.ir/repository/npm/
ENV NPM_CONFIG_REGISTRY=https://package-mirror.liara.ir/repository/npm/
WORKDIR /build WORKDIR /build
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
ENV NEXT_TELEMETRY_DISABLED=1 RUN NEXT_TELEMETRY_DISABLED=1 npm run build -- --no-lint || true
RUN npm run build
# ------------------------ # ------------------------
# Runner (production) # Runner stage (production)
# ------------------------ # ------------------------
FROM node:22-alpine AS runner FROM base AS runner
RUN sed -i 's|https://dl-cdn.alpinelinux.org/alpine|https://mirror.arvancloud.ir/alpine|g' /etc/apk/repositories \
&& npm config set registry https://package-mirror.liara.ir/repository/npm/
ENV NPM_CONFIG_REGISTRY=https://package-mirror.liara.ir/repository/npm/
WORKDIR /app WORKDIR /app
ENV NODE_ENV=production \ ENV NODE_ENV=production
NEXT_TELEMETRY_DISABLED=1 \ ENV NEXT_TELEMETRY_DISABLED=1
PORT=3000 \ ENV PORT=3000
HOSTNAME=0.0.0.0 \ ENV HOSTNAME="0.0.0.0"
TZ=Asia/Tehran
RUN addgroup -S appgroup \ RUN addgroup -S appgroup && adduser -S appuser -G appgroup
&& adduser -S appuser -G appgroup \
&& mkdir .next \ RUN mkdir .next && chown appuser:appgroup .next
&& chown appuser:appgroup .next
COPY --from=builder --chown=appuser:appgroup /build/.next/standalone ./ COPY --from=builder --chown=appuser:appgroup /build/.next/standalone ./
COPY --from=builder --chown=appuser:appgroup /build/.next/static ./.next/static COPY --from=builder --chown=appuser:appgroup /build/.next/static ./.next/static
COPY --from=builder --chown=appuser:appgroup /build/public ./public COPY --from=builder --chown=appuser:appgroup /build/public ./public
COPY --from=builder --chown=appuser:appgroup /build/package.json ./package.json
USER appuser USER appuser
# expose port
EXPOSE 3000 EXPOSE 3000
CMD ["node", "server.js"] CMD ["node", "server.js"]
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 550 KiB

@@ -273,11 +273,6 @@ const ShippingMethodSelector = ({
ارزانترین ارزانترین
</span> </span>
)} )}
{shipper.payDeliveryFeeOnDelivery && (
<span className="text-xs bg-amber-100 text-amber-800 px-1.5 py-0.5 rounded flex-shrink-0">
پرداخت در محل
</span>
)}
</div> </div>
<div className="text-sm text-gray-600"> <div className="text-sm text-gray-600">
زمان ارسال: {shipper.shippingDaysRange?.join(' تا ') || 'نامشخص'} زمان ارسال: {shipper.shippingDaysRange?.join(' تا ') || 'نامشخص'}
@@ -287,24 +282,14 @@ const ShippingMethodSelector = ({
<div className="flex items-center justify-end sm:justify-start"> <div className="flex items-center justify-end sm:justify-start">
<div className="font-medium text-gray-900 relative group text-right"> <div className="font-medium text-gray-900 relative group text-right">
{shipper.payDeliveryFeeOnDelivery ? ( {shipper.totalShippingCost.toLocaleString('fa-IR')} تومان
<span className="text-amber-700"> {priceDifference > 0 && (
<div className="text-xs text-orange-600 mt-1">
</span> +{priceDifference.toLocaleString('fa-IR')} تومان
) : ( </div>
<>
{shipper.totalShippingCost.toLocaleString('fa-IR')} تومان
{priceDifference > 0 && (
<div className="text-xs text-orange-600 mt-1">
+{priceDifference.toLocaleString('fa-IR')} تومان
</div>
)}
</>
)} )}
<div className="absolute bottom-full right-0 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-10"> <div className="absolute bottom-full right-0 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-10">
{shipper.payDeliveryFeeOnDelivery هزینه ارسال کل این فروشگاه
? 'هزینه ارسال هنگام تحویل پرداخت می‌شود'
: 'هزینه ارسال کل این فروشگاه'}
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -52,7 +52,7 @@ const InvoicePage = () => {
<div className="flex items-center justify-between border-b pb-4 mb-6"> <div className="flex items-center justify-between border-b pb-4 mb-6">
<div> <div>
<Image <Image
src="/images/logo.png" src="/images/logo.svg"
alt="لوگو" alt="لوگو"
width={120} width={120}
height={40} height={40}
+5 -19
View File
@@ -6,7 +6,6 @@ import TitleBack from '../components/TitleBack'
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import DiscountCard from '../components/DiscountCard' import DiscountCard from '../components/DiscountCard'
import { usePaymentMethods, useGetCart, useShippingCost, useCheckoutCart } from '../hooks/useCartData' import { usePaymentMethods, useGetCart, useShippingCost, useCheckoutCart } from '../hooks/useCartData'
import { ShopShippingType } from '../types/Types'
import { useState } from 'react' import { useState } from 'react'
import { toast } from '@/components/Toast' import { toast } from '@/components/Toast'
import { useGetProfile } from '@/app/profile/hooks/useProfileData' import { useGetProfile } from '@/app/profile/hooks/useProfileData'
@@ -51,24 +50,12 @@ const CartPayment: NextPage = () => {
const savedShipments = typeof window !== 'undefined' ? localStorage.getItem('selectedShipments') : null const savedShipments = typeof window !== 'undefined' ? localStorage.getItem('selectedShipments') : null
const selectedShipments = savedShipments ? JSON.parse(savedShipments) : {} const selectedShipments = savedShipments ? JSON.parse(savedShipments) : {}
const getSelectedShipper = (shop: ShopShippingType) => { // محاسبه هزینه ارسال بر اساس انتخاب‌های کاربر
const totalShippingCost = shippingData?.results?.shipping?.reduce((total, shop) => {
const selectedShipperId = selectedShipments[shop.shopId] const selectedShipperId = selectedShipments[shop.shopId]
return shop.shippers?.find(shipper => shipper.shipperId === selectedShipperId) || shop.shippers?.[0] const selectedShipper = shop.shippers?.find(shipper => shipper.shipperId === selectedShipperId)
} return total + (selectedShipper?.totalShippingCost || shop.shippers?.[0]?.totalShippingCost || 0)
}, 0) || 0
const shippingTotals = shippingData?.results?.shipping?.reduce((totals, shop) => {
const shipper = getSelectedShipper(shop)
if (!shipper) return totals
if (shipper.payDeliveryFeeOnDelivery) {
totals.onDelivery += shipper.totalShippingCost
} else {
totals.online += shipper.totalShippingCost
}
return totals
}, { online: 0, onDelivery: 0 }) || { online: 0, onDelivery: 0 }
const totalShippingCost = shippingTotals.online
const shippingCostOnDelivery = shippingTotals.onDelivery
// محاسبه قیمت نهایی // محاسبه قیمت نهایی
const finalTotal = (cart?.payable_price || 0) + totalShippingCost const finalTotal = (cart?.payable_price || 0) + totalShippingCost
@@ -145,7 +132,6 @@ const CartPayment: NextPage = () => {
itemsPrice={cart?.retail_price || 0} itemsPrice={cart?.retail_price || 0}
discount={cart.total_discount} discount={cart.total_discount}
shippingCost={totalShippingCost} shippingCost={totalShippingCost}
shippingCostOnDelivery={shippingCostOnDelivery}
total={finalTotal} total={finalTotal}
onConfirm={handlePayment} onConfirm={handlePayment}
confirmLabel="پرداخت" confirmLabel="پرداخت"
+4 -19
View File
@@ -7,7 +7,6 @@ import TitleBack from '../components/TitleBack'
import ShippingMethodSelector from '../components/ShippingMethodSelector' import ShippingMethodSelector from '../components/ShippingMethodSelector'
import { Location } from 'iconsax-react' import { Location } from 'iconsax-react'
import { useGetCart, useSaveShipmentCart, useShippingCost } from '../hooks/useCartData' import { useGetCart, useSaveShipmentCart, useShippingCost } from '../hooks/useCartData'
import { ShopShippingType } from '../types/Types'
// import { CartItemType } from '../types/Types' // import { CartItemType } from '../types/Types'
import { useGetProfile } from '@/app/profile/hooks/useProfileData' import { useGetProfile } from '@/app/profile/hooks/useProfileData'
import Link from 'next/link' import Link from 'next/link'
@@ -144,24 +143,11 @@ const CartShipping: NextPage = () => {
} }
// محاسبه هزینه ارسال بر اساس انتخاب‌های کاربر // محاسبه هزینه ارسال بر اساس انتخاب‌های کاربر
const getSelectedShipper = (shop: ShopShippingType) => { const totalShippingCost = shippingData?.results?.shipping?.reduce((total, shop) => {
const selectedShipperId = selectedShipments[shop.shopId] const selectedShipperId = selectedShipments[shop.shopId]
return shop.shippers?.find(shipper => shipper.shipperId === selectedShipperId) || shop.shippers?.[0] const selectedShipper = shop.shippers?.find(shipper => shipper.shipperId === selectedShipperId)
} return total + (selectedShipper?.totalShippingCost || shop.shippers?.[0]?.totalShippingCost || 0)
}, 0) || 0
const shippingTotals = shippingData?.results?.shipping?.reduce((totals, shop) => {
const shipper = getSelectedShipper(shop)
if (!shipper) return totals
if (shipper.payDeliveryFeeOnDelivery) {
totals.onDelivery += shipper.totalShippingCost
} else {
totals.online += shipper.totalShippingCost
}
return totals
}, { online: 0, onDelivery: 0 }) || { online: 0, onDelivery: 0 }
const totalShippingCost = shippingTotals.online
const shippingCostOnDelivery = shippingTotals.onDelivery
// محاسبه قیمت نهایی // محاسبه قیمت نهایی
const finalTotal = (cart?.payable_price || 0) + totalShippingCost const finalTotal = (cart?.payable_price || 0) + totalShippingCost
@@ -219,7 +205,6 @@ const CartShipping: NextPage = () => {
itemsPrice={cart?.retail_price || 0} itemsPrice={cart?.retail_price || 0}
discount={cart?.total_discount || 0} discount={cart?.total_discount || 0}
shippingCost={totalShippingCost} shippingCost={totalShippingCost}
shippingCostOnDelivery={shippingCostOnDelivery}
total={finalTotal} total={finalTotal}
onConfirm={handleProceedToPayment} onConfirm={handleProceedToPayment}
confirmLabel="پرداخت" confirmLabel="پرداخت"
-2
View File
@@ -60,7 +60,6 @@ export type CartItemType = {
description: string; description: string;
deliveryTime: number; deliveryTime: number;
deliveryType: string; deliveryType: string;
payDeliveryFeeOnDelivery?: boolean;
}>; }>;
warranty: { warranty: {
_id: number; _id: number;
@@ -225,7 +224,6 @@ export type ShipperType = {
shipperName: string; shipperName: string;
shippingDaysRange: string[]; shippingDaysRange: string[];
totalShippingCost: number; totalShippingCost: number;
payDeliveryFeeOnDelivery: boolean;
items: ShippingItemType[]; items: ShippingItemType[];
}; };
+1 -1
View File
@@ -173,7 +173,7 @@ body {
} }
@theme { @theme {
--color-border: #e5e5e5; --color-border: #e5e5e5;
--color-primary: #da2129; --color-primary: #a62186;
} }
html { html {
+1 -8
View File
@@ -42,7 +42,6 @@ const OrderInvoicePage = () => {
const totalDiscount = order.payment.priceDetails.total_discount const totalDiscount = order.payment.priceDetails.total_discount
const couponDiscount = order.payment.priceDetails.coupon_discount const couponDiscount = order.payment.priceDetails.coupon_discount
const shippingCost = order.payment.priceDetails.shipping_cost const shippingCost = order.payment.priceDetails.shipping_cost
const shippingCostOnDelivery = order.payment.priceDetails.shipping_cost_on_delivery || 0
const totalPrice = order.payment.totalPrice const totalPrice = order.payment.totalPrice
return ( return (
@@ -67,7 +66,7 @@ const OrderInvoicePage = () => {
<div className="flex items-center justify-between border-b pb-4 mb-6"> <div className="flex items-center justify-between border-b pb-4 mb-6">
<div> <div>
<Image <Image
src="/images/logo.png" src="/images/logo.svg"
alt="لوگو" alt="لوگو"
width={120} width={120}
height={40} height={40}
@@ -213,12 +212,6 @@ const OrderInvoicePage = () => {
<span>هزینه ارسال:</span> <span>هزینه ارسال:</span>
<span>{NumberFormat(shippingCost)} تومان</span> <span>{NumberFormat(shippingCost)} تومان</span>
</div> </div>
{shippingCostOnDelivery > 0 && (
<div className="flex justify-between text-gray-700">
<span>هزینه ارسال (پرداخت در محل):</span>
{/* <span>{NumberFormat(shippingCostOnDelivery)} تومان</span> */}
</div>
)}
<div className="flex justify-between text-xl font-bold border-t pt-3"> <div className="flex justify-between text-xl font-bold border-t pt-3">
<span>جمع کل:</span> <span>جمع کل:</span>
<span className="text-primary"> <span className="text-primary">
-1
View File
@@ -94,7 +94,6 @@ export interface PaymentMethod {
export interface PriceDetails { export interface PriceDetails {
shipping_cost: number; shipping_cost: number;
shipping_cost_on_delivery?: number;
process_cost: number; process_cost: number;
total_retail_price: number; total_retail_price: number;
total_payable_price: number; total_payable_price: number;
+1 -5
View File
@@ -10,7 +10,6 @@ type CartSummaryProps = {
itemsPrice: number; itemsPrice: number;
discount: number; discount: number;
shippingCost?: number; shippingCost?: number;
shippingCostOnDelivery?: number;
total: number; total: number;
onConfirm?: () => void; onConfirm?: () => void;
confirmHref?: string; confirmHref?: string;
@@ -35,7 +34,7 @@ const Row: FC<{ label: string; value: string | number; emphasize?: boolean }> =
}; };
const CartSummary: FC<CartSummaryProps> = (props) => { const CartSummary: FC<CartSummaryProps> = (props) => {
const { itemsCount, itemsPrice, discount, shippingCost, shippingCostOnDelivery, total, onConfirm, onDownloadInvoice, className, confirmHref, confirmLabel, confirmDisabled, isLoading } = props; const { itemsCount, itemsPrice, discount, shippingCost, total, onConfirm, onDownloadInvoice, className, confirmHref, confirmLabel, confirmDisabled, isLoading } = props;
return ( return (
<div className={"rounded-xl sm:rounded-2xl border border-border p-4 sm:p-6 h-fit bg-[#FAFAFA] " + (className ?? "")}> <div className={"rounded-xl sm:rounded-2xl border border-border p-4 sm:p-6 h-fit bg-[#FAFAFA] " + (className ?? "")}>
@@ -50,9 +49,6 @@ const CartSummary: FC<CartSummaryProps> = (props) => {
{shippingCost !== undefined && shippingCost > 0 && ( {shippingCost !== undefined && shippingCost > 0 && (
<Row label="هزینه ارسال" value={shippingCost} /> <Row label="هزینه ارسال" value={shippingCost} />
)} )}
{shippingCostOnDelivery !== undefined && shippingCostOnDelivery > 0 && (
<Row label="هزینه ارسال (پرداخت در محل)" value={shippingCostOnDelivery} />
)}
<Row label="جمع فاکتور" value={total} emphasize /> <Row label="جمع فاکتور" value={total} emphasize />
</div> </div>
+1 -7
View File
@@ -68,17 +68,11 @@ const Input: FC<Props> = (props: Props) => {
} }
}, [props.value]) }, [props.value])
useEffect(() => {
if (props.variant === 'search') {
setSearch(String(props.value ?? ''))
}
}, [props.value, props.variant])
useEffect(() => { useEffect(() => {
if (props.variant === 'search' && props.onChangeSearchFinal) { if (props.variant === 'search' && props.onChangeSearchFinal) {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
props.onChangeSearchFinal?.(search) props.onChangeSearchFinal?.(search)
}, 500) }, 1000)
return () => clearTimeout(timeout) return () => clearTimeout(timeout)
} }
}, [search, props]) }, [search, props])
+8 -8
View File
@@ -1,14 +1,14 @@
export const TOKEN_NAME = "sh_token"; export const TOKEN_NAME = "shinan_token";
export const REFRESH_TOKEN_NAME = "sh_refresh_token"; export const REFRESH_TOKEN_NAME = "shinan_refresh_token";
export const BASE_URL = "https://api.fajrtabloshop.com"; export const BASE_URL = "https://shinan-api.danakcorp.com";
// export const BASE_URL = "http://192.168.99.235:4000"; // export const BASE_URL = "http://192.168.99.235:4000";
export const PRIMARY_COLOR = "#DA2129"; export const PRIMARY_COLOR = "#a62186";
// تنظیمات فروشگاه // تنظیمات فروشگاه
export const SHOP_CONFIG = { export const SHOP_CONFIG = {
name: "فجر مرکزی", // نام فروشگاه - می‌توان آن را تغییر داد name: "شینان", // نام فروشگاه - می‌توان آن را تغییر داد
fullName: "تابلو فجر مرکزی", // نام کامل فروشگاه fullName: "شینان", // نام کامل فروشگاه
englishName: "Tablo Fajr Center", // نام کامل فروشگاه به زبان انگلیسی englishName: "Shinan", // نام کامل فروشگاه به زبان انگلیسی
description: "فروشگاه آنلاین تابلو فجر مرکزی", // توضیحات فروشگاه description: "فروشگاه آنلاین شینان", // توضیحات فروشگاه
}; };
+1 -1
View File
@@ -20,7 +20,7 @@ const Footer: FC = () => {
width={100} width={100}
height={100} height={100}
alt='logo' alt='logo'
src={siteSetting?.results?.siteSetting?.siteLogo || '/images/logo.png'} src={siteSetting?.results?.siteSetting?.siteLogo || '/images/logo.svg'}
className='w-full h-auto max-h-20 max-w-20 object-contain' className='w-full h-auto max-h-20 max-w-20 object-contain'
/> />
</div> </div>
+2 -2
View File
@@ -34,7 +34,7 @@ const Header: FC = () => {
<div className='md:hidden flex items-center justify-between relative'> <div className='md:hidden flex items-center justify-between relative'>
<Link href='/' className='px-4 py-4'> <Link href='/' className='px-4 py-4'>
<Image <Image
src='/images/logo.png' src='/images/logo.svg'
alt='logo' alt='logo'
width={120} width={120}
height={50} height={50}
@@ -49,7 +49,7 @@ const Header: FC = () => {
<div className='flex gap-[30px] items-center'> <div className='flex gap-[30px] items-center'>
<Link href='/'> <Link href='/'>
<Image <Image
src='/images/logo.png' src='/images/logo.svg'
alt='logo' alt='logo'
width={100} width={100}
height={100} height={100}
+1 -1
View File
@@ -189,7 +189,7 @@ const CartItemCard = ({
const productImage = isOnline const productImage = isOnline
? (item as CartItemType).product.imagesUrl.cover ? (item as CartItemType).product.imagesUrl.cover
: (item as EnrichedOfflineCartItem).product?.imagesUrl?.cover || '/images/logo.png' : (item as EnrichedOfflineCartItem).product?.imagesUrl?.cover || '/images/logo.svg'
const productCategory = isOnline const productCategory = isOnline
? ((item as CartItemType).product.category?._id || 'دسته‌بندی') ? ((item as CartItemType).product.category?._id || 'دسته‌بندی')
+6 -10
View File
@@ -7,7 +7,6 @@ import Image from 'next/image'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
const DesktopSearch: FC = () => { const DesktopSearch: FC = () => {
const [inputValue, setInputValue] = useState('')
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const [mounted, setMounted] = useState(false) const [mounted, setMounted] = useState(false)
@@ -17,14 +16,13 @@ const DesktopSearch: FC = () => {
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, width: 0 }) const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, width: 0 })
const { data: searchResults, isLoading } = useProductSearch(searchQuery) const { data: searchResults, isLoading } = useProductSearch(searchQuery)
const isSearching = inputValue.length > 0 && (isLoading || inputValue !== searchQuery)
useEffect(() => { useEffect(() => {
setMounted(true) setMounted(true)
}, []) }, [])
const handleInputChange = (value: string) => { const handleSearch = (value: string) => {
setInputValue(value) setSearchQuery(value)
setIsOpen(value.length > 0) setIsOpen(value.length > 0)
if (value.length > 0 && inputRef.current) { if (value.length > 0 && inputRef.current) {
@@ -39,7 +37,6 @@ const DesktopSearch: FC = () => {
const handleProductClick = (url: string) => { const handleProductClick = (url: string) => {
router.push(url) router.push(url)
setInputValue('')
setSearchQuery('') setSearchQuery('')
setIsOpen(false) setIsOpen(false)
} }
@@ -82,13 +79,12 @@ const DesktopSearch: FC = () => {
className='w-full' className='w-full'
variant='search' variant='search'
placeholder='جستجو' placeholder='جستجو'
value={inputValue} value={searchQuery}
onChange={(e) => handleInputChange(e.target.value)} onChange={(e) => handleSearch(e.target.value)}
onChangeSearchFinal={setSearchQuery}
/> />
</div> </div>
{mounted && isOpen && inputValue.length > 0 && createPortal( {mounted && isOpen && searchQuery.length > 0 && createPortal(
<div <div
ref={dropdownRef} ref={dropdownRef}
className='fixed bg-white rounded-xl shadow-lg border border-gray-200 max-h-[400px] overflow-y-auto z-[99999]' className='fixed bg-white rounded-xl shadow-lg border border-gray-200 max-h-[400px] overflow-y-auto z-[99999]'
@@ -98,7 +94,7 @@ const DesktopSearch: FC = () => {
width: `${dropdownPosition.width}px` width: `${dropdownPosition.width}px`
}} }}
> >
{isSearching ? ( {isLoading ? (
<div className='p-4 text-center text-gray-500'> <div className='p-4 text-center text-gray-500'>
در حال جستجو... در حال جستجو...
</div> </div>
+8 -8
View File
@@ -8,16 +8,17 @@ import Image from 'next/image'
const MobileSearch: FC = () => { const MobileSearch: FC = () => {
const [isSearchOpen, setIsSearchOpen] = useState(false) const [isSearchOpen, setIsSearchOpen] = useState(false)
const [inputValue, setInputValue] = useState('')
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const router = useRouter() const router = useRouter()
const { data: searchResults, isLoading } = useProductSearch(searchQuery) const { data: searchResults, isLoading } = useProductSearch(searchQuery)
const isSearching = inputValue.length > 0 && (isLoading || inputValue !== searchQuery)
const handleSearch = (value: string) => {
setSearchQuery(value)
}
const handleClose = () => { const handleClose = () => {
setIsSearchOpen(false) setIsSearchOpen(false)
setInputValue('')
setSearchQuery('') setSearchQuery('')
} }
@@ -35,9 +36,8 @@ const MobileSearch: FC = () => {
variant='search' variant='search'
placeholder='جستجو' placeholder='جستجو'
autoFocus autoFocus
value={inputValue} value={searchQuery}
onChange={(e) => setInputValue(e.target.value)} onChange={(e) => handleSearch(e.target.value)}
onChangeSearchFinal={setSearchQuery}
/> />
<button <button
onClick={handleClose} onClick={handleClose}
@@ -48,9 +48,9 @@ const MobileSearch: FC = () => {
</div> </div>
<div className='flex-1 overflow-y-auto'> <div className='flex-1 overflow-y-auto'>
{inputValue.length > 0 && ( {searchQuery.length > 0 && (
<> <>
{isSearching ? ( {isLoading ? (
<div className='p-4 text-center text-gray-500'> <div className='p-4 text-center text-gray-500'>
در حال جستجو... در حال جستجو...
</div> </div>