feat(seller): customizable profile banner + seller page polish
- Banner editor in store edit: color swatches + custom color picker, or image/GIF upload with a 1600x500 dimension hint. Renders the chosen banner (image cover / solid color) on the public seller page, falling back to the default gradient. - Sellers list: drop the desktop name overlay (match mobile, image-only tiles). - Seller page mobile header: chat moved top-left under the banner, search+filter beside the follow button, followers/product counts removed (rating kept), compacted so products fill more of the screen, circular logo, thinner action row. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
bb10907463
commit
660e01c3c3
@ -1,6 +1,6 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useNavigate } from "@remix-run/react";
|
||||
import { Loader2, Save, Store } from "lucide-react";
|
||||
import { Loader2, Save, Store, Upload } from "lucide-react";
|
||||
import ProfilePagesHeader from "../ProfilePagesHeader";
|
||||
import HeaderWithSupport from "../HeaderWithSupport";
|
||||
import AppInput from "../AppInput";
|
||||
@ -80,8 +80,26 @@ interface FormData {
|
||||
selectedCity: string;
|
||||
instagramId: string;
|
||||
storeLogo: string;
|
||||
bannerType: string;
|
||||
bannerColor: string;
|
||||
bannerImage: string;
|
||||
}
|
||||
|
||||
// Preset banner colors offered alongside the custom color picker.
|
||||
const BANNER_PRESETS = [
|
||||
"#1d2b4d",
|
||||
"#0f172a",
|
||||
"#111827",
|
||||
"#374151",
|
||||
"#7c3aed",
|
||||
"#be123c",
|
||||
"#0e7490",
|
||||
"#15803d",
|
||||
"#b45309",
|
||||
"#db2777",
|
||||
];
|
||||
const DEFAULT_BANNER_COLOR = "#1d2b4d";
|
||||
|
||||
interface StoreFormProps {
|
||||
mode: "create" | "edit";
|
||||
storeId?: string;
|
||||
@ -109,6 +127,13 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
|
||||
// Banner state (edit mode)
|
||||
const bannerFileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedBannerImage, setSelectedBannerImage] = useState<File | null>(
|
||||
null
|
||||
);
|
||||
const [bannerPreviewUrl, setBannerPreviewUrl] = useState<string | null>(null);
|
||||
|
||||
// Loading modal states
|
||||
const [createSellerOpen, setCreateSellerOpen] = useState(false);
|
||||
const [imageUploadOpen, setImageUploadOpen] = useState(false);
|
||||
@ -122,6 +147,9 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
|
||||
selectedCity: "",
|
||||
instagramId: "",
|
||||
storeLogo: "",
|
||||
bannerType: "color",
|
||||
bannerColor: "",
|
||||
bannerImage: "",
|
||||
});
|
||||
|
||||
// Validation errors state
|
||||
@ -157,6 +185,9 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
|
||||
selectedCity: "", // Not available in SellerUpdate
|
||||
instagramId: sellerData.instagramId || "",
|
||||
storeLogo: sellerData.storeLogo || "",
|
||||
bannerType: sellerData.bannerType || "color",
|
||||
bannerColor: sellerData.bannerColor || "",
|
||||
bannerImage: sellerData.bannerImage || "",
|
||||
};
|
||||
setFormData(initialData);
|
||||
}
|
||||
@ -170,11 +201,15 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
|
||||
formData.storeDescription !== (sellerData.storeDescription || "") ||
|
||||
formData.instagramId !== (sellerData.instagramId || "") ||
|
||||
formData.storeLogo !== (sellerData.storeLogo || "") ||
|
||||
selectedImage !== null; // Include image changes
|
||||
formData.bannerType !== (sellerData.bannerType || "color") ||
|
||||
formData.bannerColor !== (sellerData.bannerColor || "") ||
|
||||
formData.bannerImage !== (sellerData.bannerImage || "") ||
|
||||
selectedImage !== null || // Include image changes
|
||||
selectedBannerImage !== null;
|
||||
|
||||
setHasChanges(hasFormChanges);
|
||||
}
|
||||
}, [mode, formData, sellerData, selectedImage]);
|
||||
}, [mode, formData, sellerData, selectedImage, selectedBannerImage]);
|
||||
|
||||
// Redirect if user already has a store (create mode only)
|
||||
useEffect(() => {
|
||||
@ -196,6 +231,15 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
|
||||
};
|
||||
}, [imagePreviewUrl]);
|
||||
|
||||
// Clean up banner preview URL
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (bannerPreviewUrl) {
|
||||
URL.revokeObjectURL(bannerPreviewUrl);
|
||||
}
|
||||
};
|
||||
}, [bannerPreviewUrl]);
|
||||
|
||||
// Show loading while checking ownership or loading seller data (edit mode)
|
||||
if (mode === "edit" && (isOwnershipLoading || isSellerLoading)) {
|
||||
return (
|
||||
@ -311,6 +355,24 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle banner image selection (from gallery; GIFs preserved)
|
||||
const handleBannerSelect = async (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const picked = event.target.files?.[0];
|
||||
if (picked) {
|
||||
// convertHeicToJpeg only touches HEIC; GIF/PNG/JPG pass through untouched.
|
||||
const file = await convertHeicToJpeg(picked);
|
||||
if (bannerPreviewUrl) {
|
||||
URL.revokeObjectURL(bannerPreviewUrl);
|
||||
}
|
||||
const newUrl = URL.createObjectURL(file);
|
||||
setSelectedBannerImage(file);
|
||||
setBannerPreviewUrl(newUrl);
|
||||
setFormData((prev) => ({ ...prev, bannerType: "image" }));
|
||||
}
|
||||
};
|
||||
|
||||
// Handle drawer actions
|
||||
const handleUploadClick = () => {
|
||||
setIsDrawerOpen(true);
|
||||
@ -380,11 +442,38 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
|
||||
}
|
||||
}
|
||||
|
||||
// Upload a newly selected banner image first (GIFs preserved by the API).
|
||||
let bannerImageUrl = formData.bannerImage;
|
||||
if (selectedBannerImage) {
|
||||
try {
|
||||
setImageUploadOpen(true);
|
||||
const bannerResponse = await imageUploadMutation.mutateAsync({
|
||||
file: selectedBannerImage,
|
||||
usageType: "banner",
|
||||
});
|
||||
bannerImageUrl = bannerResponse.originalUrl || formData.bannerImage;
|
||||
setImageUploadOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Error uploading banner:", error);
|
||||
setImageUploadOpen(false);
|
||||
toast({
|
||||
title: "خطا در آپلود بنر",
|
||||
description:
|
||||
"متاسفانه خطایی در آپلود بنر رخ داد. لطفا مجددا تلاش کنید.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
storeName: formData.storeName,
|
||||
storeDescription: formData.storeDescription || null,
|
||||
instagramId: formData.instagramId || null,
|
||||
storeLogo: logoUrl || null,
|
||||
bannerType: formData.bannerType || "color",
|
||||
bannerColor: formData.bannerColor || null,
|
||||
bannerImage: bannerImageUrl || null,
|
||||
};
|
||||
|
||||
updateSellerMutation.mutate({
|
||||
@ -509,6 +598,116 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
|
||||
);
|
||||
};
|
||||
|
||||
// Render banner editor (edit mode only): color swatches/picker or image/GIF
|
||||
const renderBannerSection = () => {
|
||||
const previewImage = bannerPreviewUrl || formData.bannerImage;
|
||||
const previewColor = formData.bannerColor || DEFAULT_BANNER_COLOR;
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="text-R14">بنر فروشگاه</p>
|
||||
|
||||
{/* Live preview */}
|
||||
<div className="relative h-28 w-full rounded-[14px] overflow-hidden border border-WHITE2 bg-WHITE3">
|
||||
{formData.bannerType === "image" && previewImage ? (
|
||||
<img
|
||||
src={previewImage}
|
||||
alt="بنر فروشگاه"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="w-full h-full"
|
||||
style={{ backgroundColor: previewColor }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Type toggle */}
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleInputChange("bannerType", "color")}
|
||||
className={`flex-1 h-10 rounded-[10px] border text-R12 ${
|
||||
formData.bannerType === "color"
|
||||
? "bg-BLACK text-WHITE border-BLACK"
|
||||
: "bg-WHITE2 text-BLACK border-WHITE3"
|
||||
}`}
|
||||
>
|
||||
رنگ
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleInputChange("bannerType", "image")}
|
||||
className={`flex-1 h-10 rounded-[10px] border text-R12 ${
|
||||
formData.bannerType === "image"
|
||||
? "bg-BLACK text-WHITE border-BLACK"
|
||||
: "bg-WHITE2 text-BLACK border-WHITE3"
|
||||
}`}
|
||||
>
|
||||
تصویر
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{formData.bannerType === "color" ? (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{BANNER_PRESETS.map((c) => (
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
onClick={() => handleInputChange("bannerColor", c)}
|
||||
aria-label={c}
|
||||
className={`w-8 h-8 rounded-full border-2 transition-all ${
|
||||
(formData.bannerColor || "").toLowerCase() === c.toLowerCase()
|
||||
? "border-BLACK scale-110"
|
||||
: "border-WHITE2"
|
||||
}`}
|
||||
style={{ backgroundColor: c }}
|
||||
/>
|
||||
))}
|
||||
{/* Custom color */}
|
||||
<label
|
||||
className="w-8 h-8 rounded-full border-2 border-dashed border-GRAY3 overflow-hidden flex items-center justify-center cursor-pointer"
|
||||
aria-label="رنگ دلخواه"
|
||||
>
|
||||
<input
|
||||
type="color"
|
||||
value={previewColor}
|
||||
onChange={(e) =>
|
||||
handleInputChange("bannerColor", e.target.value)
|
||||
}
|
||||
className="w-10 h-10 cursor-pointer border-0 bg-transparent p-0"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => bannerFileInputRef.current?.click()}
|
||||
className="w-full h-11 rounded-[10px] border-2 border-dashed border-GRAY3 flex items-center justify-center gap-2 text-GRAY text-R12 hover:border-primary hover:text-primary transition-colors"
|
||||
>
|
||||
<Upload size={18} />
|
||||
{previewImage ? "تغییر تصویر بنر" : "آپلود تصویر بنر"}
|
||||
</button>
|
||||
<p className="text-R10 text-BLACK2 leading-5">
|
||||
اندازه پیشنهادی: ۱۶۰۰×۵۰۰ پیکسل. بنر تمامعرض نمایش داده میشود و
|
||||
فقط ارتفاع آن برش میخورد؛ بخشهای مهم (متن/لوگو) را وسط قرار دهید.
|
||||
فرمت JPG/PNG/WebP یا GIF متحرک.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
ref={bannerFileInputRef}
|
||||
type="file"
|
||||
accept="image/*,image/gif"
|
||||
onChange={handleBannerSelect}
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Render description field based on mode
|
||||
const renderDescriptionField = () => {
|
||||
if (mode === "create") {
|
||||
@ -630,6 +829,9 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
|
||||
<div className={contentClass}>
|
||||
{renderLogoSection()}
|
||||
|
||||
{/* Banner editor (edit mode) */}
|
||||
{mode === "edit" && renderBannerSection()}
|
||||
|
||||
{/* Username Input - Only for create mode */}
|
||||
{mode === "create" && (
|
||||
<FormField
|
||||
|
||||
@ -266,6 +266,9 @@ export const useSellerUpdate = () => {
|
||||
storeDescription?: string | null;
|
||||
storeLogo?: string | null;
|
||||
instagramId?: string | null;
|
||||
bannerType?: string | null;
|
||||
bannerColor?: string | null;
|
||||
bannerImage?: string | null;
|
||||
};
|
||||
}) => {
|
||||
if (!token) throw new Error("Authentication required");
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
BadgeCheck,
|
||||
ArrowRight,
|
||||
MessageCircle,
|
||||
Search,
|
||||
} from "lucide-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { DrawerContent, Drawer } from "../components/ui/drawer";
|
||||
@ -33,7 +34,8 @@ import UiProvider from "../components/UiProvider";
|
||||
import { useRootData } from "~/hooks/use-root-data";
|
||||
import { LoginRequiredDialog } from "~/components/LoginRequiredDialog";
|
||||
import SellerLogo from "../components/SellerLogo";
|
||||
import SearchTopBar from "~/components/SearchTopBar";
|
||||
import AppInput from "~/components/AppInput";
|
||||
import FilterDrawer from "~/components/FilterDrawer";
|
||||
|
||||
interface LoaderData {
|
||||
seller: SellerType;
|
||||
@ -78,7 +80,7 @@ export async function loader({ params }: LoaderFunctionArgs) {
|
||||
export default function Seller() {
|
||||
const { sellerId } = useParams();
|
||||
const { seller } = useLoaderData<typeof loader>();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { data: isFollowed, isLoading: isFollowStatusLoading } =
|
||||
useSellerFollowStatus(seller?.id || "");
|
||||
const { mutate: followSeller, isPending: isFollowPending } =
|
||||
@ -123,6 +125,17 @@ export default function Seller() {
|
||||
setSearchValue(searchParams.get("q") || "");
|
||||
}, [searchParams]);
|
||||
|
||||
// Apply the in-header product search to the URL (drives useGetSellerProducts)
|
||||
const handleProductSearch = () => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
if (searchValue.trim()) {
|
||||
params.set("q", searchValue);
|
||||
} else {
|
||||
params.delete("q");
|
||||
}
|
||||
setSearchParams(params, { replace: true });
|
||||
};
|
||||
|
||||
const handleFollowToggle = () => {
|
||||
if (!user) {
|
||||
setIsLoginDialogOpen(true);
|
||||
@ -185,6 +198,18 @@ export default function Seller() {
|
||||
const handleBack = () => {
|
||||
safeGoBack(navigate);
|
||||
};
|
||||
|
||||
// Customizable banner: image, solid color, or the default gradient fallback.
|
||||
const bannerIsImage = seller?.bannerType === "image" && !!seller?.bannerImage;
|
||||
const bannerIsColor = seller?.bannerType === "color" && !!seller?.bannerColor;
|
||||
const bannerBgClass =
|
||||
bannerIsImage || bannerIsColor
|
||||
? ""
|
||||
: "bg-gradient-to-br from-VITROWN_BLUE to-[#14213d]";
|
||||
const bannerStyle = bannerIsColor
|
||||
? { backgroundColor: seller?.bannerColor as string }
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<UiProvider
|
||||
isLoading={false}
|
||||
@ -196,7 +221,17 @@ export default function Seller() {
|
||||
<div className="flex flex-col pb-20 lg:max-w-[1100px] lg:mx-auto lg:w-full lg:px-8 lg:pt-8">
|
||||
{/* Desktop brand header (cover + overlapping logo + stats + actions) */}
|
||||
<div className="hidden lg:block">
|
||||
<div className="relative h-[200px] rounded-[22px] overflow-hidden bg-gradient-to-br from-VITROWN_BLUE to-[#14213d]">
|
||||
<div
|
||||
className={`relative h-[200px] rounded-[22px] overflow-hidden ${bannerBgClass}`}
|
||||
style={bannerStyle}
|
||||
>
|
||||
{bannerIsImage ? (
|
||||
<img
|
||||
src={seller?.bannerImage as string}
|
||||
alt=""
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
) : null}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent" />
|
||||
</div>
|
||||
<div className="flex items-start gap-6 px-2 mt-4 relative z-10">
|
||||
@ -284,10 +319,20 @@ export default function Seller() {
|
||||
<div className="border-b border-WHITE3 mt-6" />
|
||||
</div>
|
||||
|
||||
{/* Mobile brand header (cover + overlapping logo + stats + actions) */}
|
||||
{/* Mobile brand header (compact: ~chrome small so products fill the screen) */}
|
||||
<div className="lg:hidden">
|
||||
{/* Cover */}
|
||||
<div className="relative h-[150px] bg-gradient-to-br from-VITROWN_BLUE to-[#14213d]">
|
||||
<div
|
||||
className={`relative h-[120px] ${bannerBgClass}`}
|
||||
style={bannerStyle}
|
||||
>
|
||||
{bannerIsImage ? (
|
||||
<img
|
||||
src={seller?.bannerImage as string}
|
||||
alt=""
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
) : null}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/35 to-transparent" />
|
||||
<button
|
||||
type="button"
|
||||
@ -301,7 +346,9 @@ export default function Seller() {
|
||||
</div>
|
||||
{/* Head */}
|
||||
<div className="px-4 -mt-10 relative z-10">
|
||||
<div className="w-[84px] h-[84px] rounded-[24px] border-4 border-WHITE shadow-md overflow-hidden bg-WHITE3">
|
||||
{/* Logo (start/right) + Chat (end/left, just under the banner) */}
|
||||
<div className="flex items-end justify-between">
|
||||
<div className="w-[84px] h-[84px] rounded-full border-4 border-WHITE shadow-md overflow-hidden bg-WHITE3">
|
||||
{seller?.storeLogo ? (
|
||||
<img
|
||||
src={seller.storeLogo}
|
||||
@ -310,99 +357,87 @@ export default function Seller() {
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<h1 className="flex items-center gap-1.5 text-[21px] font-extrabold mt-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCreateChatThread}
|
||||
disabled={!seller?.id || isCreatingChatThread}
|
||||
aria-label="ارسال پیام"
|
||||
className="w-10 h-10 rounded-full bg-WHITE shadow-md border border-WHITE3 flex items-center justify-center text-BLACK active:scale-95 transition-transform disabled:opacity-60"
|
||||
>
|
||||
<MessageCircle size={20} />
|
||||
</button>
|
||||
</div>
|
||||
<h1 className="flex items-center gap-1.5 text-[21px] font-extrabold mt-2">
|
||||
{seller?.storeName}
|
||||
{seller?.isVerified ? (
|
||||
<BadgeCheck className="text-BLUE" size={18} />
|
||||
) : null}
|
||||
</h1>
|
||||
{/* Handle + rating inline (followers/product count removed) */}
|
||||
<div className="flex items-center gap-2 mt-0.5">
|
||||
{seller?.username ? (
|
||||
<p className="text-GRAY text-[13px] mt-0.5" dir="ltr">
|
||||
<p className="text-GRAY text-[13px]" dir="ltr">
|
||||
@{seller.username}
|
||||
</p>
|
||||
) : null}
|
||||
{/* Stats */}
|
||||
<div className="flex gap-8 py-3.5 mt-3 border-y border-WHITE3">
|
||||
<div>
|
||||
<b className="block text-[17px] font-extrabold">
|
||||
{seller?.productCount ?? 0}
|
||||
</b>
|
||||
<span className="text-GRAY text-[11.5px]">محصول</span>
|
||||
</div>
|
||||
<div>
|
||||
<b className="block text-[17px] font-extrabold">
|
||||
{seller?.followerCount ?? 0}
|
||||
</b>
|
||||
<span className="text-GRAY text-[11.5px]">دنبالکننده</span>
|
||||
</div>
|
||||
<div>
|
||||
<b className="flex items-center gap-1 text-[17px] font-extrabold">
|
||||
{seller?.productReviewsCount ? seller?.reviewAverage : "—"}
|
||||
{seller?.productReviewsCount ? (
|
||||
<Star size={14} className="fill-BLACK" />
|
||||
<span className="flex items-center gap-1 text-[13px] font-bold">
|
||||
<Star size={13} className="fill-BLACK" />
|
||||
{seller?.reviewAverage}
|
||||
</span>
|
||||
) : null}
|
||||
</b>
|
||||
<span className="text-GRAY text-[11.5px]">امتیاز</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Description */}
|
||||
{seller?.storeDescription ? (
|
||||
<div className="mt-3">
|
||||
<div className="mt-2">
|
||||
<ReadMoreText
|
||||
sellerData={seller}
|
||||
text={seller?.storeDescription || ""}
|
||||
maxLength={150}
|
||||
maxLength={110}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2.5 mt-4">
|
||||
{/* Actions: follow + search + filter together */}
|
||||
<div className="flex items-center gap-2 mt-3">
|
||||
<Button
|
||||
variant="dark"
|
||||
size="lg"
|
||||
className="flex-1"
|
||||
className="shrink-0 !h-10"
|
||||
onClick={handleFollowToggle}
|
||||
disabled={isFollowMutating || isFollowStatusLoading}
|
||||
>
|
||||
{isFollowed ? (
|
||||
<>
|
||||
<Check size={22} />
|
||||
<Check size={20} />
|
||||
دنبال شده
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<UserPlus size={22} />
|
||||
<UserPlus size={20} />
|
||||
دنبال کردن
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="shrink-0 px-4"
|
||||
aria-label="ارسال پیام"
|
||||
onClick={() => {
|
||||
handleCreateChatThread();
|
||||
<div className="flex-1 flex items-center gap-2">
|
||||
<AppInput
|
||||
inputIcon={Search}
|
||||
inputDir="rtl"
|
||||
inputTitle={""}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") handleProductSearch();
|
||||
}}
|
||||
disabled={!seller?.id || isCreatingChatThread}
|
||||
>
|
||||
<MessageCircle size={22} />
|
||||
</Button>
|
||||
className="bg-WHITE2 focus:border-blue-500 !h-10"
|
||||
inputValue={searchValue}
|
||||
inputPlaceholder="جستجو در محصولات..."
|
||||
inputOnChange={(e) => setSearchValue(e.target.value)}
|
||||
/>
|
||||
<FilterDrawer minimumPrice={0} maximumPrice={10000000} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
{/* Search Bar (mobile) */}
|
||||
<div className="lg:hidden">
|
||||
<SearchTopBar
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
placeholder="جستجو در محصولات فروشگاه..."
|
||||
minimumPrice={0}
|
||||
maximumPrice={10000000}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full mt-3 lg:mt-0">
|
||||
<div className="hidden lg:block lg:mt-2" />
|
||||
|
||||
{/* seller products */}
|
||||
|
||||
@ -101,16 +101,6 @@ export default function Sellers() {
|
||||
<Store size={48} className="text-gray-400" />
|
||||
</div>
|
||||
)}
|
||||
{seller.sellerName ? (
|
||||
<>
|
||||
<div className="hidden lg:block absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
|
||||
<div className="hidden lg:flex absolute inset-x-0 bottom-0 p-4 items-end">
|
||||
<span className="text-white font-bold text-[15px] truncate">
|
||||
{seller.sellerName}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -4468,6 +4468,24 @@ export interface Seller {
|
||||
* @memberof Seller
|
||||
*/
|
||||
city?: number | null;
|
||||
/**
|
||||
* Banner type: "color" or "image".
|
||||
* @type {string}
|
||||
* @memberof Seller
|
||||
*/
|
||||
bannerType?: string | null;
|
||||
/**
|
||||
* Hex banner color when bannerType is "color".
|
||||
* @type {string}
|
||||
* @memberof Seller
|
||||
*/
|
||||
bannerColor?: string | null;
|
||||
/**
|
||||
* Banner image/GIF URL when bannerType is "image".
|
||||
* @type {string}
|
||||
* @memberof Seller
|
||||
*/
|
||||
bannerImage?: string | null;
|
||||
}
|
||||
|
||||
|
||||
@ -5589,6 +5607,24 @@ export interface SellerUpdate {
|
||||
* @memberof SellerUpdate
|
||||
*/
|
||||
instagramId?: string | null;
|
||||
/**
|
||||
* Banner type: "color" or "image".
|
||||
* @type {string}
|
||||
* @memberof SellerUpdate
|
||||
*/
|
||||
bannerType?: string | null;
|
||||
/**
|
||||
* Hex banner color when bannerType is "color".
|
||||
* @type {string}
|
||||
* @memberof SellerUpdate
|
||||
*/
|
||||
bannerColor?: string | null;
|
||||
/**
|
||||
* Banner image/GIF URL when bannerType is "image".
|
||||
* @type {string}
|
||||
* @memberof SellerUpdate
|
||||
*/
|
||||
bannerImage?: string | null;
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
||||
Loading…
Reference in New Issue
Block a user