diff --git a/.env.local.example b/.env.local.example index 63aa21a..fbfc8b9 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,8 +1,2 @@ -# Copy this file to .env.local and fill in your real keys. - -# Neshan (نشان) map API key — get one from https://platform.neshan.org -# Used by components/MapSection.jsx for the static map image. -NEXT_PUBLIC_NESHAN_API_KEY= - - -NEXT_PUBLIC_API_BASE_URL=https://your-real-backend.com \ No newline at end of file +# Optional backend API base URL. Copy to .env.local to configure. +NEXT_PUBLIC_API_BASE_URL=https://your-real-backend.com diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 5553498..9a9dd30 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -6,7 +6,6 @@ - diff --git a/README.md b/README.md index fa688e3..c5ece2b 100644 --- a/README.md +++ b/README.md @@ -1,132 +1,56 @@ # روبات مارکت — Vending Machine Showcase -پروژه Next.js (App Router) صفحه معرفی محصولات «روبات مارکت»، با انیمیشن‌های GSAP/ScrollTrigger، -مدیریت سبد خرید با Redux Toolkit، حالت تیره/روشن، دو زبانه (فارسی/انگلیسی)، نقشه، فرم تیکت -پشتیبانی، و ویجت چت. +صفحه معرفی دستگاه‌های وندینگ روبات مارکت، ساخته‌شده با Next.js، React و Ant Design. -## اجرای پروژه +## اجرا ```bash npm install npm run dev ``` -سپس `http://localhost:3000` را باز کنید. +سایت در http://localhost:3000 در دسترس است. -## ساختار پروژه +## ساخت نسخه نهایی -``` -app/ - layout.jsx # پوسته HTML، فونت وزیرمتن - page.jsx # چیدمان کامل صفحه - providers.jsx # Redux + Theme + Language + AntD ConfigProvider - globals.css # توکن‌های رنگ دارک/لایت، جلوه اسپات‌لایت، تایپ‌رایتر - context/ - ThemeContext.jsx # حالت دارک/لایت (ذخیره در localStorage) - LanguageContext.jsx # زبان فا/en (ذخیره در localStorage + جهت صفحه) -store/ # Redux (سبد خرید) -data/ - content.js # منبع واحد محتوای دوزبانه (fa/en) — همه‌ی متن‌ها اینجاست -components/ - Header.jsx # هدر + سوییچ زبان/تم + دکمه ورود به پنل کاربری - ThemeToggle.jsx / LanguageToggle.jsx - HeroSection.jsx # هدلاین تایپ‌شونده + پس‌زمینه رنگی در حال چرخش - HorizontalShowcase.jsx # اسکرول افقی پین‌شده + ویدیوی پس‌زمینه - VendingCard.jsx # کارت دستگاه + جلوه اسپات‌لایت - FloatingItems.jsx - FeaturesSection.jsx - AboutSection.jsx - LocationsSection.jsx - MapSection.jsx # نقشه Neshan (کلید API را خودتان اضافه می‌کنید) - CatalogSection.jsx # کاتالوگ محصولات قابل سفارش - IntroVideoSection.jsx # ویدیوی معرفی شرکت - TicketForm.jsx # فرم تیکت پشتیبانی - ChatWidget.jsx # آیکون شناور چت + مودال گفتگو - CartDrawer.jsx - Footer.jsx -public/ - images/, gifs/, video/ # دارایی‌های placeholder — در پیام قبلی توضیح داده شده +```bash +npm run build +npm start ``` -## ویژگی‌های اضافه‌شده در این نسخه +خروجی standalone برای اجرای Docker تنظیم شده است. -### نقشه (Neshan) -`components/MapSection.jsx` از **Neshan Static Map API** استفاده می‌کند (یک تصویر ساده، بدون نیاز -به اسکریپت/SDK جاوااسکریپت). کلید API را اینجا وارد کنید: +## ساختار و بخش‌های فعال -1. یک فایل `.env.local` در ریشه پروژه بسازید (از روی `.env.local.example` کپی کنید) -2. مقدار `NEXT_PUBLIC_NESHAN_API_KEY` را با کلید واقعی خودتان از - [platform.neshan.org](https://platform.neshan.org) پر کنید +- `app/page.jsx`: صفحه اصلی شامل هدر، سبد خرید، هیرو، ویترین دستگاه‌ها، مزایا، درباره ما، محل‌های مناسب نصب و فوتر. +- `components/HeroSection.jsx`: اسلایدر تصاویر دستگاه‌ها. +- `components/ShowcaseSection.jsx`: انتخاب نمایش موبایل یا ویترین افقی دسکتاپ. +- `components/HorizontalShowcase.jsx`: ویترین افقی با GSAP و ویدیوی پس‌زمینه وابسته به اسکرول. +- `components/MobileShowcase.jsx`: فهرست دستگاه‌ها در موبایل. +- `components/VendingCard.jsx` و `components/FloatingItems.jsx`: نمایش دستگاه و جلوه‌های تصویری. +- `components/CartDrawer.jsx` و `store/`: سبد خرید و مدیریت تعداد دستگاه‌ها با Redux. +- `data/content.js`: محتوای فارسی و انگلیسی، مشخصات و قیمت دستگاه‌ها و مسیر رسانه‌ها. +- `app/context/` و `app/providers.jsx`: زبان، جهت صفحه، تم و تنظیمات Ant Design. +- `app/globals.css`: رنگ‌ها و استایل‌های مشترک. +- `public/`: تصاویر دستگاه‌ها، اقلام شناور، گیف‌ها و ویدیوی ویترین. -تا وقتی کلید خالی باشد، به‌جای نقشه یک پیام راهنما («برای فعال شدن نقشه، کلید API نشان را در -تنظیمات پروژه قرار دهید») نمایش داده می‌شود — سایت خراب نمی‌شود. نقشه در حالت تیره/روشن هم استایل -متفاوتی می‌گیرد (`standard-night` / `standard-day`). لینک «مشاهده در نشان» کنار هر آدرس به نقشه -تعاملی neshan.org می‌رود و نیازی به کلید ندارد. مختصات فعلی در `data/content.js` -(`mapLocations`) تقریبی هستند — با پین دقیق جایگزین کنید. +## زبان و تم -### کاتالوگ محصولات -`components/CatalogSection.jsx` — گرید محصولاتی (زعفران، تنقلات، نوشیدنی، قهوه، چای و ...) که -مشتریان دستگاه می‌توانند با قیمت کمتر از بازار سفارش دهند. هر آیتم قیمت بازار (خط‌خورده) و قیمت -ویژه را نشان می‌دهد و دکمه «افزودن به سفارش» آن را به همان سبد خرید Redux اضافه می‌کند (در کنار -دستگاه‌ها). لیست محصولات، قیمت‌ها و تصاویر در `data/content.js` (کلید `catalog`) قابل ویرایش است؛ -تصاویر placeholder در `public/images/catalog/` قرار دارند. +زبان فارسی/انگلیسی و حالت روشن/تیره از هدر تغییر می‌کنند و ترجیح کاربر در localStorage ذخیره می‌شود. +ویترین افقی برای محاسبات اسکرول همیشه جهت LTR دارد، اما متن کارت‌ها جهت زبان فعال را رعایت می‌کند. -### سکشن ویدیوی معرفی شرکت -`components/IntroVideoSection.jsx` — یک پلیر ویدیوی مستقل (جدا از ویدیوی پس‌زمینه اسکرول افقی) با -پوستر و دکمه پخش استاندارد مرورگر. برای جایگزینی، فایل واقعی را در -`public/video/company-intro-placeholder.mp4` بگذارید (یا مسیر `introVideoSrc` را در -`data/content.js` عوض کنید)؛ پوستر هم از `public/images/video-poster.svg` می‌آید. +## ورود و سبد خرید -### رنگ‌بندی متناوب سکشن‌ها -هر سکشن اصلی صفحه (به‌جز هیرو و بخش اسکرول افقی که پس‌زمینه مخصوص خودشان را دارند) به‌ترتیب بین سه -تن رنگی برند می‌چرخد: `bg-page` → `bg-surface` → `bg-elevated` → ... به‌همراه یک خط جداکننده ظریف -بالای هر بخش. این‌ها همان توکن‌های دارک/لایت هستند، پس با تغییر تم هم هماهنگ می‌مانند. برای تغییر -ترتیب یا اضافه کردن سکشن جدید، کافی‌ست یکی از این سه کلاس را روی `
` بگذارید. +دکمه ورود به https://panel.my-rm.com متصل است. +سبد خرید در حافظه نگه‌داری می‌شود و با بارگذاری مجدد صفحه پاک می‌شود. +پرداخت هنوز متصل نیست؛ دکمه پرداخت در `CartDrawer.jsx` پیام نمایشی نشان می‌دهد. -### حالت دارک/لایت -دکمه سوییچ در هدر (آیکون خورشید/ماه). ترجیح در `localStorage` ذخیره می‌شود و در اولین بار بر اساس -تنظیمات سیستم کاربر تعیین می‌شود. رنگ‌بندی از طریق متغیرهای CSS در `app/globals.css` -(`--color-bg-page`, `--color-text-primary`, ...) و کلاس‌های کمکی `bg-page` / `bg-surface` / -`text-primary` / `text-secondary` / `border-subtle` مدیریت می‌شود؛ برای هر رنگ جدید فقط همین‌جا را -ویرایش کنید. تم Ant Design هم به‌صورت خودکار (`darkAlgorithm` / `defaultAlgorithm`) هماهنگ می‌شود. +## تنظیم API -### دو زبانه (فارسی / انگلیسی) -دکمه «EN / فا» در هدر. تمام متن‌های سایت (منو، هیرو، محصولات، درباره ما، مکان‌های نصب، فرم تیکت، -چت، فوتر) از `data/content.js` خوانده می‌شوند — یک آبجکت با کلیدهای `fa` و `en`. تغییر زبان همچنین -جهت صفحه (`dir`) و `lang` تگ `html` را به‌روزرسانی می‌کند. برای اضافه یا اصلاح متن، فقط -`data/content.js` را ویرایش کنید؛ کامپوننت‌ها نیازی به تغییر ندارند. +برای تعیین آدرس بک‌اند، از `.env.local.example` یک فایل `.env.local` بسازید و +`NEXT_PUBLIC_API_BASE_URL` را تنظیم کنید. کلاینت مشترک API در `lib/axios.js` قرار دارد. -> نکته فنی: بخش اسکرول افقی (`HorizontalShowcase`) به‌عمد همیشه `dir="ltr"` روی خود track دارد -> (چون محاسبات GSAP به آن نیاز دارد)، اما هر کارت داخلش جهت متن را بر اساس زبان فعال تنظیم می‌کند. +## جایگزینی رسانه‌ها -### فرم تیکت پشتیبانی -`components/TicketForm.jsx` — فرم Ant Design با اعتبارسنجی (نام، تلفن، ایمیل اختیاری، موضوع، -اولویت، پیام). ارسال فعلاً فقط یک پیام موفقیت نمایش می‌دهد؛ بلوک کامنت‌شده -`// TODO: Connect to the real ticketing/support API` دقیقاً نشان می‌دهد کجا باید axios را وصل کنید. - -### ویجت چت -دکمه شناور گوشه صفحه (`components/ChatWidget.jsx`) یک مودال گفتگوی ساده باز می‌کند. پیام‌ها فعلاً -در state محلی نگه‌داری می‌شوند و یک پاسخ خودکار نمایشی می‌دهند. بلوک -`// TODO: Connect to a real chat/support backend` محل اتصال به بک‌اند واقعی (یا وب‌سوکت) را مشخص -کرده. - -### هیرو: تایپ‌رایتر + پس‌زمینه رنگی در حال تغییر -هدلاین با افکت تایپ‌رایتر بین چند جمله (`data/content.js` → `hero.typingPhrases`) می‌چرخد. -پس‌زمینه به‌صورت حلقه‌ای بین سبز برند (`#00a693`)، مشکی و سفید تغییر رنگ می‌دهد (با GSAP). یک لایه -scrim تیره روی آن قرار دارد تا متن در هر سه حالت خوانا بماند. وقتی ویدیوی هیرو را آماده کردید، -داخل `components/HeroSection.jsx` یک بلوک `TODO` کامنت‌شده دقیقاً نشان می‌دهد چطور به‌جای پس‌زمینه -رنگی، ویدیو را جایگزین کنید (فقط از حالت کامنت خارجش کنید). - -### ورود به پنل کاربری -دکمه «ورود به پنل کاربری» / «Sign In» در هدر اضافه شده (در `components/Header.jsx`). چون گفتید -لینک‌دهی را خودتان انجام می‌دهید، فعلاً فقط یک `onClick` خالی با کامنت TODO دارد — مسیر واقعی پنل -کاربری را همان‌جا وصل کنید. - -## جایگزینی دارایی‌های واقعی (تصاویر/گیف/ویدیو) -همان‌طور که قبلاً توضیح داده شد، فایل‌های داخل `public/images`, `public/gifs`, `public/video` -placeholder هستند. برای جایگزینی، فایل جدید را با همان نام/مسیر جایگزین کنید یا مسیر را در -`data/content.js` تغییر دهید. - -## اتصال درگاه پرداخت -در `components/CartDrawer.jsx` داخل `handleCheckout` بلوک `// TODO: Connect to payment gateway API` -با نمونه‌کد axios کامنت‌شده وجود دارد. +فایل‌های واقعی را با مسیرهای موجود در `data/content.js` جایگزین کنید یا مسیرها را در همان فایل تغییر دهید. +ویدیوی پس‌زمینه ویترین از `public/video/showcase-placeholder.mp4` خوانده می‌شود. diff --git a/app/globals.css b/app/globals.css index 10f2266..71728fb 100644 --- a/app/globals.css +++ b/app/globals.css @@ -219,35 +219,3 @@ html[dir='rtl'] body { opacity: 0; } } - -/* ------------------------------------------------------------------ */ -/* نوار لوگوی بی‌پایان (trusted-by marquee) */ -/* ------------------------------------------------------------------ */ -@keyframes marqueeScroll { - from { - transform: translateX(0); - } - to { - transform: translateX(-50%); - } - /* دقیقاً نصف عرض، چون لیست دو برابر شده */ -} - -.animate-marquee { - animation: marqueeScroll 28s linear infinite; -} - -.animate-marquee:hover { - animation-play-state: paused; -} - -/* ------------------------------------------------------------------ */ -/* Chat widget */ -/* ------------------------------------------------------------------ */ -.chat-bubble { - max-width: 80%; - padding: 10px 14px; - border-radius: 14px; - font-size: 0.875rem; - line-height: 1.6; -} diff --git a/app/page.jsx b/app/page.jsx index e415bcb..e89b60c 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -6,32 +6,18 @@ import HeroSection from '@/components/HeroSection'; import CartDrawer from '@/components/CartDrawer'; import Footer from '@/components/Footer'; import Header from '@/components/Header'; -import TrustedBySection from '@/components/TrustedBySection'; -import CatalogSection from '@/components/CatalogSection'; -import IntroVideoSection from '@/components/IntroVideoSection'; -import MapSection from '@/components/MapSection'; -import TicketForm from '@/components/TicketForm'; -import ChatWidget from '@/components/ChatWidget'; - export default function HomePage() { - return (
- {/**/} - {/**/} - {/**/} - {/**/} - {/**/} - {/**/}
); diff --git a/components/CatalogSection.jsx b/components/CatalogSection.jsx deleted file mode 100644 index 01dc39f..0000000 --- a/components/CatalogSection.jsx +++ /dev/null @@ -1,86 +0,0 @@ -'use client'; - -// کاتالوگ محصولات جانبی (زعفران، تنقلات، نوشیدنی و ...) که مشتریان دستگاه با قیمت کمتر از بازار -// می‌توانند سفارش دهند. دکمه «افزودن به سفارش» از همان سبد خرید Redux دستگاه‌ها استفاده می‌کند. - -import { Button, message } from 'antd'; -import { PlusOutlined } from '@ant-design/icons'; -import { useDispatch } from 'react-redux'; -import { addToCart } from '@/store/cartSlice'; -import { useLanguage } from '@/app/context/LanguageContext'; - -function toLocaleNumber(n, locale) { - return n.toLocaleString(locale === 'fa' ? 'fa-IR' : 'en-US'); -} - -export default function CatalogSection() { - const { t, locale } = useLanguage(); - const dispatch = useDispatch(); - const currencySuffix = locale === 'fa' ? ' ریال' : ' Rials'; - - const handleAdd = (item) => { - dispatch( - addToCart({ - id: `catalog-${item.id}`, - name: item.name, - priceNumeric: item.priceNumeric, - priceLabel: `${toLocaleNumber(item.priceNumeric, locale)}${currencySuffix}`, - image: item.image, - }) - ); - - message.success(t.catalog.addToast(item.name)); - }; - - return ( -
-
-
- {t.catalog.eyebrow} -

