update lang
This commit is contained in:
1 parent
84a9b5c603
commit
f0f714e8e2
8 files changed
+357
-25
No files matched your search
@@ -12,6 +12,9 @@ import DynamicHeroArticle from './DynamicHeroArticle';
|
||||
import DynamicScentNotesBreakdown from './DynamicScentNotesBreakdown';
|
||||
import DynamicQuoteCallout from './DynamicQuoteCallout';
|
||||
import DynamicProductCarousel from './DynamicProductCarousel';
|
||||
import AccordionSection from './sections/AccordionSection';
|
||||
import ContactInfoSection from './sections/ContactInfoSection';
|
||||
import ContactFormSection from './sections/ContactFormSection';
|
||||
|
||||
interface DynamicPageRendererProps {
|
||||
sections: PageSection[];
|
||||
@@ -63,6 +66,12 @@ export default function DynamicPageRenderer({
|
||||
return <DynamicQuoteCallout key={section.id} payload={section.payload} />;
|
||||
case 'PRODUCT_CAROUSEL':
|
||||
return <DynamicProductCarousel key={section.id} payload={section.payload} />;
|
||||
case 'ACCORDION':
|
||||
return <AccordionSection key={section.id} payload={section.payload as any} />;
|
||||
case 'CONTACT_INFO':
|
||||
return <ContactInfoSection key={section.id} payload={section.payload as any} />;
|
||||
case 'CONTACT_FORM':
|
||||
return <ContactFormSection key={section.id} payload={section.payload as any} />;
|
||||
default:
|
||||
console.warn(`[DynamicPageRenderer] Unknown block type: ${section.type}`);
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { AccordionSectionPayload } from '@homaroma/types';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
|
||||
interface AccordionSectionProps {
|
||||
payload: AccordionSectionPayload;
|
||||
}
|
||||
|
||||
export default function AccordionSection({ payload }: AccordionSectionProps) {
|
||||
const [openIndex, setOpenIndex] = useState<string | null>(null);
|
||||
|
||||
const toggleAccordion = (index: string) => {
|
||||
setOpenIndex(openIndex === index ? null : index);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="w-full py-16 px-4 md:px-8 bg-spa-sand/20">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{payload.categories.map((category, catIndex) => (
|
||||
<div key={catIndex} className="mb-12 last:mb-0">
|
||||
<h3 className="text-2xl font-serif text-spa-charcoal mb-6 flex items-center gap-3">
|
||||
{category.icon && <span className="text-spa-sage">{category.icon}</span>}
|
||||
{category.name}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-4">
|
||||
{category.questions.map((item, qIndex) => {
|
||||
const indexKey = `${catIndex}-${qIndex}`;
|
||||
const isOpen = openIndex === indexKey;
|
||||
return (
|
||||
<div
|
||||
key={qIndex}
|
||||
className="border border-spa-sage/30 rounded-xl bg-white overflow-hidden transition-all duration-300"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex items-center justify-between p-5 text-left focus:outline-none focus:ring-2 focus:ring-spa-sage/50"
|
||||
onClick={() => toggleAccordion(indexKey)}
|
||||
>
|
||||
<span className="text-lg font-medium text-spa-charcoal pr-4">
|
||||
{item.q}
|
||||
</span>
|
||||
<span className="text-spa-sage flex-shrink-0">
|
||||
{isOpen ? <ChevronUp size={20} /> : <ChevronDown size={20} />}
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
className={`px-5 overflow-hidden transition-all duration-300 ${
|
||||
isOpen ? 'max-h-96 pb-5 opacity-100' : 'max-h-0 opacity-0'
|
||||
}`}
|
||||
>
|
||||
<p className="text-spa-charcoal/80 leading-relaxed whitespace-pre-wrap">
|
||||
{item.a}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { ContactFormSectionPayload } from '@homaroma/types';
|
||||
import { Send } from 'lucide-react';
|
||||
|
||||
interface ContactFormSectionProps {
|
||||
payload: ContactFormSectionPayload;
|
||||
}
|
||||
|
||||
export default function ContactFormSection({ payload }: ContactFormSectionProps) {
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
message: ''
|
||||
});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isSuccess, setIsSuccess] = useState(false);
|
||||
|
||||
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="w-full py-20 px-4 md:px-8 bg-spa-sand/30">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-serif text-spa-charcoal mb-4">
|
||||
{payload.title || "ติดต่อเรา"}
|
||||
</h2>
|
||||
<p className="text-lg text-spa-charcoal/70 max-w-xl mx-auto">
|
||||
{payload.subtitle || "ส่งข้อความถึงทีมงานของเรา เรายินดีให้บริการและพร้อมตอบทุกข้อสงสัย"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-3xl shadow-xl shadow-spa-sand/50 p-8 md:p-12 border border-spa-sage/10">
|
||||
{isSuccess ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="w-16 h-16 bg-green-100 text-green-600 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<Send size={32} />
|
||||
</div>
|
||||
<h3 className="text-2xl font-serif text-spa-charcoal mb-2">ส่งข้อความสำเร็จ</h3>
|
||||
<p className="text-spa-charcoal/70">ขอบคุณที่ติดต่อเรา ทีมงานจะรีบตอบกลับโดยเร็วที่สุด</p>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="name" className="block text-sm font-medium text-spa-charcoal/80">
|
||||
ชื่อ-นามสกุล
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-3 rounded-xl border border-spa-sage/30 focus:outline-none focus:ring-2 focus:ring-spa-sage/50 bg-spa-sand/5 transition-all"
|
||||
placeholder="กรอกชื่อของคุณ"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-spa-charcoal/80">
|
||||
อีเมล
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-3 rounded-xl border border-spa-sage/30 focus:outline-none focus:ring-2 focus:ring-spa-sage/50 bg-spa-sand/5 transition-all"
|
||||
placeholder="example@email.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="message" className="block text-sm font-medium text-spa-charcoal/80">
|
||||
ข้อความ
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
required
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
rows={5}
|
||||
className="w-full px-4 py-3 rounded-xl border border-spa-sage/30 focus:outline-none focus:ring-2 focus:ring-spa-sage/50 bg-spa-sand/5 transition-all resize-none"
|
||||
placeholder="พิมพ์ข้อความของคุณที่นี่..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="w-full py-4 px-6 bg-spa-sage hover:bg-spa-sage-dark text-white rounded-xl font-medium flex items-center justify-center gap-2 transition-colors disabled:opacity-70 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<span className="w-6 h-6 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
<Send size={18} />
|
||||
ส่งข้อความ
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import type { ContactInfoSectionPayload } from '@homaroma/types';
|
||||
import { MapPin, Mail, MessageCircle, Clock } from 'lucide-react';
|
||||
|
||||
interface ContactInfoSectionProps {
|
||||
payload: ContactInfoSectionPayload;
|
||||
}
|
||||
|
||||
export default function ContactInfoSection({ payload }: ContactInfoSectionProps) {
|
||||
return (
|
||||
<section className="w-full py-16 px-4 md:px-8 bg-white">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
|
||||
<div className="flex flex-col items-center text-center p-6 bg-spa-sand/10 rounded-2xl border border-spa-sage/20 hover:border-spa-sage transition-colors duration-300">
|
||||
<div className="w-12 h-12 bg-spa-sage/10 rounded-full flex items-center justify-center text-spa-sage mb-4">
|
||||
<MapPin size={24} />
|
||||
</div>
|
||||
<h4 className="text-lg font-serif text-spa-charcoal mb-2">ที่อยู่</h4>
|
||||
<p className="text-spa-charcoal/70 leading-relaxed whitespace-pre-wrap">
|
||||
{payload.address}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center text-center p-6 bg-spa-sand/10 rounded-2xl border border-spa-sage/20 hover:border-spa-sage transition-colors duration-300">
|
||||
<div className="w-12 h-12 bg-spa-sage/10 rounded-full flex items-center justify-center text-spa-sage mb-4">
|
||||
<Mail size={24} />
|
||||
</div>
|
||||
<h4 className="text-lg font-serif text-spa-charcoal mb-2">อีเมล</h4>
|
||||
<a href={`mailto:${payload.email}`} className="text-spa-charcoal/70 hover:text-spa-sage transition-colors">
|
||||
{payload.email}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center text-center p-6 bg-spa-sand/10 rounded-2xl border border-spa-sage/20 hover:border-spa-sage transition-colors duration-300">
|
||||
<div className="w-12 h-12 bg-spa-sage/10 rounded-full flex items-center justify-center text-spa-sage mb-4">
|
||||
<MessageCircle size={24} />
|
||||
</div>
|
||||
<h4 className="text-lg font-serif text-spa-charcoal mb-2">Line</h4>
|
||||
<p className="text-spa-charcoal/70">
|
||||
{payload.line}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center text-center p-6 bg-spa-sand/10 rounded-2xl border border-spa-sage/20 hover:border-spa-sage transition-colors duration-300">
|
||||
<div className="w-12 h-12 bg-spa-sage/10 rounded-full flex items-center justify-center text-spa-sage mb-4">
|
||||
<Clock size={24} />
|
||||
</div>
|
||||
<h4 className="text-lg font-serif text-spa-charcoal mb-2">เวลาทำการ</h4>
|
||||
<p className="text-spa-charcoal/70 whitespace-pre-wrap">
|
||||
{payload.hours}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user