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:
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -2999,6 +2999,9 @@
|
||||
"comparisonTitle": "مقایسه قیمت فروشگاهها",
|
||||
"noListingsForProduct": "فروشگاهی برای این کالا یافت نشد",
|
||||
"discoverTitle": "فروشگاهها",
|
||||
"pinterestFilter": "پینترستی",
|
||||
"pinterestProductLabel": "محصول پینترستی",
|
||||
"pinterestProductHint": "این کالا در فیلتر پینترستی فروشگاه نمایش داده میشود",
|
||||
"quantityLabel": "تعداد",
|
||||
"buyNow": "ادامه خرید",
|
||||
"addressRequiredHint": "برای ثبت سفارش نیاز به ثبت آدرس و لوکیشن میباشید",
|
||||
|
||||
@@ -139,6 +139,7 @@ export interface IShopProductListing {
|
||||
isTaggedPost?: boolean;
|
||||
postId?: string;
|
||||
postCaption?: string | null;
|
||||
is_pinterest?: boolean;
|
||||
}
|
||||
export interface LastPost {
|
||||
_id: string;
|
||||
|
||||
Reference in New Issue
Block a user