{t.catalog.title}

-
-

- {t.catalog.subtitle} -

- -
- {t.catalog.items.map((item) => ( -
-
- {item.name} -
- {item.category} -

{item.name}

-
-
- -
-
-

- {toLocaleNumber(item.originalPriceNumeric, locale)} - {currencySuffix} -

-

- {toLocaleNumber(item.priceNumeric, locale)} - {currencySuffix} -

-
- -
-
- ))} -
-
-
- ); -} diff --git a/components/ChatWidget.jsx b/components/ChatWidget.jsx deleted file mode 100644 index 788c9b6..0000000 --- a/components/ChatWidget.jsx +++ /dev/null @@ -1,103 +0,0 @@ -'use client'; - -// آیکون شناور چت گوشه صفحه؛ با کلیک یک مودال گفتگوی ساده باز می‌شود (فعلاً بدون اتصال به بک‌اند واقعی). - -import { useEffect, useRef, useState } from 'react'; -import { Modal, Input, Button, Tooltip } from 'antd'; -import { MessageOutlined, SendOutlined, CloseOutlined } from '@ant-design/icons'; -import { useLanguage } from '@/app/context/LanguageContext'; - -export default function ChatWidget() { - const { t, dir } = useLanguage(); - const [open, setOpen] = useState(false); - const [draft, setDraft] = useState(''); - const [messages, setMessages] = useState([]); - const scrollRef = useRef(null); - - // Seed the bot greeting once, in the current language. - useEffect(() => { - setMessages([{ from: 'bot', text: t.chat.initialBotMessage }]); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [t.chat.initialBotMessage]); - - useEffect(() => { - scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' }); - }, [messages, open]); - - const handleSend = () => { - const text = draft.trim(); - if (!text) return; - - setMessages((prev) => [...prev, { from: 'user', text }]); - setDraft(''); - - // ----------------------------------------------------------------- - // TODO: Connect to a real chat/support backend (e.g. via axios or a - // websocket) instead of this local placeholder reply. - // - // import axios from 'axios'; - // const { data } = await axios.post('/api/chat', { message: text }); - // setMessages((prev) => [...prev, { from: 'bot', text: data.reply }]); - // api() <===> todo : use this func to fetch ... - - // ----------------------------------------------------------------- - setTimeout(() => { - setMessages((prev) => [...prev, { from: 'bot', text: t.chat.fallbackReply }]); - }, 600); - }; - - return ( - <> - - - - - setOpen(false)} - footer={null} - closeIcon={} - title={t.chat.title} - centered - > -
- {messages.map((m, i) => ( -
- {m.text} -
- ))} -
- -
- setDraft(e.target.value)} - onPressEnter={handleSend} - placeholder={t.chat.placeholder} - /> - -
-
- - ); -} diff --git a/components/Footer.jsx b/components/Footer.jsx index 73eb71e..10a482d 100644 --- a/components/Footer.jsx +++ b/components/Footer.jsx @@ -36,11 +36,7 @@ export default function Footer() {
  • {t.nav.home}
  • {t.nav.showcase}
  • {t.nav.about}
  • - {/*
  • {t.nav.catalog}
  • */}
  • {t.nav.locations}
  • - {/*
  • {t.nav.video}
  • */} - {/*
  • {t.nav.map}
  • */} - {/*
  • {t.nav.support}
  • */} diff --git a/components/Header.jsx b/components/Header.jsx index 6df2259..4507b10 100644 --- a/components/Header.jsx +++ b/components/Header.jsx @@ -23,11 +23,7 @@ export default function Header() { {href: '#hero', label: t.nav.home}, {href: '#showcase', label: t.nav.showcase}, {href: '#about', label: t.nav.about}, - // {href: '#catalog', label: t.nav.catalog}, {href: '#locations', label: t.nav.locations}, - // {href: '#video', label: t.nav.video}, - // {href: '#map', label: t.nav.map}, - // {href: '#support', label: t.nav.support}, ]; useEffect(() => { diff --git a/components/IntroVideoSection.jsx b/components/IntroVideoSection.jsx deleted file mode 100644 index 56283c9..0000000 --- a/components/IntroVideoSection.jsx +++ /dev/null @@ -1,35 +0,0 @@ -'use client'; - -// سکشن مستقل ویدیوی معرفی شرکت (جدا از ویدیوی پس‌زمینه ویترین محصولات). - -import { useLanguage } from '@/app/context/LanguageContext'; -import { introVideoSrc, introVideoPoster } from '@/data/content'; - -export default function IntroVideoSection() { - const { t } = useLanguage(); - - return ( -
    -
    - {t.introVideo.eyebrow} -

    - {t.introVideo.title} -

    -

    {t.introVideo.subtitle}

    - - {/* - TODO: replace /public/video/company-intro-placeholder.mp4 with the real - company introduction video (keep the same filename, or update - `introVideoSrc` in data/content.js). The poster image shows before - playback starts — swap /public/images/video-poster.svg the same way. - */} -
    - -
    -
    -
    - ); -} diff --git a/components/MapSection.jsx b/components/MapSection.jsx deleted file mode 100644 index 0239cd4..0000000 --- a/components/MapSection.jsx +++ /dev/null @@ -1,129 +0,0 @@ -'use client'; - -// نقشه نشان (Neshan) — از Static Map API استفاده می‌کند. تا وقتی کلید API خالی باشد، -// به‌جای نقشه یک پیام راهنما نشان داده می‌شود (نه تصویر خراب). - -import {useState} from 'react'; -import {EnvironmentOutlined} from '@ant-design/icons'; -import {useLanguage} from '@/app/context/LanguageContext'; -import {useTheme} from '@/app/context/ThemeContext'; -import {mapLocations} from '@/data/content'; -import Image from "next/image"; - -// --------------------------------------------------------------------------- -// Neshan (نشان) map integration. -// -// TODO: put your Neshan API key here, or (preferred) set it as an environment -// variable in `.env.local`: -// NEXT_PUBLIC_NESHAN_API_KEY=your-key-here -// Get a key from https://platform.neshan.org -// --------------------------------------------------------------------------- -const NESHAN_API_KEY = process.env.NEXT_PUBLIC_NESHAN_API_KEY || ''; - -// Neshan's Static Map API — a plain image, no JS SDK/script tag needed. -// Verify query param names/values against the current Neshan docs -// (https://platform.neshan.org/api/static-map/) if this ever needs updating — -// `type` accepts their day/night map styles, e.g. "standard-day" / "standard-night". -function staticMapUrl({lat, lng}, mapStyle) { - const params = new URLSearchParams({ - key: NESHAN_API_KEY, - type: mapStyle, - center: `${lat},${lng}`, - zoom: '15', - size: '600x400', - marker: `${lat},${lng}`, - }); - return `https://api.neshan.org/v5/static?${params.toString()}`; -} - -function neshanViewUrl({lat, lng}) { - return `https://neshan.org/maps/@${lat},${lng},15z`; -} - -function MapThumb({coords, mapStyle, alt}) { - const [errored, setErrored] = useState(false); - const {t} = useLanguage(); - - if (!NESHAN_API_KEY || errored) { - return ( -
    - -

    {t.map.configNotice}

    -
    - ); - } - - return ( - // eslint-disable-next-line @next/next/no-img-element - {alt} setErrored(true)} - className="w-full h-full object-cover" - /> - ); -} - -export default function MapSection() { - const {t} = useLanguage(); - const {theme} = useTheme(); - const mapStyle = theme === 'dark' ? 'standard-night' : 'standard-day'; - - const pins = [ - { - key: 'factory', - label: t.map.factoryLabel, - coords: mapLocations.factory, - address: t.contactInfo.factoryAddress - }, - { - key: 'sales', - label: t.map.salesLabel, - coords: mapLocations.salesOffice, - address: t.contactInfo.salesOfficeAddress - }, - ]; - - return ( -
    -
    -
    - {t.map.eyebrow} -

    {t.map.title}

    -
    - -
    -
    - -
    - -
    - {pins.map((pin) => ( -
    -
    -
    - -
    -
    -

    {pin.label}

    -

    {pin.address}

    - - {t.map.openInMaps} ↗ - -
    -
    -
    - ))} -
    -
    -
    -
    - ); -} diff --git a/components/TicketForm.jsx b/components/TicketForm.jsx deleted file mode 100644 index 6a561bb..0000000 --- a/components/TicketForm.jsx +++ /dev/null @@ -1,119 +0,0 @@ -'use client'; - -// فرم ثبت تیکت پشتیبانی با اعتبارسنجی antd؛ ارسال واقعی به بک‌اند در بلوک TODO داخل کد مشخص شده. - -import { Form, Input, Select, Button, message } from 'antd'; -import { useLanguage } from '@/app/context/LanguageContext'; - -const { TextArea } = Input; - -export default function TicketForm() { - const { t, locale } = useLanguage(); - const [form] = Form.useForm(); - - const handleFinish = async (values) => { - // ------------------------------------------------------------------- - // TODO: Connect to the real ticketing/support API. - // - // import axios from 'axios'; - // - // try { - // await axios.post('/api/tickets', values); - // } catch (err) { - // message.error('...'); - // return; - // } - - // api() <===> todo : use this func to fetch ... - - // ------------------------------------------------------------------- - console.log('Ticket submitted (placeholder):', values); - message.success(t.ticket.successMsg); - form.resetFields(); - }; - - return ( -
    -
    -
    - {t.ticket.eyebrow} -

    - {t.ticket.title} -

    -

    {t.ticket.subtitle}

    -
    - -
    -
    - {t.ticket.name}} - name="name" - rules={[{ required: true, message: t.ticket.requiredMsg }]} - > - - - - {t.ticket.phone}} - name="phone" - rules={[ - { required: true, message: t.ticket.requiredMsg }, - { pattern: /^[0-9+\s-]{7,15}$/, message: t.ticket.invalidPhone }, - ]} - > - - -
    - -
    - {t.ticket.email}} - name="email" - rules={[{ type: 'email', message: t.ticket.invalidEmail }]} - > - - - - {t.ticket.priority}} - name="priority" - > - - - - {t.ticket.message}} - name="message" - rules={[{ required: true, message: t.ticket.requiredMsg }]} - > -