refactor: migrate project to TypeScript and translate comments to English
Build & Deploy (develop) / build-and-deploy (push) Successful in 2m23s
Build & Deploy (develop) / build-and-deploy (push) Successful in 2m23s
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
// سکشن معرفی شرکت با متن بلند؛ بهخاطر طولانی بودن متن، یک دکمه «ادامه مطلب» برای باز/بسته کردن دارد.
|
||||
// Company introduction with optional expandable paragraphs.
|
||||
|
||||
import {useState} from 'react';
|
||||
import {useLanguage} from '@/app/context/LanguageContext';
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
// کشوی (Drawer) سبد خرید: نمایش آیتمهای انتخابی، تغییر تعداد، جمع کل، و دکمه پرداخت (placeholder).
|
||||
// Cart drawer with quantity editing, totals, and placeholder checkout.
|
||||
|
||||
import {Drawer, Button, Empty, InputNumber, message} from 'antd';
|
||||
import {DeleteOutlined} from '@ant-design/icons';
|
||||
import {useSelector, useDispatch} from 'react-redux';
|
||||
import {useAppSelector, useAppDispatch} from '@/store/hooks';
|
||||
import {
|
||||
selectCartItems,
|
||||
selectCartTotal,
|
||||
@@ -14,17 +14,18 @@ import {
|
||||
setQuantity,
|
||||
} from '@/store/cartSlice';
|
||||
import {useLanguage} from '@/app/context/LanguageContext';
|
||||
import type {Locale} from '@/types/domain';
|
||||
import api from "@/lib/axios";
|
||||
|
||||
function toLocaleNumber(n, locale) {
|
||||
function toLocaleNumber(n: number, locale: Locale) {
|
||||
return n.toLocaleString(locale === 'fa' ? 'fa-IR' : 'en-US');
|
||||
}
|
||||
|
||||
export default function CartDrawer() {
|
||||
const dispatch = useDispatch();
|
||||
const isOpen = useSelector(selectIsDrawerOpen);
|
||||
const items = useSelector(selectCartItems);
|
||||
const total = useSelector(selectCartTotal);
|
||||
const dispatch = useAppDispatch();
|
||||
const isOpen = useAppSelector(selectIsDrawerOpen);
|
||||
const items = useAppSelector(selectCartItems);
|
||||
const total = useAppSelector(selectCartTotal);
|
||||
const {t, locale, dir} = useLanguage();
|
||||
|
||||
const currencySuffix = locale === 'fa' ? ' ریال' : ' Rials';
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
// نوار مزایا: ضمانت کالا، مشاوره رایگان، خرید اقساطی، بستهبندی ایمن.
|
||||
// Service benefits: warranty, consultation, installments, and packaging.
|
||||
|
||||
import {
|
||||
SafetyCertificateOutlined,
|
||||
@@ -1,17 +1,18 @@
|
||||
'use client';
|
||||
|
||||
// رندر آیتمهای شناور (چیپس/نوشابه) دور دستگاه با حرکت تصادفی و ملایم GSAP.
|
||||
// Animate decorative snack images with gentle randomized motion.
|
||||
|
||||
import type {FloatingSnack} from '@/types/domain';
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {gsap} from 'gsap';
|
||||
|
||||
/**
|
||||
* Renders the centralized `floatingSnacks` config as absolutely-positioned
|
||||
* images and gives each one a gentle, randomized floating drift via GSAP.
|
||||
* Positions/sizes/images all come from data/content.js — edit there only.
|
||||
* Positions/sizes/images all come from data/content.ts — edit there only.
|
||||
*/
|
||||
export default function FloatingItems({items}) {
|
||||
const containerRef = useRef(null);
|
||||
export default function FloatingItems({items}: {items: FloatingSnack[]}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const nodes = containerRef.current?.querySelectorAll('[data-float-item]');
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
// فوتر سایت: درباره ما کوتاه، لینکهای شبکه اجتماعی، دسترسی سریع، و اطلاعات تماس/آدرس.
|
||||
// Company information, quick links, social links, and contact details.
|
||||
|
||||
import { PhoneOutlined, EnvironmentOutlined } from '@ant-design/icons';
|
||||
import { useLanguage } from '@/app/context/LanguageContext';
|
||||
@@ -1,20 +1,20 @@
|
||||
'use client';
|
||||
|
||||
// هدر ثابت بالای صفحه: لوگو، منوی ناوبری، سوییچ زبان/تم، دکمه ورود به پنل کاربری و آیکون سبد خرید.
|
||||
// Fixed navigation with language, theme, account, and cart controls.
|
||||
|
||||
import {useState, useEffect} from 'react';
|
||||
import Image from 'next/image';
|
||||
import {Badge, Button} from 'antd';
|
||||
import {ShoppingCartOutlined, MenuOutlined, CloseOutlined, UserOutlined} from '@ant-design/icons';
|
||||
import {useSelector, useDispatch} from 'react-redux';
|
||||
import {useAppSelector, useAppDispatch} from '@/store/hooks';
|
||||
import {selectCartCount, openDrawer} from '@/store/cartSlice';
|
||||
import {useLanguage} from '@/app/context/LanguageContext';
|
||||
import ThemeToggle from './ThemeToggle';
|
||||
import LanguageToggle from './LanguageToggle';
|
||||
|
||||
export default function Header() {
|
||||
const dispatch = useDispatch();
|
||||
const cartCount = useSelector(selectCartCount);
|
||||
const dispatch = useAppDispatch();
|
||||
const cartCount = useAppSelector(selectCartCount);
|
||||
const {t} = useLanguage();
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
@@ -1,25 +1,20 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* سکشن اول سایت (Hero) — ایده کلی از صفحه وندینگ سایت Sielaff گرفته شده:
|
||||
* یک اسلایدر تمامعرض که هر ۵ ثانیه بهصورت خودکار به اسلاید بعدی میرود،
|
||||
* همراه با یک شکل مورب رنگی (بهجای قرمز آنها، رنگ سبز برند خودمان #00a693)،
|
||||
* عنوان بزرگ دو خطی، و دکمههای فلش قبلی/بعدی برای ناوبری دستی.
|
||||
*/
|
||||
/** Full-width product carousel with autoplay and manual navigation. */
|
||||
|
||||
import {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import {LeftOutlined, RightOutlined} from '@ant-design/icons';
|
||||
import {useLanguage} from '@/app/context/LanguageContext';
|
||||
|
||||
const AUTOPLAY_DELAY = 5000; // هر ۵ ثانیه اسلاید عوض میشود
|
||||
const AUTOPLAY_DELAY = 5000; // Advance every five seconds.
|
||||
|
||||
export default function HeroSection() {
|
||||
const {t, dir} = useLanguage();
|
||||
const slides = t.hero.slides;
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const timerRef = useRef(null);
|
||||
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
|
||||
// رفتن به اسلاید بعدی (با چرخش به اول لیست وقتی به آخر رسید)
|
||||
// Wrap around after the last slide.
|
||||
const goToNext = useCallback(() => {
|
||||
setActiveIndex((prev) => (prev + 1) % slides.length);
|
||||
}, [slides.length]);
|
||||
@@ -28,9 +23,7 @@ export default function HeroSection() {
|
||||
setActiveIndex((prev) => (prev - 1 + slides.length) % slides.length);
|
||||
}, [slides.length]);
|
||||
|
||||
// تایمر پخش خودکار: هر بار که اسلاید عوض میشود، تایمر قبلی پاک و یک تایمر
|
||||
// جدید ۵ ثانیهای ست میشود. با کلیک دستی روی فلشها هم همین تابع دوباره
|
||||
// صدا زده میشود تا شمارش از نو شروع شود (تجربه کاربری بهتر).
|
||||
// Restart the autoplay timer after manual navigation.
|
||||
const restartAutoplay = useCallback(() => {
|
||||
if (timerRef.current) clearInterval(timerRef.current);
|
||||
timerRef.current = setInterval(goToNext, AUTOPLAY_DELAY);
|
||||
@@ -43,10 +36,10 @@ export default function HeroSection() {
|
||||
};
|
||||
}, [restartAutoplay]);
|
||||
|
||||
const handleManualNav = (direction) => {
|
||||
const handleManualNav = (direction: 'next' | 'prev') => {
|
||||
if (direction === 'next') goToNext();
|
||||
else goToPrev();
|
||||
restartAutoplay(); // شمارش ۵ ثانیه بعد از کلیک دستی از نو شروع شود
|
||||
restartAutoplay(); // Reset the delay after a manual click.
|
||||
};
|
||||
|
||||
const slide = slides[activeIndex];
|
||||
@@ -54,7 +47,7 @@ export default function HeroSection() {
|
||||
return (
|
||||
<section id="hero" className="relative h-screen w-full overflow-hidden bg-page">
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
{/* شکل مورب تزئینی سمت راست/چپ — نسخه سبز برند بهجای قرمز Sielaff */}
|
||||
{/* Decorative brand-colored shape. */}
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
<div
|
||||
className="absolute inset-y-0 w-1/3 hidden md:block"
|
||||
@@ -68,8 +61,8 @@ export default function HeroSection() {
|
||||
/>
|
||||
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
{/* اسلایدهای عکس — همه اسلایدها روی هم قرار دارند و فقط با opacity */}
|
||||
{/* محو/نمایان میشوند (transition نرم بهجای پرش ناگهانی) */}
|
||||
{/* Fade between overlapping product images. */}
|
||||
{/* Use opacity transitions for smooth slide changes. */}
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
<div className="absolute inset-0">
|
||||
{slides.map((s, i) => (
|
||||
@@ -88,7 +81,7 @@ export default function HeroSection() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* لایهی نیمهشفاف پشت متن، برای خوانایی روی هر عکسی */}
|
||||
{/* Overlay for text readability. */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
@@ -98,7 +91,7 @@ export default function HeroSection() {
|
||||
/>
|
||||
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
{/* متن روی اسلاید: کیکر کوچک + عنوان بزرگ دو خطی + توضیح + دکمه CTA */}
|
||||
{/* Active slide headline, description, and call to action. */}
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
<div
|
||||
className="relative z-10 h-full flex flex-col justify-end md:justify-end px-6 md:px-16 pb-28 md:pb-72">
|
||||
@@ -124,7 +117,7 @@ export default function HeroSection() {
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
{/* فلشهای ناوبری دستی (قبلی / بعدی) — وسط دو طرف */}
|
||||
{/* Previous and next slide controls. */}
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
<button
|
||||
onClick={() => handleManualNav('prev')}
|
||||
@@ -141,7 +134,7 @@ export default function HeroSection() {
|
||||
<LeftOutlined/>
|
||||
</button>
|
||||
|
||||
{/* نقطههای شمارشگر اسلاید پایین صفحه — کلیک روی هرکدام مستقیم به همان اسلاید میرود */}
|
||||
{/* Jump directly to a slide using the indicators. */}
|
||||
<div className="absolute bottom-8 inset-x-0 z-20 flex justify-center gap-2">
|
||||
{slides.map((s, i) => (
|
||||
<button
|
||||
@@ -1,11 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import type {Machine} from '@/types/domain';
|
||||
import {useEffect, useRef, useState} from 'react';
|
||||
import {gsap} from 'gsap';
|
||||
import {ScrollTrigger} from 'gsap/ScrollTrigger';
|
||||
import {Modal, Button, message} from 'antd';
|
||||
import {ShoppingCartOutlined} from '@ant-design/icons';
|
||||
import {useDispatch} from 'react-redux';
|
||||
import {useAppDispatch} from '@/store/hooks';
|
||||
import VendingCard from './VendingCard';
|
||||
import {showcaseVideoSrc} from '@/data/content';
|
||||
import {useLanguage} from '@/app/context/LanguageContext';
|
||||
@@ -14,12 +15,12 @@ import {addToCart} from '@/store/cartSlice';
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
export default function HorizontalShowcase() {
|
||||
const sectionRef = useRef(null);
|
||||
const trackRef = useRef(null);
|
||||
const videoRef = useRef(null);
|
||||
const dispatch = useDispatch();
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
const trackRef = useRef<HTMLDivElement>(null);
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const dispatch = useAppDispatch();
|
||||
const {t} = useLanguage();
|
||||
const [quickViewMachine, setQuickViewMachine] = useState(null);
|
||||
const [quickViewMachine, setQuickViewMachine] = useState<Machine | null>(null);
|
||||
const [videoAvailable, setVideoAvailable] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -33,10 +34,10 @@ export default function HorizontalShowcase() {
|
||||
Math.max(track.scrollWidth - window.innerWidth, 0)
|
||||
);
|
||||
|
||||
let animationFrameId = null;
|
||||
let animationFrameId: number | null = null;
|
||||
let latestProgress = 0;
|
||||
|
||||
const syncVideo = (progress) => {
|
||||
const syncVideo = (progress: number) => {
|
||||
latestProgress = progress;
|
||||
if (animationFrameId !== null) return;
|
||||
|
||||
@@ -148,4 +149,4 @@ export default function HorizontalShowcase() {
|
||||
</Modal>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
// دکمه سوییچ زبان فارسی/انگلیسی.
|
||||
// Switch between Persian and English.
|
||||
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { useLanguage } from '@/app/context/LanguageContext';
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
// گرید «مکان مناسب برای نصب» — هتلها، مدارس، داروخانهها، بیمارستانها، شیفت شب، محصولات تخصصی.
|
||||
// Suggested installation locations for vending machines.
|
||||
|
||||
import {
|
||||
HomeOutlined,
|
||||
@@ -1,18 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import type {Machine} from '@/types/domain';
|
||||
import {useState} from 'react';
|
||||
import {Modal, Button, message} from 'antd';
|
||||
import {ShoppingCartOutlined, EyeOutlined} from '@ant-design/icons';
|
||||
import {useDispatch} from 'react-redux';
|
||||
import {useAppDispatch} from '@/store/hooks';
|
||||
import {useLanguage} from '@/app/context/LanguageContext';
|
||||
import {addToCart} from '@/store/cartSlice';
|
||||
|
||||
export default function MobileShowcase() {
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const {t, dir} = useLanguage();
|
||||
const [quickViewMachine, setQuickViewMachine] = useState(null);
|
||||
const [quickViewMachine, setQuickViewMachine] = useState<Machine | null>(null);
|
||||
|
||||
const handleAddToCart = (machine) => {
|
||||
const handleAddToCart = (machine: Machine) => {
|
||||
dispatch(
|
||||
addToCart({
|
||||
id: machine.id,
|
||||
@@ -104,4 +105,4 @@ export default function MobileShowcase() {
|
||||
</Modal>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,14 @@ import MobileShowcase from './MobileShowcase';
|
||||
const MOBILE_BREAKPOINT = 768;
|
||||
|
||||
export default function ShowcaseSection() {
|
||||
const [isMobile, setIsMobile] = useState(null);
|
||||
const [isMobile, setIsMobile] = useState<boolean | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia(
|
||||
`(max-width: ${MOBILE_BREAKPOINT - 1}px)`
|
||||
);
|
||||
|
||||
const handleChange = (event) => setIsMobile(event.matches);
|
||||
const handleChange = (event: MediaQueryListEvent) => setIsMobile(event.matches);
|
||||
|
||||
setIsMobile(mediaQuery.matches);
|
||||
mediaQuery.addEventListener('change', handleChange);
|
||||
@@ -25,4 +25,4 @@ export default function ShowcaseSection() {
|
||||
if (isMobile === null) return null;
|
||||
|
||||
return isMobile ? <MobileShowcase/> : <HorizontalShowcase/>;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,25 @@
|
||||
'use client';
|
||||
|
||||
|
||||
import type {Machine} from '@/types/domain';
|
||||
import type {MouseEvent} from 'react';
|
||||
import {useRef} from 'react';
|
||||
import {Button, message} from 'antd';
|
||||
import {ShoppingCartOutlined, EyeOutlined} from '@ant-design/icons';
|
||||
import {useDispatch} from 'react-redux';
|
||||
import {useAppDispatch} from '@/store/hooks';
|
||||
import {addToCart} from '@/store/cartSlice';
|
||||
import {useLanguage} from '@/app/context/LanguageContext';
|
||||
import FloatingItems from './FloatingItems';
|
||||
import {floatingSnacks} from '@/data/content';
|
||||
|
||||
export default function VendingCard({machine, onQuickView}) {
|
||||
const dispatch = useDispatch();
|
||||
export default function VendingCard({machine, onQuickView}: {machine: Machine; onQuickView?: (machine: Machine) => void}) {
|
||||
const dispatch = useAppDispatch();
|
||||
const {t, dir} = useLanguage();
|
||||
const stageRef = useRef(null);
|
||||
const lensRef = useRef(null);
|
||||
const revealRef = useRef(null);
|
||||
const stageRef = useRef<HTMLDivElement>(null);
|
||||
const lensRef = useRef<HTMLDivElement>(null);
|
||||
const revealRef = useRef<HTMLImageElement>(null);
|
||||
|
||||
const handleMouseMove = (e) => {
|
||||
const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
|
||||
const stage = stageRef.current;
|
||||
if (!stage) return;
|
||||
const rect = stage.getBoundingClientRect();
|
||||
Reference in New Issue
Block a user