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:
Arda Samadi 2026-07-01 12:00:23 +03:30
parent bb10907463
commit 660e01c3c3
5 changed files with 355 additions and 89 deletions

View File

@ -1,6 +1,6 @@
import { useState, useRef, useEffect } from "react"; import { useState, useRef, useEffect } from "react";
import { useNavigate } from "@remix-run/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 ProfilePagesHeader from "../ProfilePagesHeader";
import HeaderWithSupport from "../HeaderWithSupport"; import HeaderWithSupport from "../HeaderWithSupport";
import AppInput from "../AppInput"; import AppInput from "../AppInput";
@ -80,8 +80,26 @@ interface FormData {
selectedCity: string; selectedCity: string;
instagramId: string; instagramId: string;
storeLogo: 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 { interface StoreFormProps {
mode: "create" | "edit"; mode: "create" | "edit";
storeId?: string; storeId?: string;
@ -109,6 +127,13 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [hasChanges, setHasChanges] = 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 // Loading modal states
const [createSellerOpen, setCreateSellerOpen] = useState(false); const [createSellerOpen, setCreateSellerOpen] = useState(false);
const [imageUploadOpen, setImageUploadOpen] = useState(false); const [imageUploadOpen, setImageUploadOpen] = useState(false);
@ -122,6 +147,9 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
selectedCity: "", selectedCity: "",
instagramId: "", instagramId: "",
storeLogo: "", storeLogo: "",
bannerType: "color",
bannerColor: "",
bannerImage: "",
}); });
// Validation errors state // Validation errors state
@ -157,6 +185,9 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
selectedCity: "", // Not available in SellerUpdate selectedCity: "", // Not available in SellerUpdate
instagramId: sellerData.instagramId || "", instagramId: sellerData.instagramId || "",
storeLogo: sellerData.storeLogo || "", storeLogo: sellerData.storeLogo || "",
bannerType: sellerData.bannerType || "color",
bannerColor: sellerData.bannerColor || "",
bannerImage: sellerData.bannerImage || "",
}; };
setFormData(initialData); setFormData(initialData);
} }
@ -170,11 +201,15 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
formData.storeDescription !== (sellerData.storeDescription || "") || formData.storeDescription !== (sellerData.storeDescription || "") ||
formData.instagramId !== (sellerData.instagramId || "") || formData.instagramId !== (sellerData.instagramId || "") ||
formData.storeLogo !== (sellerData.storeLogo || "") || 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); setHasChanges(hasFormChanges);
} }
}, [mode, formData, sellerData, selectedImage]); }, [mode, formData, sellerData, selectedImage, selectedBannerImage]);
// Redirect if user already has a store (create mode only) // Redirect if user already has a store (create mode only)
useEffect(() => { useEffect(() => {
@ -196,6 +231,15 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
}; };
}, [imagePreviewUrl]); }, [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) // Show loading while checking ownership or loading seller data (edit mode)
if (mode === "edit" && (isOwnershipLoading || isSellerLoading)) { if (mode === "edit" && (isOwnershipLoading || isSellerLoading)) {
return ( 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 // Handle drawer actions
const handleUploadClick = () => { const handleUploadClick = () => {
setIsDrawerOpen(true); 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 = { const updateData = {
storeName: formData.storeName, storeName: formData.storeName,
storeDescription: formData.storeDescription || null, storeDescription: formData.storeDescription || null,
instagramId: formData.instagramId || null, instagramId: formData.instagramId || null,
storeLogo: logoUrl || null, storeLogo: logoUrl || null,
bannerType: formData.bannerType || "color",
bannerColor: formData.bannerColor || null,
bannerImage: bannerImageUrl || null,
}; };
updateSellerMutation.mutate({ 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 // Render description field based on mode
const renderDescriptionField = () => { const renderDescriptionField = () => {
if (mode === "create") { if (mode === "create") {
@ -630,6 +829,9 @@ export default function StoreForm({ mode, storeId }: StoreFormProps) {
<div className={contentClass}> <div className={contentClass}>
{renderLogoSection()} {renderLogoSection()}
{/* Banner editor (edit mode) */}
{mode === "edit" && renderBannerSection()}
{/* Username Input - Only for create mode */} {/* Username Input - Only for create mode */}
{mode === "create" && ( {mode === "create" && (
<FormField <FormField

View File

@ -266,6 +266,9 @@ export const useSellerUpdate = () => {
storeDescription?: string | null; storeDescription?: string | null;
storeLogo?: string | null; storeLogo?: string | null;
instagramId?: string | null; instagramId?: string | null;
bannerType?: string | null;
bannerColor?: string | null;
bannerImage?: string | null;
}; };
}) => { }) => {
if (!token) throw new Error("Authentication required"); if (!token) throw new Error("Authentication required");

View File

@ -21,6 +21,7 @@ import {
BadgeCheck, BadgeCheck,
ArrowRight, ArrowRight,
MessageCircle, MessageCircle,
Search,
} from "lucide-react"; } from "lucide-react";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { DrawerContent, Drawer } from "../components/ui/drawer"; import { DrawerContent, Drawer } from "../components/ui/drawer";
@ -33,7 +34,8 @@ import UiProvider from "../components/UiProvider";
import { useRootData } from "~/hooks/use-root-data"; import { useRootData } from "~/hooks/use-root-data";
import { LoginRequiredDialog } from "~/components/LoginRequiredDialog"; import { LoginRequiredDialog } from "~/components/LoginRequiredDialog";
import SellerLogo from "../components/SellerLogo"; import SellerLogo from "../components/SellerLogo";
import SearchTopBar from "~/components/SearchTopBar"; import AppInput from "~/components/AppInput";
import FilterDrawer from "~/components/FilterDrawer";
interface LoaderData { interface LoaderData {
seller: SellerType; seller: SellerType;
@ -78,7 +80,7 @@ export async function loader({ params }: LoaderFunctionArgs) {
export default function Seller() { export default function Seller() {
const { sellerId } = useParams(); const { sellerId } = useParams();
const { seller } = useLoaderData<typeof loader>(); const { seller } = useLoaderData<typeof loader>();
const [searchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const { data: isFollowed, isLoading: isFollowStatusLoading } = const { data: isFollowed, isLoading: isFollowStatusLoading } =
useSellerFollowStatus(seller?.id || ""); useSellerFollowStatus(seller?.id || "");
const { mutate: followSeller, isPending: isFollowPending } = const { mutate: followSeller, isPending: isFollowPending } =
@ -123,6 +125,17 @@ export default function Seller() {
setSearchValue(searchParams.get("q") || ""); setSearchValue(searchParams.get("q") || "");
}, [searchParams]); }, [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 = () => { const handleFollowToggle = () => {
if (!user) { if (!user) {
setIsLoginDialogOpen(true); setIsLoginDialogOpen(true);
@ -185,6 +198,18 @@ export default function Seller() {
const handleBack = () => { const handleBack = () => {
safeGoBack(navigate); 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 ( return (
<UiProvider <UiProvider
isLoading={false} 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"> <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) */} {/* Desktop brand header (cover + overlapping logo + stats + actions) */}
<div className="hidden lg:block"> <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 className="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent" />
</div> </div>
<div className="flex items-start gap-6 px-2 mt-4 relative z-10"> <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 className="border-b border-WHITE3 mt-6" />
</div> </div>
{/* Mobile brand header (cover + overlapping logo + stats + actions) */} {/* Mobile brand header (compact: ~chrome small so products fill the screen) */}
<div className="lg:hidden"> <div className="lg:hidden">
{/* Cover */} {/* 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" /> <div className="absolute inset-0 bg-gradient-to-t from-black/35 to-transparent" />
<button <button
type="button" type="button"
@ -301,108 +346,98 @@ export default function Seller() {
</div> </div>
{/* Head */} {/* Head */}
<div className="px-4 -mt-10 relative z-10"> <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) */}
{seller?.storeLogo ? ( <div className="flex items-end justify-between">
<img <div className="w-[84px] h-[84px] rounded-full border-4 border-WHITE shadow-md overflow-hidden bg-WHITE3">
src={seller.storeLogo} {seller?.storeLogo ? (
alt={seller?.storeName || "store"} <img
className="w-full h-full object-cover" src={seller.storeLogo}
/> alt={seller?.storeName || "store"}
) : null} className="w-full h-full object-cover"
/>
) : null}
</div>
<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> </div>
<h1 className="flex items-center gap-1.5 text-[21px] font-extrabold mt-3"> <h1 className="flex items-center gap-1.5 text-[21px] font-extrabold mt-2">
{seller?.storeName} {seller?.storeName}
{seller?.isVerified ? ( {seller?.isVerified ? (
<BadgeCheck className="text-BLUE" size={18} /> <BadgeCheck className="text-BLUE" size={18} />
) : null} ) : null}
</h1> </h1>
{seller?.username ? ( {/* Handle + rating inline (followers/product count removed) */}
<p className="text-GRAY text-[13px] mt-0.5" dir="ltr"> <div className="flex items-center gap-2 mt-0.5">
@{seller.username} {seller?.username ? (
</p> <p className="text-GRAY text-[13px]" dir="ltr">
) : null} @{seller.username}
{/* Stats */} </p>
<div className="flex gap-8 py-3.5 mt-3 border-y border-WHITE3"> ) : null}
<div> {seller?.productReviewsCount ? (
<b className="block text-[17px] font-extrabold"> <span className="flex items-center gap-1 text-[13px] font-bold">
{seller?.productCount ?? 0} <Star size={13} className="fill-BLACK" />
</b> {seller?.reviewAverage}
<span className="text-GRAY text-[11.5px]">محصول</span> </span>
</div> ) : null}
<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" />
) : null}
</b>
<span className="text-GRAY text-[11.5px]">امتیاز</span>
</div>
</div> </div>
{/* Description */} {/* Description */}
{seller?.storeDescription ? ( {seller?.storeDescription ? (
<div className="mt-3"> <div className="mt-2">
<ReadMoreText <ReadMoreText
sellerData={seller} sellerData={seller}
text={seller?.storeDescription || ""} text={seller?.storeDescription || ""}
maxLength={150} maxLength={110}
/> />
</div> </div>
) : null} ) : null}
{/* Actions */} {/* Actions: follow + search + filter together */}
<div className="flex gap-2.5 mt-4"> <div className="flex items-center gap-2 mt-3">
<Button <Button
variant="dark" variant="dark"
size="lg" size="lg"
className="flex-1" className="shrink-0 !h-10"
onClick={handleFollowToggle} onClick={handleFollowToggle}
disabled={isFollowMutating || isFollowStatusLoading} disabled={isFollowMutating || isFollowStatusLoading}
> >
{isFollowed ? ( {isFollowed ? (
<> <>
<Check size={22} /> <Check size={20} />
دنبال شده دنبال شده
</> </>
) : ( ) : (
<> <>
<UserPlus size={22} /> <UserPlus size={20} />
دنبال کردن دنبال کردن
</> </>
)} )}
</Button> </Button>
<Button <div className="flex-1 flex items-center gap-2">
variant="outline" <AppInput
size="lg" inputIcon={Search}
className="shrink-0 px-4" inputDir="rtl"
aria-label="ارسال پیام" inputTitle={""}
onClick={() => { onKeyDown={(e) => {
handleCreateChatThread(); if (e.key === "Enter") handleProductSearch();
}} }}
disabled={!seller?.id || isCreatingChatThread} className="bg-WHITE2 focus:border-blue-500 !h-10"
> inputValue={searchValue}
<MessageCircle size={22} /> inputPlaceholder="جستجو در محصولات..."
</Button> inputOnChange={(e) => setSearchValue(e.target.value)}
/>
<FilterDrawer minimumPrice={0} maximumPrice={10000000} />
</div>
</div> </div>
</div> </div>
</div> </div>
<div className="w-full"> <div className="w-full mt-3 lg:mt-0">
{/* Search Bar (mobile) */}
<div className="lg:hidden">
<SearchTopBar
searchValue={searchValue}
setSearchValue={setSearchValue}
placeholder="جستجو در محصولات فروشگاه..."
minimumPrice={0}
maximumPrice={10000000}
/>
</div>
<div className="hidden lg:block lg:mt-2" /> <div className="hidden lg:block lg:mt-2" />
{/* seller products */} {/* seller products */}

View File

@ -101,16 +101,6 @@ export default function Sellers() {
<Store size={48} className="text-gray-400" /> <Store size={48} className="text-gray-400" />
</div> </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> </Link>
))} ))}
</div> </div>

View File

@ -4457,17 +4457,35 @@ export interface Seller {
*/ */
category?: string | null; category?: string | null;
/** /**
* *
* @type {number} * @type {number}
* @memberof Seller * @memberof Seller
*/ */
state?: number | null; state?: number | null;
/** /**
* *
* @type {number} * @type {number}
* @memberof Seller * @memberof Seller
*/ */
city?: number | null; 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;
} }
@ -5584,14 +5602,32 @@ export interface SellerUpdate {
*/ */
storeLogo?: string | null; storeLogo?: string | null;
/** /**
* *
* @type {string} * @type {string}
* @memberof SellerUpdate * @memberof SellerUpdate
*/ */
instagramId?: string | null; 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;
} }
/** /**
* *
* @export * @export
* @interface SellerVariantAttributeKV * @interface SellerVariantAttributeKV
*/ */