Add Pinterest product tag: discover filter, Reels badge, and images-step checkbox

- Pinterest filter toggle on the Shop discover page, alongside "All",
  backed by the backend's new ?pinterest=true param on
  /shop-products/discover
- Pinterest badge shown above the favorite button in ShopReelsView's
  action rail for products tagged is_pinterest
- "Pinterest product" checkbox at the bottom of the product images step
  (create wizard and edit flow share the same route)
- New pinterest bold-icon SVG under public/images/icons/vuesax/bold

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
payacom
2026-09-16 10:24:12 +03:30
parent d2f3a96ddf
commit d7051c6b16
7 changed files with 96 additions and 5 deletions

View File

@@ -36,16 +36,21 @@ function ProductImagesPage() {
const [images, setImages] = useState<PickedImage[]>([]);
const [primaryIndex, setPrimaryIndex] = useState(0);
const [isPinterest, setIsPinterest] = useState(false);
useEffect(() => {
if (!listingId) return;
request<{ listing: { images: string[]; primaryImageIndex: number } }>(
"GET",
`/shop-products/${listingId}`
).then((res) => {
request<{
listing: {
images: string[];
primaryImageIndex: number;
is_pinterest?: boolean;
};
}>("GET", `/shop-products/${listingId}`).then((res) => {
if (!res?.listing) return;
setImages((res.listing.images || []).map((path) => ({ type: "existing", path })));
setPrimaryIndex(res.listing.primaryImageIndex || 0);
setIsPinterest(Boolean(res.listing.is_pinterest));
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listingId]);
@@ -83,6 +88,7 @@ function ProductImagesPage() {
newFiles.forEach((img) => formData.append("images", img.file, img.file.name));
formData.append("existing_images", JSON.stringify(existingImages));
formData.append("primaryImageIndex", String(primaryIndex));
formData.append("is_pinterest", String(isPinterest));
try {
await request("PATCH", `/shop-products/${listingId}`, formData);
@@ -172,6 +178,34 @@ function ProductImagesPage() {
onChange={handleSelect}
/>
</div>
<button
type="button"
onClick={() => setIsPinterest((v) => !v)}
className="mt-4 flex w-full max-w-sm items-center gap-2.5 rounded-2xl border border-neutral-200 bg-neutral-50 px-3.5 py-3 text-right dark:border-neutral-700 dark:bg-neutral-900"
>
<span
className={cn(
"flex h-5 w-5 shrink-0 items-center justify-center rounded-md border-2",
isPinterest
? "border-[#E60023] bg-[#E60023]"
: "border-neutral-300 dark:border-neutral-600"
)}
>
{isPinterest ? (
<BoldIcon name="tick-square" size={12} tinted className="text-white" />
) : null}
</span>
<BoldIcon name="pinterest" size={18} tinted className="text-[#E60023]" />
<span className="min-w-0 flex-1">
<span className="block text-sm font-medium">
{t("shops.pinterestProductLabel")}
</span>
<span className="block text-[11px] text-neutral-500">
{t("shops.pinterestProductHint")}
</span>
</span>
</button>
</AuthPageContent>
<AuthFormFooter onSkip={goNext} skipDisabled={loading}>
<AuthNextButton

View File

@@ -1,21 +1,26 @@
"use client";
import { useState } from "react";
import Container from "@/components/elements/Container";
import LocalePageShell from "@/components/i18n/LocalePageShell";
import PageTitle from "@/components/settings/PageTitle";
import Header from "@/components/main/Header";
import TabNavigation from "@/components/TabNavigation";
import ShopProductGrid from "@/components/shops/ShopProductGrid";
import BoldIcon from "@/components/ui/BoldIcon";
import { cn } from "@/lib/utils";
import useInfiniteScroll from "@/hooks/useInfiniteScroll";
import { IShopProductListing } from "@/types/types";
import { useTranslation } from "react-i18next";
export default function ShopDiscoverPage() {
const { t } = useTranslation("common");
const [pinterestOnly, setPinterestOnly] = useState(false);
const { data, isLoading } = useInfiniteScroll({
endpoint: "/shop-products/discover",
queryKey: ["shop-products-discover"],
queryKey: ["shop-products-discover", String(pinterestOnly)],
params: pinterestOnly ? { pinterest: "true" } : {},
});
const listings: IShopProductListing[] =
@@ -27,6 +32,35 @@ export default function ShopDiscoverPage() {
<LocalePageShell>
<Container className="pb-28">
<PageTitle>{t("shops.discoverTitle")}</PageTitle>
<div className="mb-3 flex items-center gap-2">
<button
type="button"
onClick={() => setPinterestOnly(false)}
className={cn(
"rounded-2xl border px-3.5 py-2 text-sm font-medium transition",
!pinterestOnly
? "border-pink-400 bg-pink-50 text-pink-600 dark:bg-pink-950/30"
: "border-neutral-200 text-neutral-700 dark:border-neutral-700 dark:text-neutral-300"
)}
>
{t("common.all")}
</button>
<button
type="button"
onClick={() => setPinterestOnly(true)}
className={cn(
"flex items-center gap-1.5 rounded-2xl border px-3.5 py-2 text-sm font-medium transition",
pinterestOnly
? "border-[#E60023] bg-[#E60023]/10 text-[#E60023]"
: "border-neutral-200 text-neutral-700 dark:border-neutral-700 dark:text-neutral-300"
)}
>
<BoldIcon name="pinterest" size={14} tinted />
{t("shops.pinterestFilter")}
</button>
</div>
<ShopProductGrid
listings={listings}
isLoading={isLoading}

View File

@@ -301,6 +301,19 @@ export default function ShopReelsView({
className="text-white brightness-125 drop-shadow-lg"
/>
</button>
{listing.is_pinterest && (
<span
aria-hidden
className="flex flex-col items-center gap-1"
>
<BoldIcon
name="pinterest"
size={26}
tinted
className="text-[#E60023] drop-shadow-lg"
/>
</span>
)}
<button
type="button"
onClick={() => handleToggleFavorite(listing._id)}

View File

@@ -3000,6 +3000,9 @@
"comparisonTitle": "Compare shop prices",
"noListingsForProduct": "No shops found for this product",
"discoverTitle": "Shops",
"pinterestFilter": "Pinterest",
"pinterestProductLabel": "Pinterest product",
"pinterestProductHint": "This product will show up under the shop's Pinterest filter",
"quantityLabel": "Quantity",
"buyNow": "Continue purchase",
"addressRequiredHint": "You need to set your address and location to place an order",

View File

@@ -2999,6 +2999,9 @@
"comparisonTitle": "مقایسه قیمت فروشگاه‌ها",
"noListingsForProduct": "فروشگاهی برای این کالا یافت نشد",
"discoverTitle": "فروشگاه‌ها",
"pinterestFilter": "پینترستی",
"pinterestProductLabel": "محصول پینترستی",
"pinterestProductHint": "این کالا در فیلتر پینترستی فروشگاه نمایش داده می‌شود",
"quantityLabel": "تعداد",
"buyNow": "ادامه خرید",
"addressRequiredHint": "برای ثبت سفارش نیاز به ثبت آدرس و لوکیشن می‌باشید",

View File

@@ -139,6 +139,7 @@ export interface IShopProductListing {
isTaggedPost?: boolean;
postId?: string;
postCaption?: string | null;
is_pinterest?: boolean;
}
export interface LastPost {
_id: string;