x
This commit is contained in:
1 parent
f7ac94ca0d
commit
335f3b4f59
41 files changed
+809
-413
No files matched your search
@@ -2,7 +2,8 @@
|
||||
|
||||
import Image from 'next/image'
|
||||
import Link from '../LocalizedLink'
|
||||
import { Clock, Mail, MapPin, MessageCircle } from 'lucide-react'
|
||||
import { Clock, Mail, MapPin, MessageCircle, Send } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import type { Category, PageSection, Product } from '@homaroma/types'
|
||||
import { getCategoryImage } from '@homaroma/types'
|
||||
import ProductCard from '../ProductCard'
|
||||
@@ -125,12 +126,12 @@ function HeroSection({ payload, lang }: { payload: Record<string, unknown>, lang
|
||||
<div className="relative z-10 mx-auto mt-16 w-full max-w-4xl space-y-6 px-8 text-center md:mt-0">
|
||||
<div className="space-y-6">
|
||||
{tagline && (
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-surface-card drop-shadow-md md:text-sm">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-[#EAEFEA] drop-shadow-md md:text-sm">
|
||||
{tagline}
|
||||
</p>
|
||||
)}
|
||||
<h1 className="ui-hero-title">
|
||||
{renderAccentTitle(title || '')}
|
||||
{renderAccentTitle(title || '', 'text-[#D4AF37] font-medium italic')}
|
||||
</h1>
|
||||
{description && (
|
||||
<p className="ui-hero-copy">
|
||||
@@ -146,7 +147,7 @@ function HeroSection({ payload, lang }: { payload: Record<string, unknown>, lang
|
||||
</Link>
|
||||
<a
|
||||
href="#categories"
|
||||
className="ui-hero-link"
|
||||
className="ui-hero-link group"
|
||||
>
|
||||
{heroLearnMoreText(resolvedLang)}
|
||||
<span className="transition-transform group-hover:translate-x-1">→</span>
|
||||
@@ -208,11 +209,11 @@ function CategoriesSection({
|
||||
unoptimized
|
||||
className="absolute inset-0 h-full w-full object-cover transition duration-700 group-hover:scale-105"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-text-main/65 to-transparent" />
|
||||
<div className="absolute bottom-5 left-5 right-5 text-surface-card">
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent z-10 opacity-80 group-hover:opacity-100 transition-opacity" />
|
||||
<div className="absolute bottom-5 left-5 right-5 z-20 text-white">
|
||||
<span className="block text-lg font-semibold">{name}</span>
|
||||
{category._count && (
|
||||
<span className="text-sm text-surface-card">
|
||||
<span className="text-sm text-white/90">
|
||||
{category._count.products} {productsLabel[resolvedLang] ?? productsLabel.en}
|
||||
</span>
|
||||
)}
|
||||
@@ -665,6 +666,207 @@ function ContactInfoSection({ payload, lang }: { payload: Record<string, unknown
|
||||
)
|
||||
}
|
||||
|
||||
function ContactFormSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const [formData, setFormData] = useState({ name: '', email: '', message: '' })
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [isSuccess, setIsSuccess] = useState(false)
|
||||
|
||||
const title = localizedFromRecord(payload, 'title', resolvedLang)
|
||||
const subtitle = localizedFromRecord(payload, 'subtitle', resolvedLang)
|
||||
|
||||
const labels: Record<string, Record<string, string>> = {
|
||||
th: {
|
||||
title: 'ติดต่อเรา',
|
||||
subtitle: 'ส่งข้อความถึงทีมงานของเรา เรายินดีให้บริการและพร้อมตอบทุกข้อสงสัย',
|
||||
successTitle: 'ส่งข้อความสำเร็จ',
|
||||
successBody: 'ขอบคุณที่ติดต่อเรา ทีมงานจะรีบตอบกลับโดยเร็วที่สุด',
|
||||
name: 'ชื่อ-นามสกุล',
|
||||
namePlaceholder: 'กรอกชื่อของคุณ',
|
||||
email: 'อีเมล',
|
||||
message: 'ข้อความ',
|
||||
messagePlaceholder: 'พิมพ์ข้อความของคุณที่นี่...',
|
||||
submit: 'ส่งข้อความ',
|
||||
},
|
||||
en: {
|
||||
title: 'Contact Us',
|
||||
subtitle: 'Send a message to our team and we will get back to you soon.',
|
||||
successTitle: 'Message sent successfully',
|
||||
successBody: 'Thanks for reaching out. Our team will reply as soon as possible.',
|
||||
name: 'Full Name',
|
||||
namePlaceholder: 'Enter your name',
|
||||
email: 'Email',
|
||||
message: 'Message',
|
||||
messagePlaceholder: 'Type your message here...',
|
||||
submit: 'Send Message',
|
||||
},
|
||||
ja: {
|
||||
title: 'お問い合わせ',
|
||||
subtitle: 'チームへメッセージをお送りください。できるだけ早くご返信します。',
|
||||
successTitle: '送信に成功しました',
|
||||
successBody: 'お問い合わせありがとうございます。担当者ができるだけ早く返信します。',
|
||||
name: '氏名',
|
||||
namePlaceholder: 'お名前を入力してください',
|
||||
email: 'メール',
|
||||
message: 'メッセージ',
|
||||
messagePlaceholder: 'ここにメッセージを入力してください...',
|
||||
submit: '送信',
|
||||
},
|
||||
zh: {
|
||||
title: '联系我们',
|
||||
subtitle: '给我们的团队留言,我们会尽快回复您。',
|
||||
successTitle: '发送成功',
|
||||
successBody: '感谢您的联系,我们会尽快回复。',
|
||||
name: '姓名',
|
||||
namePlaceholder: '请输入您的姓名',
|
||||
email: '邮箱',
|
||||
message: '留言',
|
||||
messagePlaceholder: '在这里输入您的留言...',
|
||||
submit: '发送消息',
|
||||
},
|
||||
vi: {
|
||||
title: 'Liên hệ',
|
||||
subtitle: 'Gửi tin nhắn cho đội ngũ của chúng tôi, chúng tôi sẽ phản hồi sớm nhất.',
|
||||
successTitle: 'Gửi tin nhắn thành công',
|
||||
successBody: 'Cảm ơn bạn đã liên hệ, đội ngũ của chúng tôi sẽ phản hồi sớm nhất có thể.',
|
||||
name: 'Họ và tên',
|
||||
namePlaceholder: 'Nhập tên của bạn',
|
||||
email: 'Email',
|
||||
message: 'Tin nhắn',
|
||||
messagePlaceholder: 'Nhập tin nhắn của bạn tại đây...',
|
||||
submit: 'Gửi tin nhắn',
|
||||
},
|
||||
ko: {
|
||||
title: '문의하기',
|
||||
subtitle: '팀에 메시지를 보내주시면 최대한 빨리 답변드리겠습니다.',
|
||||
successTitle: '메시지 전송 완료',
|
||||
successBody: '문의해 주셔서 감사합니다. 가능한 빨리 답변드리겠습니다.',
|
||||
name: '이름',
|
||||
namePlaceholder: '이름을 입력하세요',
|
||||
email: '이메일',
|
||||
message: '메시지',
|
||||
messagePlaceholder: '여기에 메시지를 입력하세요...',
|
||||
submit: '메시지 보내기',
|
||||
}
|
||||
}
|
||||
|
||||
const copy = labels[resolvedLang] ?? labels.en
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
setIsSubmitting(false)
|
||||
setIsSuccess(true)
|
||||
setFormData({ name: '', email: '', message: '' })
|
||||
|
||||
// Reset success message after 3 seconds
|
||||
setTimeout(() => setIsSuccess(false), 3000)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target
|
||||
setFormData(prev => ({ ...prev, [name]: value }))
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="ui-section-band bg-surface-page px-4 py-20 md:px-8">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<div className="mb-12 text-center">
|
||||
<h2 className="mb-4 text-3xl font-semibold text-text-main md:text-4xl">
|
||||
{title || copy.title}
|
||||
</h2>
|
||||
<p className="mx-auto max-w-xl text-lg text-muted-text">
|
||||
{subtitle || copy.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-3xl border border-line-primary/20 bg-surface-card p-8 shadow-card md:p-12">
|
||||
{isSuccess ? (
|
||||
<div className="py-12 text-center">
|
||||
<div className="mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-brand-success/20 text-brand-success">
|
||||
<Send size={32} />
|
||||
</div>
|
||||
<h3 className="mb-2 text-2xl font-semibold text-text-main">{copy.successTitle}</h3>
|
||||
<p className="text-muted-text">{copy.successBody}</p>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="name" className="block text-sm font-medium text-text-main">
|
||||
{copy.name}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
className="w-full rounded-xl border border-line-primary/30 bg-surface-page px-4 py-3 text-text-main outline-none transition-all focus:border-brand-primary focus:ring-2 focus:ring-brand-primary/20"
|
||||
placeholder={copy.namePlaceholder}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-text-main">
|
||||
{copy.email}
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className="w-full rounded-xl border border-line-primary/30 bg-surface-page px-4 py-3 text-text-main outline-none transition-all focus:border-brand-primary focus:ring-2 focus:ring-brand-primary/20"
|
||||
placeholder="example@email.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="message" className="block text-sm font-medium text-text-main">
|
||||
{copy.message}
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
required
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
rows={5}
|
||||
className="w-full resize-none rounded-xl border border-line-primary/30 bg-surface-page px-4 py-3 text-text-main outline-none transition-all focus:border-brand-primary focus:ring-2 focus:ring-brand-primary/20"
|
||||
placeholder={copy.messagePlaceholder}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-brand-primary px-6 py-4 font-semibold text-white transition-colors hover:bg-brand-primary-hover disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<span className="h-6 w-6 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
||||
) : (
|
||||
<>
|
||||
<Send size={18} />
|
||||
{copy.submit}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default function DynamicPageRenderer({ sections, categories, lang: langProp }: DynamicPageRendererProps) {
|
||||
const i18nLang = useLang()
|
||||
// Use i18n.language reactively — switches immediately when user changes language
|
||||
@@ -709,6 +911,8 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
|
||||
return <AccordionSection key={section.id} payload={payload} lang={lang} />
|
||||
case 'CONTACT_INFO':
|
||||
return <ContactInfoSection key={section.id} payload={payload} lang={lang} />
|
||||
case 'CONTACT_FORM':
|
||||
return <ContactFormSection key={section.id} payload={payload} lang={lang} />
|
||||
default:
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.warn(`[DynamicPageRenderer] Unsupported section type: ${section.type}`)
|
||||
|
||||
Reference in new issue
Block a user