-
+ {/* پیشفرضِ router.back() اینجا کاربر رو برمیگردوند به هرجایی که ازش وارد این
+ ویزارد شده — که برای ویرایش، صفحهی نمایشِ کالا
+ (shops/[shopId]/products/[listingId]) بود، نه لیستِ کالاها. طبقِ خواستهی
+ کاربر همیشه صریحاً میره به لیستِ کالاهای همین فروشگاه */}
+
{t("shops.productNameTitle")}
diff --git a/src/app/shops/[shopId]/products/new/variants/page.tsx b/src/app/shops/[shopId]/products/new/variants/page.tsx
index 0b6d34d..027a115 100644
--- a/src/app/shops/[shopId]/products/new/variants/page.tsx
+++ b/src/app/shops/[shopId]/products/new/variants/page.tsx
@@ -297,6 +297,7 @@ function ProductVariantsPage() {
loading={loading}
disabled={loading || rows.length === 0}
onClick={handleSubmit}
+ className="border-[#ef4444] bg-[#ef4444] text-white hover:bg-[#ef4444]/90 dark:bg-[#ef4444] dark:hover:bg-[#ef4444]/90"
>
{t("shops.saveProduct")}
diff --git a/src/app/shops/checkout/[orderId]/page.tsx b/src/app/shops/checkout/[orderId]/page.tsx
index 4d4fbfe..acf3815 100644
--- a/src/app/shops/checkout/[orderId]/page.tsx
+++ b/src/app/shops/checkout/[orderId]/page.tsx
@@ -6,7 +6,7 @@ import PageTitle from "@/components/settings/PageTitle";
import AuthNextButton from "@/components/auth/AuthNextButton";
import { IMAGE_BASE_URL } from "@/components/main/BaseUrl";
import useAxios from "@/hooks/useAxios";
-import { useParams, useRouter } from "next/navigation";
+import { useParams } from "next/navigation";
import { useEffect, useState } from "react";
import Image from "next/image";
import toast from "react-hot-toast";
@@ -34,7 +34,6 @@ type OrderDetail = {
export default function CheckoutPage() {
const { t } = useTranslation("common");
- const router = useRouter();
const params = useParams<{ orderId: string }>();
const { request, loading } = useAxios();
const [order, setOrder] = useState(null);
@@ -54,7 +53,10 @@ export default function CheckoutPage() {
{ orderId: params.orderId }
);
if (response?.authority) {
- router.push(`https://www.zarinpal.com/pg/StartPay/${response.authority}`);
+ // router.push (next/navigation) برای URLهای بیرونی رفتارِ تضمینشدهای نداره —
+ // window.location.href مطمئنترین راهه (خصوصاً سافاری) و با بقیهی مسیرهای
+ // پرداختِ این اپ (booking، آکادمی) هم یکی میمونه
+ window.location.href = `https://www.zarinpal.com/pg/StartPay/${response.authority}`;
}
} catch (err: unknown) {
const message =
diff --git a/src/app/shops/listing/[listingId]/ListingDetailClient.tsx b/src/app/shops/listing/[listingId]/ListingDetailClient.tsx
index 4e19784..40239db 100644
--- a/src/app/shops/listing/[listingId]/ListingDetailClient.tsx
+++ b/src/app/shops/listing/[listingId]/ListingDetailClient.tsx
@@ -10,7 +10,9 @@ import BackButton from "@/components/ui/BackButton";
import DiscountBadge from "@/components/ui/DiscountBadge";
import TabNavigation from "@/components/TabNavigation";
import useAxios from "@/hooks/useAxios";
+import { copyTextToClipboard } from "@/lib/chat/getCopyableMessageText";
import { cn } from "@/lib/utils";
+import { btnDefault, btnPrimary } from "@/lib/ui/buttonStyles";
import { IShopProductListing, IShopProductVariant } from "@/types/types";
import Cookies from "js-cookie";
import { trackExploreInteraction } from "@/api/trackExploreInteraction";
@@ -94,6 +96,24 @@ export default function ListingDetailClient() {
const shopInfo: ShopHeader | null =
listing && typeof listing.shop === "object" ? (listing.shop as ShopHeader) : null;
+ // طبق خواستهی کاربر: اشتراکگذاری لینکِ خودِ کالا، نه فروشگاه — عینِ shareProduct
+ // توی ProductDetailScreen.tsx اندروید (Web Share API با fallback به کپیِ لینک)
+ const shareProduct = async () => {
+ if (!listing?._id) return;
+ const productUrl = `https://modstagram.com/shops/listing/${listing._id}`;
+ if (typeof navigator !== "undefined" && navigator.share) {
+ try {
+ await navigator.share({ url: productUrl, title: listing.title });
+ return;
+ } catch {
+ return;
+ }
+ }
+ const ok = await copyTextToClipboard(productUrl);
+ if (ok) toast.success(t("shops.share.copied"));
+ else toast.error(t("shops.share.copyFailed"));
+ };
+
useEffect(() => {
if (!listing?._id) return;
const token = Cookies.get("token") || "";
@@ -164,6 +184,10 @@ export default function ListingDetailClient() {
}, [matchedVariant?._id]);
const enabledShippingMethods = shopInfo?.shipping_methods?.filter((m) => m.enabled) || [];
+ // هزینهی روشِ ارسالِ انتخابشده — قبلاً هیچجا خونده نمیشد، پس انتخابِ روشِ ارسال
+ // هیچ تاثیری روی قیمتِ نمایشدادهشده نداشت (فقط موقعِ ثبتِ سفارش/handleBuy چک میشد)
+ const shippingCost =
+ enabledShippingMethods.find((m) => m.method === shippingMethod)?.cost || 0;
const categoryLine = listing
? [listing.category, listing.sub_category, listing.sub_sub_category]
@@ -256,34 +280,47 @@ export default function ListingDetailClient() {
+ // چیدمانِ این صفحه ambient dir معکوسیه (dir={isFa?"ltr":"rtl"} چند خط
+ // بالاتر، برای بخشهای دیگهی همین صفحه). گریدِ عکسِ کالاها باید عینِ
+ // اندروید (چیدمانِ عادی/غیرمعکوس، راستبهچپ برای فارسی) باشه، پس اینجا
+ // با dir عادی override میشه. گردی هم قبلاً responsive (۶px زیرِ sm، ۱۶px
+ // بالاش) بود، الان همیشه ۱۶px — عینِ tileImg توی ShopProfileScreen.tsx
+
{listings.map((listing) => {
const primaryImage =
listing.images?.[listing.primaryImageIndex] || listing.images?.[0];
@@ -330,7 +336,7 @@ export default function ShopProfileClient() {
onClick={() =>
router.push(`/shops/profile/${shopId}/reel/${tileId}`)
}
- className="relative aspect-square overflow-hidden rounded-md bg-neutral-100 sm:rounded-2xl dark:bg-neutral-800"
+ className="relative aspect-square overflow-hidden rounded-2xl bg-neutral-100 dark:bg-neutral-800"
>
{listing.isTaggedPost && (
diff --git a/src/components/TabNavigation.tsx b/src/components/TabNavigation.tsx
index 7643b0d..563cd6e 100644
--- a/src/components/TabNavigation.tsx
+++ b/src/components/TabNavigation.tsx
@@ -51,16 +51,25 @@ export default function TabNavigation({ currentPage }: TabNavigationProps) {
(currentPage.startsWith("/projects") ||
currentPage === "/new-project")) ||
(tab.href === "/academy" && currentPage.startsWith("/academy"));
+ // isActive فقط برای رنگ/هایلایتِ آیکونه — شاملِ زیرصفحهها هم میشه (مثلاً
+ // /settings/wallet). اگه کلیک روی خودِ همین isActive شرط بشه، وقتی از یه
+ // زیرصفحهی تنظیمات روی آیکونِ تنظیمات بزنی، چون isActive از قبل true هست
+ // preventDefault صدا زده میشه و Link اصلاً navigate نمیکنه — کاربر مجبور
+ // میشد اول یه تبِ دیگه بزنه بعد دوباره تنظیمات، تا واقعاً برگرده به /settings.
+ // اینترسپت (برای اسکرولبهبالا/رویدادِ تب) فقط وقتی درسته که دقیقاً همون
+ // مسیرِ رَوتِ تب باشیم؛ وگرنه باید بذاریم Link عادی navigate کنه
+ const isExactRoot = currentPage === tab.href;
return (
{
- // وقتی تبی که از قبل توش هستیم دوباره زده میشه، Next.js چون مسیر عوض
- // نمیشه هیچ اسکرول/رفرشی انجام نمیده — طبق درخواست کاربر، مثل اینستاگرام
- // باید صفحه بره بالا و (برای تبِ خانه) جدیدترین پستها هم لود بشن
- if (isActive) {
+ // وقتی تبی که از قبل توش هستیم (دقیقاً همون صفحهی روتش) دوباره زده
+ // میشه، Next.js چون مسیر عوض نمیشه هیچ اسکرول/رفرشی انجام نمیده —
+ // طبق درخواست کاربر، مثل اینستاگرام باید صفحه بره بالا و (برای تبِ
+ // خانه) جدیدترین پستها هم لود بشن
+ if (isExactRoot) {
e.preventDefault();
window.dispatchEvent(
new CustomEvent("app:tab-reselect", {
diff --git a/src/components/chat/GiftInChatModal.tsx b/src/components/chat/GiftInChatModal.tsx
index bed0435..af9a2ff 100644
--- a/src/components/chat/GiftInChatModal.tsx
+++ b/src/components/chat/GiftInChatModal.tsx
@@ -1,7 +1,6 @@
"use client";
import { useMemo, useState } from "react";
-import { useRouter } from "next/navigation";
import Modal from "@/components/elements/Modal";
import RoundedButton from "@/components/elements/RoundedButton";
import RoundedInput from "@/components/elements/RoundedInput";
@@ -38,7 +37,6 @@ export default function GiftInChatModal({
}: GiftInChatModalProps) {
const { t } = useTranslation("common");
const { request } = useAxios();
- const router = useRouter();
const [selectedAmount, setSelectedAmount] = useState(PRESET_AMOUNTS[0]);
const [customAmount, setCustomAmount] = useState("");
@@ -79,7 +77,9 @@ export default function GiftInChatModal({
reset();
onClose();
} else if (response?.paymentUrl) {
- router.push(response.paymentUrl);
+ // لینک خارجی — router.push رفتارِ تضمینشدهای نداره؛ window.location.href
+ // مطمئنتره (خصوصاً سافاری)
+ window.location.href = response.paymentUrl;
} else {
toast.error(t("giftPage.paymentInfoError"));
}
diff --git a/src/components/course/CourseManager.tsx b/src/components/course/CourseManager.tsx
index 554af46..3e756ce 100644
--- a/src/components/course/CourseManager.tsx
+++ b/src/components/course/CourseManager.tsx
@@ -326,12 +326,9 @@ export function CourseManager() {
}
if (paymentUrl) {
- window.open(paymentUrl, "_blank", "noopener,noreferrer");
- toast.success(t("academyCourse.redirectedToPayment"));
-
- setTimeout(() => {
- setIsDialogOpen1?.(false);
- }, 1000);
+ // window.open بعدِ await در سافاری (دسکتاپ/iOS) پاپآپبلاک میشه چون دیگه
+ // synchronously به کلیکِ کاربر متصل نیست — window.location.href مطمئنتره
+ window.location.href = paymentUrl;
} else {
console.error("ساختار پاسخ نامعتبر:", response);
toast.error(
diff --git a/src/components/models/ModelsFilter.tsx b/src/components/models/ModelsFilter.tsx
index c8bc847..76e51bb 100644
--- a/src/components/models/ModelsFilter.tsx
+++ b/src/components/models/ModelsFilter.tsx
@@ -199,16 +199,14 @@ function ModelsFilter({ expertise }: ModelsFilterProps) {
>
- {/* طبق خواستهی صریح کاربر: فعلاً که تعداد فروشگاهها کمه این آیکون مخفیه؛
- هر وقت تعداد فروشگاهها زیاد شد از کامنت درش بیارید */}
- {/* router.push("/shops/discover")}
aria-label={t("filters.toolbar.shop")}
className={toolbarIconButtonClass}
>
- */}
+