feat: drive shop "service description" button/content from backend

Replaces the hardcoded DNS-only content and isDns/category-label check
with data from the config's `has_extra_description` / `extra_description`
fields, so any product can opt into showing free-text extra info.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 16:48:20 +03:30
parent 826f61a0fe
commit 5e9e0b7969
5 changed files with 23 additions and 145 deletions

View File

@@ -53,10 +53,7 @@ const ProductList = () => {
activeId={activeId}
setActiveId={setActiveId}
/>
<ListAllProduct
activeId={activeId}
categories={data?.data?.result?.categories}
/>
<ListAllProduct activeId={activeId} />
</div>
</div>
);

View File

@@ -9,25 +9,11 @@ import { RootState } from "../../../../../redux/store";
import { useNavigate } from "react-router-dom";
import toast from "react-hot-toast";
interface Category {
id: number | string;
label?: string;
}
const ListAllProduct = ({
activeId,
categories,
}: {
activeId: number;
categories?: Category[];
}) => {
const ListAllProduct = ({ activeId }: { activeId: number }) => {
const axios = useAxios();
const [selectedConfigId, setSelectedConfigId] = useState<number | null>(null);
const { token } = useSelector((state: RootState) => state.token);
const navigate = useNavigate();
const isDnsCategory =
categories?.find((category) => Number(category.id) === activeId)?.label?.toLowerCase() ===
"dns";
const { data, error, isLoading, isFetching } = useQuery({
queryKey: ["configs", activeId],
queryFn: () => {
@@ -84,7 +70,6 @@ const ListAllProduct = ({
isOpen={selectedConfigId !== null}
setIsOpen={(open) => { if (!open) setSelectedConfigId(null); }}
configId={selectedConfigId}
isDns={isDnsCategory}
/>
)}
<div

View File

@@ -23,7 +23,7 @@ import {
buildAdvancedVerificationMessage,
} from "../../../../advancedVerification/constants";
const ModalBuy = ({ isOpen, setIsOpen, configId, isDns }: ModalBuyProps) => {
const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
const axios = useAxios();
const dispatch = useDispatch();
const navigate = useNavigate();
@@ -54,6 +54,10 @@ const ModalBuy = ({ isOpen, setIsOpen, configId, isDns }: ModalBuyProps) => {
);
const advancedVerificationMessage =
buildAdvancedVerificationMessage(advancedVerificationLimit);
const hasExtraDescription = Boolean(
data?.data?.result?.has_extra_description
);
const extraDescription = data?.data?.result?.extra_description ?? "";
const advancedVerificationFromApi =
data?.data?.result?.requires_advanced_verification;
const hasAdvancedVerificationApiFlag =
@@ -239,7 +243,11 @@ const ModalBuy = ({ isOpen, setIsOpen, configId, isDns }: ModalBuyProps) => {
return (
<>
<ModalDnsServiceInfo isOpen={isInfoOpen} setIsOpen={setIsInfoOpen} />
<ModalDnsServiceInfo
isOpen={isInfoOpen}
setIsOpen={setIsInfoOpen}
description={extraDescription}
/>
<MuiModal
isOpenModal={isOpen}
setIsOpenModal={setIsOpen}
@@ -293,7 +301,7 @@ const ModalBuy = ({ isOpen, setIsOpen, configId, isDns }: ModalBuyProps) => {
</div>
) : (
<div className="w-full h-full flex flex-col gap-3 justify-between items-center text-sm md:text-xl">
{isDns && (
{hasExtraDescription && (
<Button
extra="!m-0 !w-full !py-1.5 !bg-gray-800 !text-secondTheme border border-secondTheme/40 flex flex-row-reverse justify-center items-center gap-2 shadow-md text-sm md:text-base"
onClick={() => setIsInfoOpen(true)}

View File

@@ -4,136 +4,25 @@ import MuiModal from "../../../../../common/modal";
interface ModalDnsServiceInfoProps {
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
description?: string | null;
}
const GAMES = [
"Valorant",
"Call of Duty: Warzone",
"EA SPORTS FC 25",
"EA SPORTS FC 26",
"PUBG Mobile",
"PUBG: Battlegrounds (PC)",
"Call of Duty: Mobile (CODM)",
"Fortnite",
"League of Legends (LoL)",
"Clash Royale",
"Clash of Clans",
];
const SERVICES = [
"ChatGPT",
"Discord",
"Spotify",
"NVIDIA (App & Services)",
"GitHub، Docker، NPM، PyPI و بسیاری از ابزارها و سرویس‌های توسعه (Developer Services)",
];
const STREAMING = ["YouTube", "Twitch", "Kick"];
const BENEFITS = [
"بدون نیاز به VPN",
"راه‌اندازی در کمتر از یک دقیقه",
"مناسب برای کامپیوتر، موبایل و کنسول بازی",
"پشتیبانی و بروزرسانی مداوم سرویس‌ها",
];
const Section = ({
emoji,
title,
children,
}: {
emoji: string;
title: string;
children: React.ReactNode;
}) => (
<div className="w-full flex flex-col gap-2 bg-gray-900/60 border border-gray-700 rounded-lg p-3">
<div className="flex flex-row-reverse items-center gap-1.5 text-secondTheme font-bold">
<span>{emoji}</span>
<span>{title}</span>
</div>
{children}
</div>
);
const ModalDnsServiceInfo = ({ isOpen, setIsOpen }: ModalDnsServiceInfoProps) => {
const ModalDnsServiceInfo = ({
isOpen,
setIsOpen,
description,
}: ModalDnsServiceInfoProps) => {
return (
<MuiModal
isOpenModal={isOpen}
setIsOpenModal={setIsOpen}
title="توضیحات سرویس"
nameService="DNS"
nameService="توضیحات سرویس"
>
<div className="w-full h-full flex flex-col gap-3 max-sm:text-xs max-md:text-sm text-base">
<Section emoji="🎮" title="بازی‌های پشتیبانی‌شده">
<ul className="flex flex-col gap-1.5">
{GAMES.map((game) => (
<li
key={game}
className="flex flex-row-reverse items-center gap-2 text-gray-200"
>
<span>🔹</span>
<span dir="ltr" className="text-start">
{game}
</span>
</li>
))}
</ul>
</Section>
<Section emoji="💻" title="سرویس‌ها و پلتفرم‌ها">
<ul className="flex flex-col gap-1.5">
{SERVICES.map((service) => (
<li
key={service}
className="flex flex-row-reverse items-start gap-2 text-gray-200"
>
<span className="shrink-0">✔️</span>
<span className="text-start leading-6">{service}</span>
</li>
))}
</ul>
</Section>
<Section emoji="📺" title="سرویس‌های استریم">
<ul className="flex flex-col gap-1.5">
{STREAMING.map((item) => (
<li
key={item}
className="flex flex-row-reverse items-center justify-between gap-2 text-gray-200"
>
<div className="flex flex-row-reverse items-center gap-2">
<span>▶️</span>
<span dir="ltr">{item}</span>
</div>
<span className="text-[10px] md:text-xs text-yellow-300/90 whitespace-nowrap">
فقط روی اینترنت خانگی
</span>
</li>
))}
</ul>
</Section>
<Section emoji="⚡️" title="مزایای سرویس">
<ul className="flex flex-col gap-1.5">
{BENEFITS.map((benefit) => (
<li
key={benefit}
className="flex flex-row-reverse items-center gap-2 text-gray-200"
>
<span>✅</span>
<span>{benefit}</span>
</li>
))}
</ul>
</Section>
<div className="w-full flex flex-row-reverse items-start gap-2 bg-secondTheme/10 border border-secondTheme/30 text-secondTheme rounded-lg p-3">
<span className="shrink-0">📌</span>
<span className="text-start leading-6">
بازی و سرویس‌های جدید به‌صورت مداوم به DNS اضافه می‌شوند و این
لیست همواره در حال بروزرسانی است.
</span>
</div>
<p className="w-full whitespace-pre-line leading-7 text-gray-200 text-start">
{description}
</p>
</div>
</MuiModal>
);

View File

@@ -203,7 +203,6 @@ interface ModalBuyProps {
// unknown
// >;
configId: number;
isDns?: boolean;
}
interface BuyConfigDataProps {