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

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.48 2 2 6.48 2 12C2 16.25 4.67 19.9 8.44 21.34C8.35 20.56 8.28 19.36 8.47 18.5C8.65 17.72 9.63 13.6 9.63 13.6C9.63 13.6 9.33 13 9.33 12.09C9.33 10.66 10.16 9.6 11.2 9.6C12.08 9.6 12.5 10.26 12.5 11.05C12.5 11.93 11.93 13.26 11.63 14.5C11.39 15.53 12.15 16.38 13.16 16.38C14.99 16.38 16.4 14.45 16.4 11.66C16.4 9.19 14.62 7.46 12.08 7.46C9.14 7.46 7.42 9.66 7.42 11.94C7.42 12.82 7.76 13.77 8.19 14.29C8.27 14.39 8.28 14.48 8.26 14.58C8.19 14.86 8.04 15.46 8.01 15.59C7.97 15.77 7.87 15.81 7.68 15.72C6.44 15.14 5.67 13.34 5.67 11.9C5.67 8.75 7.95 5.86 12.24 5.86C15.68 5.86 18.36 8.31 18.36 11.62C18.36 15.07 16.19 17.85 13.16 17.85C12.13 17.85 11.16 17.31 10.83 16.68C10.83 16.68 10.32 18.65 10.2 19.1C9.97 19.97 9.35 21.07 8.94 21.74C9.9 22.04 10.93 22.2 12 22.2C17.52 22.2 22 17.72 22 12.2C22 6.68 17.52 2 12 2Z" fill="#292D32"/>
</svg>

After

Width:  |  Height:  |  Size: 952 B

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;