refactor: remove disabled home sections and their unused assets

This commit is contained in:
2026-09-14 14:39:05 +03:30
parent d5a7ed8092
commit 1ef621f832
26 changed files with 46 additions and 1040 deletions
-86
View File
@@ -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 (
<section id="catalog" className="relative px-6 md:px-16 py-20 md:py-28 bg-page border-t border-subtle">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-4">
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.catalog.eyebrow}</span>
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text mt-3">{t.catalog.title}</h2>
</div>
<p className="text-secondary text-sm md:text-base text-center max-w-2xl mx-auto mb-12">
{t.catalog.subtitle}
</p>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
{t.catalog.items.map((item) => (
<div
key={item.id}
className="bg-surface border border-subtle rounded-2xl p-5 flex flex-col hover:border-brand-500/60 transition-colors"
>
<div className="flex items-center gap-4 mb-4">
<img src={item.image} alt={item.name} className="w-16 h-16 object-contain shrink-0" />
<div className="min-w-0">
<span className="text-[11px] text-brand-400 font-semibold">{item.category}</span>
<h3 className="text-primary font-bold text-sm leading-6">{item.name}</h3>
</div>
</div>
<div className="mt-auto flex items-end justify-between gap-2">
<div>
<p className="text-xs text-secondary line-through opacity-70">
{toLocaleNumber(item.originalPriceNumeric, locale)}
{currencySuffix}
</p>
<p className="text-base font-bold text-primary">
{toLocaleNumber(item.priceNumeric, locale)}
{currencySuffix}
</p>
</div>
<Button
type="primary"
size="middle"
icon={<PlusOutlined />}
onClick={() => handleAdd(item)}
>
{t.catalog.addToOrder}
</Button>
</div>
</div>
))}
</div>
</div>
</section>
);
}
-103
View File
@@ -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 (
<>
<Tooltip title={t.chat.fabLabel} placement="left">
<button
onClick={() => setOpen(true)}
aria-label={t.chat.fabLabel}
className="fixed bottom-6 z-40 w-14 h-14 rounded-full bg-brand hover:bg-brand-400 text-ink-950 shadow-brandGlow flex items-center justify-center text-2xl transition-transform hover:scale-105"
style={dir === 'rtl' ? { left: '1.5rem' } : { right: '1.5rem' }}
>
<MessageOutlined />
</button>
</Tooltip>
<Modal
open={open}
onCancel={() => setOpen(false)}
footer={null}
closeIcon={<CloseOutlined />}
title={t.chat.title}
centered
>
<div
ref={scrollRef}
className="flex flex-col gap-3 h-80 overflow-y-auto py-2 px-1"
>
{messages.map((m, i) => (
<div
key={i}
className={`chat-bubble ${
m.from === 'user'
? 'self-end bg-brand text-ink-950'
: 'self-start bg-surface-strong text-primary'
}`}
>
{m.text}
</div>
))}
</div>
<div className="flex items-center gap-2 mt-3 border-t border-subtle pt-3">
<Input
size="large"
value={draft}
onChange={(e) => setDraft(e.target.value)}
onPressEnter={handleSend}
placeholder={t.chat.placeholder}
/>
<Button type="primary" size="large" icon={<SendOutlined />} onClick={handleSend}>
{t.chat.send}
</Button>
</div>
</Modal>
</>
);
}
-4
View File
@@ -36,11 +36,7 @@ export default function Footer() {
<li><a href="#hero" className="hover:text-brand-400">{t.nav.home}</a></li>
<li><a href="#showcase" className="hover:text-brand-400">{t.nav.showcase}</a></li>
<li><a href="#about" className="hover:text-brand-400">{t.nav.about}</a></li>
{/*<li><a href="#catalog" className="hover:text-brand-400">{t.nav.catalog}</a></li>*/}
<li><a href="#locations" className="hover:text-brand-400">{t.nav.locations}</a></li>
{/*<li><a href="#video" className="hover:text-brand-400">{t.nav.video}</a></li>*/}
{/*<li><a href="#map" className="hover:text-brand-400">{t.nav.map}</a></li>*/}
{/*<li><a href="#support" className="hover:text-brand-400">{t.nav.support}</a></li>*/}
</ul>
</div>
-4
View File
@@ -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(() => {
-35
View File
@@ -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 (
<section id="video" className="relative px-6 md:px-16 py-20 md:py-28 bg-elevated border-t border-subtle">
<div className="max-w-5xl mx-auto text-center">
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.introVideo.eyebrow}</span>
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text mt-3 mb-3">
{t.introVideo.title}
</h2>
<p className="text-secondary text-sm md:text-base mb-10">{t.introVideo.subtitle}</p>
{/*
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.
*/}
<div className="rounded-2xl overflow-hidden border border-subtle shadow-brandGlow">
<video controls poster={introVideoPoster} className="w-full aspect-video bg-black">
<source src={introVideoSrc} type="video/mp4" />
{t.introVideo.videoNotSupported}
</video>
</div>
</div>
</section>
);
}
-129
View File
@@ -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 (
<div
className="w-full h-full min-h-[280px] flex flex-col items-center justify-center gap-3 bg-surface text-secondary text-sm text-center p-6">
<EnvironmentOutlined className="text-3xl text-brand-400"/>
<p>{t.map.configNotice}</p>
</div>
);
}
return (
// eslint-disable-next-line @next/next/no-img-element
<img
src={staticMapUrl(coords, mapStyle)}
alt={alt}
onError={() => 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 (
<section id="map" className="relative px-6 md:px-16 py-20 md:py-28 bg-page border-t border-subtle">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-12">
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.map.eyebrow}</span>
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text mt-3">{t.map.title}</h2>
</div>
<div className="grid lg:grid-cols-[1.3fr,1fr] gap-6">
<div className="rounded-2xl overflow-hidden border border-subtle h-[360px] lg:h-auto min-h-[360px]">
<MapThumb coords={pins[0].coords} mapStyle={mapStyle} alt={pins[0].label}/>
</div>
<div className="flex flex-col gap-4">
{pins.map((pin) => (
<div key={pin.key} className="bg-surface border border-subtle rounded-2xl p-5">
<div className="flex items-start gap-3">
<div
className="w-10 h-10 shrink-0 rounded-lg bg-brand/15 border border-subtle flex items-center justify-center text-brand-400">
<EnvironmentOutlined/>
</div>
<div>
<h3 className="text-primary font-bold text-sm mb-1">{pin.label}</h3>
<p className="text-secondary text-sm leading-7 mb-2">{pin.address}</p>
<a
href={neshanViewUrl(pin.coords)}
target="_blank"
rel="noopener noreferrer"
className="text-brand-400 hover:text-brand-300 text-xs font-semibold transition-colors"
>
{t.map.openInMaps} ↗
</a>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</section>
);
}
-119
View File
@@ -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 (
<section id="support" className="relative px-6 md:px-16 py-20 md:py-28 bg-surface border-t border-subtle">
<div className="max-w-2xl mx-auto">
<div className="text-center mb-10">
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.ticket.eyebrow}</span>
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text mt-3 mb-3">
{t.ticket.title}
</h2>
<p className="text-secondary text-sm md:text-base">{t.ticket.subtitle}</p>
</div>
<Form
form={form}
layout="vertical"
onFinish={handleFinish}
requiredMark={false}
initialValues={{ priority: 'medium' }}
>
<div className="grid md:grid-cols-2 gap-x-4">
<Form.Item
label={<span className="text-secondary">{t.ticket.name}</span>}
name="name"
rules={[{ required: true, message: t.ticket.requiredMsg }]}
>
<Input size="large" placeholder={t.ticket.namePlaceholder} />
</Form.Item>
<Form.Item
label={<span className="text-secondary">{t.ticket.phone}</span>}
name="phone"
rules={[
{ required: true, message: t.ticket.requiredMsg },
{ pattern: /^[0-9+\s-]{7,15}$/, message: t.ticket.invalidPhone },
]}
>
<Input size="large" placeholder={t.ticket.phonePlaceholder} dir="ltr" />
</Form.Item>
</div>
<div className="grid md:grid-cols-2 gap-x-4">
<Form.Item
label={<span className="text-secondary">{t.ticket.email}</span>}
name="email"
rules={[{ type: 'email', message: t.ticket.invalidEmail }]}
>
<Input size="large" placeholder={t.ticket.emailPlaceholder} dir="ltr" />
</Form.Item>
<Form.Item
label={<span className="text-secondary">{t.ticket.priority}</span>}
name="priority"
>
<Select
size="large"
options={t.ticket.priorities.map((p) => ({ value: p.value, label: p.label }))}
/>
</Form.Item>
</div>
<Form.Item
label={<span className="text-secondary">{t.ticket.subject}</span>}
name="subject"
rules={[{ required: true, message: t.ticket.requiredMsg }]}
>
<Input size="large" placeholder={t.ticket.subjectPlaceholder} />
</Form.Item>
<Form.Item
label={<span className="text-secondary">{t.ticket.message}</span>}
name="message"
rules={[{ required: true, message: t.ticket.requiredMsg }]}
>
<TextArea rows={5} placeholder={t.ticket.messagePlaceholder} />
</Form.Item>
<Form.Item className="mb-0">
<Button type="primary" size="large" htmlType="submit" block>
{t.ticket.submit}
</Button>
</Form.Item>
</Form>
</div>
</section>
);
}
-56
View File
@@ -1,56 +0,0 @@
'use client';
/**
* سکشن «مشتریانی که به ما اعتماد کرده‌اند» — یک نوار لوگوی بی‌پایان (marquee)
* که به‌آرامی و پیوسته حرکت می‌کند. برای ساخت افکت حلقه‌ی بی‌پایان، لیست
* لوگوها را دو بار پشت‌سرهم رندر می‌کنیم و با انیمیشن CSS آن را جابه‌جا می‌کنیم.
*
* نکته فنی: مسیر (track) همیشه جهت LTR دارد، چون منطق «حرکت به چپ» انیمیشن
* فرض بر همین جهت است — دقیقاً همان مشکلی که در اسکرول افقی محصولات داشتیم و
* حلش کردیم. این‌جا هم برای جلوگیری از همان باگ، جهت را ثابت نگه داشتیم.
*/
import { useLanguage } from '@/app/context/LanguageContext';
export default function TrustedBySection() {
const { t } = useLanguage();
const clients = t.trustedBy.clients;
// لیست لوگوها را دو برابر می‌کنیم تا وقتی نیمه اول با انیمیشن رد شد،
// نیمه دوم دقیقاً از همان‌جا ادامه بدهد و چشم متوجه پرش/شکاف نشود.
const doubledClients = [...clients, ...clients];
return (
<section className="relative px-6 md:px-16 py-16 md:py-20 bg-page border-t border-subtle overflow-hidden">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-10">
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.trustedBy.eyebrow}</span>
<h2 className="text-xl md:text-2xl font-extrabold text-primary mt-2">{t.trustedBy.title}</h2>
</div>
</div>
{/* یک محو تدریجی (fade mask) دو طرف نوار، تا لبه‌های لوگوها بریده به‌نظر نرسد */}
<div
className="relative"
style={{
maskImage: 'linear-gradient(90deg, transparent 0%, black 8%, black 92%, transparent 100%)',
WebkitMaskImage: 'linear-gradient(90deg, transparent 0%, black 8%, black 92%, transparent 100%)',
}}
>
<div className="flex w-max animate-marquee gap-10" dir="ltr">
{doubledClients.map((client, i) => (
<div
key={`${client.id}-${i}`}
className="w-40 md:w-48 shrink-0 flex items-center justify-center bg-surface border border-subtle rounded-xl p-4 opacity-80 hover:opacity-100 transition-opacity"
>
<img
src={`/images/clients/client-${((i % clients.length) + 1)}.svg`}
alt={client.name}
className="w-full h-auto"
/>
</div>
))}
</div>
</div>
</section>
);
}