重构Contact1组件,增加Contact菜单,邮件发送测试成功
This commit is contained in:
parent
e55b8378f1
commit
bb0b7f87f4
@ -16,15 +16,18 @@ export async function POST(request) {
|
||||
);
|
||||
const message = response.data.message;
|
||||
if (message?.success) {
|
||||
return Response.json({ success: true, message: message.message || 'Email sent successfully' });
|
||||
const successMessage = typeof message.message === 'string' ? message.message : 'Email sent successfully';
|
||||
return Response.json({ success: true, message: successMessage });
|
||||
} else if (message?.error) {
|
||||
return Response.json({ error: message.error }, { status: 400 });
|
||||
const errorMessage = typeof message.error === 'string' ? message.error : 'Failed to send email';
|
||||
return Response.json({ error: errorMessage }, { status: 400 });
|
||||
} else {
|
||||
return Response.json({ error: 'Unknown error' }, { status: 500 });
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = typeof error.message === 'string' ? error.message : 'Internal server error';
|
||||
return Response.json(
|
||||
{ error: error.message, detail: error?.response?.data || null },
|
||||
{ error: errorMessage, detail: error?.response?.data || null },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,32 +1,61 @@
|
||||
import Contact1 from "@/components/contact/Contact1";
|
||||
import React from "react";
|
||||
import Banner from "@/components/banner/Banner";
|
||||
import { getPageData, getSiteSettings, fetchComponentData } from "@/utils/data";
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export const revalidate = 3600;
|
||||
|
||||
export async function generateMetadata() {
|
||||
const slugArr = ["contact"];
|
||||
const res = await fetch(`${process.env.PUBLIC_SITE_URL}/api/get-page-data`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ slug_list: slugArr })
|
||||
const { data, error, page_info } = await getPageData({
|
||||
slug_list: slugArr,
|
||||
downloadFiles: false // Do not download files for metadata
|
||||
});
|
||||
const { data, error } = await res.json();
|
||||
const siteSettings = await getSiteSettings();
|
||||
const siteName = siteSettings.site_name || '';
|
||||
const siteNameInPageTitles = siteSettings.site_name_in_page_titles || 'None';
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
title: error.title || '',
|
||||
title: error.title || 'Contact Error',
|
||||
description: error.message || '',
|
||||
};
|
||||
}
|
||||
|
||||
let title = '';
|
||||
if (Array.isArray(data) && page_info) {
|
||||
title = page_info.meta_title || page_info.title || '';
|
||||
} else {
|
||||
title = data?.meta_title || data?.title || '';
|
||||
}
|
||||
|
||||
if (siteName && title) {
|
||||
if (siteNameInPageTitles === 'After') {
|
||||
title = `${title} - ${siteName}`;
|
||||
} else if (siteNameInPageTitles === 'Before') {
|
||||
title = `${siteName} - ${title}`;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: data?.meta_title || data?.title || '',
|
||||
title,
|
||||
description: data?.meta_description || data?.description || '',
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ContactPage() {
|
||||
const res = await fetch(`${process.env.PUBLIC_SITE_URL}/api/get-component-data?component_name=Contact`, { cache: 'no-store' });
|
||||
const { data } = await res.json();
|
||||
export default async function Page() {
|
||||
const slugArr = ["contact"];
|
||||
|
||||
// 获取页面数据
|
||||
const { data, error } = await getPageData({
|
||||
slug_list: slugArr,
|
||||
downloadFiles: true // Download files for page rendering
|
||||
});
|
||||
|
||||
if (error) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -35,7 +64,7 @@ export default async function ContactPage() {
|
||||
componentName="Banner-contact"
|
||||
className="contact-banner"
|
||||
/>
|
||||
<Contact1 ssrData={data} />
|
||||
<Contact1 />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,338 +0,0 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
export default function Contact1({ ssrData }) {
|
||||
const [data, setData] = useState(ssrData || null);
|
||||
const [loading, setLoading] = useState(!ssrData);
|
||||
const [error, setError] = useState(null);
|
||||
const [modal, setModal] = useState({ open: false, message: '', type: 'success' });
|
||||
|
||||
useEffect(() => {
|
||||
if (!ssrData) {
|
||||
setLoading(true);
|
||||
axios.get("/api/get-component-data", { params: { component_name: "Contact" } })
|
||||
.then(res => setData(res.data.data))
|
||||
.catch(() => setError("Failed to get Contact data"))
|
||||
.finally(() => setLoading(false));
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [ssrData]);
|
||||
|
||||
if (loading) return <div className="text-center py-20">Loading...</div>;
|
||||
if (error) return null;
|
||||
if (!data) return null;
|
||||
|
||||
|
||||
const contacts = Array.isArray(data.items) ? data.items : [];
|
||||
|
||||
const [form, setForm] = useState({
|
||||
name: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
department: '',
|
||||
message: ''
|
||||
});
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [submitResult, setSubmitResult] = useState(null);
|
||||
|
||||
function handleInputChange(e) {
|
||||
const { name, value } = e.target;
|
||||
setForm(prev => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
setSubmitting(true);
|
||||
setSubmitResult(null);
|
||||
try {
|
||||
const content = [
|
||||
`Name: ${form.name}`,
|
||||
`Phone: ${form.phone}`,
|
||||
`Email: ${form.email}`,
|
||||
`Department: ${form.department}`,
|
||||
`Message:<br>${form.message.replace(/\n/g, '<br>')}`
|
||||
].join("<br>");
|
||||
const res = await axios.post('/api/send-email', {
|
||||
subject: `Website Contact Form: ${form.name}`,
|
||||
content
|
||||
});
|
||||
if (res.data.success) {
|
||||
setModal({ open: true, message: res.data.message || 'Email sent successfully!', type: 'success' });
|
||||
setSubmitResult(null);
|
||||
setForm({ name: '', phone: '', email: '', department: '', message: '' });
|
||||
} else {
|
||||
setModal({ open: true, message: res.data.error || 'Failed to send', type: 'error' });
|
||||
setSubmitResult(null);
|
||||
}
|
||||
} catch (err) {
|
||||
setModal({ open: true, message: err?.response?.data?.error || err.message || 'Failed to send', type: 'error' });
|
||||
setSubmitResult(null);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
setModal({ open: false, message: '', type: 'success' });
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="wrapper !bg-[#ffffff] relative border-0 upper-end before:top-[-4rem] before:border-l-transparent before:border-r-[100vw] before:border-t-[4rem] before:border-[#fefefe] before:content-[''] before:block before:absolute before:z-0 before:border-y-transparent before:border-0 before:border-solid before:right-0">
|
||||
<div className="container pb-12">
|
||||
<div className="flex flex-wrap mx-[-15px] !mb-[4.5rem] md:!mb-24">
|
||||
<div className="xl:w-10/12 w-full flex-[0_0_auto] !px-[15px] max-w-full !mx-auto !mt-[-9rem]">
|
||||
<div className="card">
|
||||
<div className="flex flex-wrap mx-0">
|
||||
<div className="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] max-w-full !self-stretch">
|
||||
<div className="map map-full rounded-t-[0.4rem] rounded-lg-start h-full min-h-[15rem]">
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m14!1m8!1m3!1d3308.116930443662!2d-117.6923042!3d33.9895302!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x80dcccd7e575dffd%3A0x30aebc3ca15ed41d!2s14508%20Central%20Ave%2C%20Chino%2C%20CA%2091710%2C%20USA!5e0!3m2!1sen!2sjp!4v1712719609523!5m2!1sen!2sjp"
|
||||
style={{ width: "100%", height: "100%", border: 0 }}
|
||||
allowFullScreen=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] xl:!px-[35px] lg:!px-[20px] !px-[15px] !my-[50px] max-w-full">
|
||||
<div className="flex flex-col gap-5">
|
||||
{contacts.map((item, idx) => (
|
||||
<div className="flex flex-row items-start gap-3" key={idx}>
|
||||
{(item.item_html_code || item.item_icon) && (
|
||||
<div className="w-8 h-8 flex items-center justify-center text-xl">
|
||||
{item.item_html_code ? (
|
||||
<span style={{ display: 'inline-block', width: '2rem', height: '2rem' }}
|
||||
dangerouslySetInnerHTML={{ __html: item.item_html_code }} />
|
||||
) : (
|
||||
<img src={item.item_icon} alt="icon" style={{ width: '2rem', height: '2rem', objectFit: 'contain' }} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-[90px]">
|
||||
{item.item_title}
|
||||
</div>
|
||||
<div className="break-all">
|
||||
{/(mail)/i.test(item.item_title) ? (
|
||||
<a href={`mailto:${item.item_subtitle || item.item_value || ''}`} className="!text-inherit hover:!text-[#1fc76f]">
|
||||
{item.item_subtitle || item.item_value || ''}
|
||||
</a>
|
||||
) : /(phone)/i.test(item.item_title) ? (
|
||||
<a href={`tel:${item.item_subtitle || item.item_value || ''}`} className="hover:underline">
|
||||
{item.item_subtitle || item.item_value || ''}
|
||||
</a>
|
||||
) : (
|
||||
<span>{item.item_subtitle || item.item_value || ''}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/*/column */}
|
||||
</div>
|
||||
{/*/.row */}
|
||||
</div>
|
||||
{/* /.card */}
|
||||
</div>
|
||||
{/* /column */}
|
||||
</div>
|
||||
{/* /.row */}
|
||||
<div className="flex flex-wrap mx-[-15px]">
|
||||
<div className="xl:w-8/12 xl:!ml-[16.66666667%] lg:w-10/12 lg:!ml-[8.33333333%] w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<h2 className="!text-[calc(1.305rem_+_0.66vw)] font-bold xl:!text-[1.8rem] !leading-[1.3] !mb-3 !text-center">
|
||||
{data.title}
|
||||
</h2>
|
||||
<p className="lead !leading-[1.65] text-[0.9rem] font-medium !text-center !mb-10">
|
||||
{data.description}
|
||||
</p>
|
||||
<form
|
||||
className="contact-form needs-validation"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="messages" />
|
||||
{/* Modal for feedback */}
|
||||
{modal.open && (
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
background: 'rgba(0,0,0,0.3)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 9999
|
||||
}}>
|
||||
<div style={{
|
||||
background: '#fff',
|
||||
borderRadius: '16px',
|
||||
padding: '2.5rem 3.5rem',
|
||||
minWidth: '390px',
|
||||
maxWidth: '96vw',
|
||||
boxShadow: '0 8px 32px rgba(0,0,0,0.18)',
|
||||
textAlign: 'center',
|
||||
color: modal.type === 'success' ? '#1fc76f' : '#e53e3e',
|
||||
fontWeight: 500
|
||||
}}>
|
||||
<div style={{ fontSize: '1.1rem', marginBottom: '1.5rem' }}>{modal.message}</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
style={{
|
||||
background: modal.type === 'success' ? '#1fc76f' : '#e53e3e',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: '24px',
|
||||
padding: '0.5rem 2rem',
|
||||
fontSize: '1rem',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.08)'
|
||||
}}
|
||||
>OK</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap mx-[-10px]">
|
||||
<div className="xl:w-6/12 lg:w-6/12 md:w-6/12 w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<div className="form-floating relative !mb-4">
|
||||
<input
|
||||
id="form_name"
|
||||
type="text"
|
||||
name="name"
|
||||
className="form-control relative block w-full text-[.75rem] font-medium !text-[#60697b] bg-[#fefefe] bg-clip-padding border shadow-[0_0_1.25rem_rgba(30,34,40,0.04)] rounded-[0.4rem] border-solid border-[rgba(8,60,130,0.07)] transition-[border-color] duration-[0.15s] ease-in-out focus:shadow-[0_0_1.25rem_rgba(30,34,40,0.04),unset] focus-visible:!border-[rgba(63,120,224,0.5)] placeholder:!text-[#959ca9] placeholder:opacity-100 m-0 !pr-9 p-[.6rem_1rem] h-[calc(2.5rem_+_2px)] min-h-[calc(2.5rem_+_2px)] !leading-[1.25]"
|
||||
placeholder=""
|
||||
required
|
||||
value={form.name}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<label
|
||||
htmlFor="form_name"
|
||||
className="!text-[#959ca9] !mb-2 !inline-block text-[.75rem] !absolute !z-[2] h-full overflow-hidden text-start text-ellipsis whitespace-nowrap pointer-events-none border origin-[0_0] px-4 py-[0.6rem] border-solid border-transparent left-0 top-0 font-Manrope"
|
||||
>
|
||||
Name *
|
||||
</label>
|
||||
<div className="valid-feedback">Looks good!</div>
|
||||
<div className="invalid-feedback">
|
||||
Please enter your first name.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /column */}
|
||||
<div className="xl:w-6/12 lg:w-6/12 md:w-6/12 w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<div className="form-floating relative !mb-4">
|
||||
<input
|
||||
id="form_phone"
|
||||
type="text"
|
||||
name="phone"
|
||||
className="form-control relative block w-full text-[.75rem] font-medium !text-[#60697b] bg-[#fefefe] bg-clip-padding border shadow-[0_0_1.25rem_rgba(30,34,40,0.04)] rounded-[0.4rem] border-solid border-[rgba(8,60,130,0.07)] transition-[border-color] duration-[0.15s] ease-in-out focus:shadow-[0_0_1.25rem_rgba(30,34,40,0.04),unset] focus-visible:!border-[rgba(63,120,224,0.5)] placeholder:!text-[#959ca9] placeholder:opacity-100 m-0 !pr-9 p-[.6rem_1rem] h-[calc(2.5rem_+_2px)] min-h-[calc(2.5rem_+_2px)] !leading-[1.25]"
|
||||
placeholder=""
|
||||
value={form.phone}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<label
|
||||
htmlFor="form_phone"
|
||||
className="!text-[#959ca9] !mb-2 !inline-block text-[.75rem] !absolute !z-[2] h-full overflow-hidden text-start text-ellipsis whitespace-nowrap pointer-events-none border origin-[0_0] px-4 py-[0.6rem] border-solid border-transparent left-0 top-0 font-Manrope"
|
||||
>
|
||||
Phone
|
||||
</label>
|
||||
<div className="valid-feedback">Looks good!</div>
|
||||
<div className="invalid-feedback">
|
||||
Please enter your phone number.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /column */}
|
||||
<div className="xl:w-6/12 lg:w-6/12 md:w-6/12 w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<div className="form-floating relative !mb-4">
|
||||
<input
|
||||
id="form_email"
|
||||
type="email"
|
||||
name="email"
|
||||
className="form-control relative block w-full text-[.75rem] font-medium !text-[#60697b] bg-[#fefefe] bg-clip-padding border shadow-[0_0_1.25rem_rgba(30,34,40,0.04)] rounded-[0.4rem] border-solid border-[rgba(8,60,130,0.07)] transition-[border-color] duration-[0.15s] ease-in-out focus:shadow-[0_0_1.25rem_rgba(30,34,40,0.04),unset] focus-visible:!border-[rgba(63,120,224,0.5)] placeholder:!text-[#959ca9] placeholder:opacity-100 m-0 !pr-9 p-[.6rem_1rem] h-[calc(2.5rem_+_2px)] min-h-[calc(2.5rem_+_2px)] !leading-[1.25]"
|
||||
placeholder=""
|
||||
required
|
||||
value={form.email}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<label
|
||||
htmlFor="form_email"
|
||||
className="!text-[#959ca9] !mb-2 !inline-block text-[.75rem] !absolute !z-[2] h-full overflow-hidden text-start text-ellipsis whitespace-nowrap pointer-events-none border origin-[0_0] px-4 py-[0.6rem] border-solid border-transparent left-0 top-0 font-Manrope"
|
||||
>
|
||||
Email *
|
||||
</label>
|
||||
<div className="valid-feedback">Looks good!</div>
|
||||
<div className="invalid-feedback">
|
||||
Please provide a valid email address.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /column */}
|
||||
<div className="xl:w-6/12 lg:w-6/12 md:w-6/12 w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<div className="form-select-wrapper !mb-4">
|
||||
<select
|
||||
className="form-select"
|
||||
id="form-select"
|
||||
name="department"
|
||||
value={form.department}
|
||||
onChange={handleInputChange}
|
||||
>
|
||||
<option value="">Select a department</option>
|
||||
<option value="Sales">Sales</option>
|
||||
<option value="Marketing">Marketing</option>
|
||||
<option value="Customer Support">Customer Support</option>
|
||||
</select>
|
||||
<div className="valid-feedback">Looks good!</div>
|
||||
<div className="invalid-feedback">
|
||||
Please select a department.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /column */}
|
||||
<div className="w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<div className="form-floating relative !mb-4">
|
||||
<textarea
|
||||
id="form_message"
|
||||
name="message"
|
||||
className="form-control relative block w-full text-[.75rem] font-medium !text-[#60697b] bg-[#fefefe] bg-clip-padding border shadow-[0_0_1.25rem_rgba(30,34,40,0.04)] rounded-[0.4rem] border-solid border-[rgba(8,60,130,0.07)] transition-[border-color] duration-[0.15s] ease-in-out focus:shadow-[0_0_1.25rem_rgba(30,34,40,0.04),unset] focus-visible:!border-[rgba(63,120,224,0.5)] placeholder:!text-[#959ca9] placeholder:opacity-100 m-0 !pr-9 p-[.6rem_1rem] h-[calc(2.5rem_+_2px)] min-h-[calc(2.5rem_+_2px)] !leading-[1.25]"
|
||||
placeholder=""
|
||||
style={{ height: 150 }}
|
||||
required
|
||||
value={form.message}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<label
|
||||
htmlFor="form_message"
|
||||
className="!text-[#959ca9] !mb-2 !inline-block text-[.75rem] !absolute !z-[2] h-full overflow-hidden text-start text-ellipsis whitespace-nowrap pointer-events-none border origin-[0_0] px-4 py-[0.6rem] border-solid border-transparent left-0 top-0 font-Manrope"
|
||||
>
|
||||
Message *
|
||||
</label>
|
||||
<div className="valid-feedback">Looks good!</div>
|
||||
<div className="invalid-feedback">
|
||||
Please enter your messsage.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* /column */}
|
||||
<div className="w-full flex-[0_0_auto] !px-[15px] max-w-full !text-center">
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary !text-white !bg-[#1fc76f] border-[#1fc76f] hover:text-white hover:bg-[#1fc76f] hover:!border-[#1fc76f] active:text-white active:bg-[#1fc76f] active:border-[#1fc76f] disabled:text-white disabled:bg-[#1fc76f] disabled:border-[#1fc76f] !rounded-[50rem] btn-send !mb-3 hover:translate-y-[-0.15rem] hover:shadow-[0_0.25rem_0.75rem_rgba(30,34,40,0.15)]"
|
||||
value={submitting ? 'Sending...' : (data.button_text || 'Submit')}
|
||||
disabled={submitting}
|
||||
/>
|
||||
</div>
|
||||
{/* /column */}
|
||||
</div>
|
||||
{/* /.row */}
|
||||
</form>
|
||||
{/* /form */}
|
||||
</div>
|
||||
{/* /column */}
|
||||
</div>
|
||||
{/* /.row */}
|
||||
</div>
|
||||
{/* /.container */}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
201
components/contact/Contact1/UI.jsx
Normal file
201
components/contact/Contact1/UI.jsx
Normal file
@ -0,0 +1,201 @@
|
||||
"use client";
|
||||
import React, { useState } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
export default function UI({ data }) {
|
||||
if (!data) return null;
|
||||
|
||||
const contacts = Array.isArray(data.items) ? data.items : [];
|
||||
|
||||
const [form, setForm] = useState({
|
||||
name: '', phone: '', email: '', department: '', message: ''
|
||||
});
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [modal, setModal] = useState({ open: false, message: '', type: 'success' });
|
||||
|
||||
function handleInputChange(e) {
|
||||
const { name, value } = e.target;
|
||||
setForm(prev => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const content = [
|
||||
`Name: ${form.name}`,
|
||||
`Phone: ${form.phone}`,
|
||||
`Email: ${form.email}`,
|
||||
`Department: ${form.department}`,
|
||||
`Message:<br>${form.message.replace(/\n/g, '<br>')}`
|
||||
].join("<br>");
|
||||
const res = await axios.post('/api/send-email', {
|
||||
subject: `Website Contact Form: ${form.name}`,
|
||||
content
|
||||
});
|
||||
if (res.data.success) {
|
||||
const message = typeof res.data.message === 'string' ? res.data.message : 'Email sent successfully!';
|
||||
setModal({ open: true, message, type: 'success' });
|
||||
setForm({ name: '', phone: '', email: '', department: '', message: '' });
|
||||
} else {
|
||||
const errorMessage = typeof res.data.error === 'string' ? res.data.error : 'Failed to send email';
|
||||
setModal({ open: true, message: errorMessage, type: 'error' });
|
||||
}
|
||||
} catch (err) {
|
||||
let errorMessage = 'Failed to send email';
|
||||
if (err?.response?.data?.error) {
|
||||
errorMessage = typeof err.response.data.error === 'string' ? err.response.data.error : 'Failed to send email';
|
||||
} else if (err?.message) {
|
||||
errorMessage = typeof err.message === 'string' ? err.message : 'Failed to send email';
|
||||
}
|
||||
setModal({ open: true, message: errorMessage, type: 'error' });
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
setModal({ open: false, message: '', type: 'success' });
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="wrapper !bg-[#ffffff] relative border-0 upper-end before:top-[-4rem] before:border-l-transparent before:border-r-[100vw] before:border-t-[4rem] before:border-[#fefefe] before:content-[''] before:block before:absolute before:z-0 before:border-y-transparent before:border-0 before:border-solid before:right-0">
|
||||
<div className="container pb-12">
|
||||
<div className="flex flex-wrap mx-[-15px] !mb-[4.5rem] md:!mb-24">
|
||||
<div className="xl:w-10/12 w-full flex-[0_0_auto] !px-[15px] max-w-full !mx-auto !mt-[-9rem]">
|
||||
<div className="card">
|
||||
<div className="flex flex-wrap mx-0">
|
||||
<div className="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] max-w-full !self-stretch">
|
||||
<div className="map map-full rounded-t-[0.4rem] rounded-lg-start h-full min-h-[15rem]">
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m14!1m8!1m3!1d3308.116930443662!2d-117.6923042!3d33.9895302!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x80dcccd7e575dffd%3A0x30aebc3ca15ed41d!2s14508%20Central%20Ave%2C%20Chino%2C%20CA%2091710%2C%20USA!5e0!3m2!1sen!2sjp!4v1712719609523!5m2!1sen!2sjp"
|
||||
style={{ width: "100%", height: "100%", border: 0 }}
|
||||
allowFullScreen=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] xl:!px-[35px] lg:!px-[20px] !px-[15px] !my-[50px] max-w-full">
|
||||
<div className="flex flex-col gap-5">
|
||||
{contacts.map((item, idx) => (
|
||||
<div className="flex flex-row items-start gap-3" key={idx}>
|
||||
{(item.item_html_code || item.item_icon) && (
|
||||
<div className="w-8 h-8 flex items-center justify-center text-xl">
|
||||
{item.item_html_code ? (
|
||||
<span style={{ display: 'inline-block', width: '2rem', height: '2rem' }}
|
||||
dangerouslySetInnerHTML={{ __html: item.item_html_code }} />
|
||||
) : (
|
||||
<img src={item.item_icon} alt="icon" style={{ width: '2rem', height: '2rem', objectFit: 'contain' }} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-[90px]">
|
||||
{item.item_title}
|
||||
</div>
|
||||
<div className="break-all">
|
||||
{/(mail)/i.test(item.item_title) ? (
|
||||
<a href={`mailto:${item.item_subtitle || item.item_value || ''}`} className="!text-inherit hover:!text-[#1fc76f]">
|
||||
{item.item_subtitle || item.item_value || ''}
|
||||
</a>
|
||||
) : /(phone)/i.test(item.item_title) ? (
|
||||
<a href={`tel:${item.item_subtitle || item.item_value || ''}`} className="hover:underline">
|
||||
{item.item_subtitle || item.item_value || ''}
|
||||
</a>
|
||||
) : (
|
||||
<span>{item.item_subtitle || item.item_value || ''}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap mx-[-15px]">
|
||||
<div className="xl:w-8/12 xl:!ml-[16.66666667%] lg:w-10/12 lg:!ml-[8.33333333%] w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<h2 className="!text-[calc(1.305rem_+_0.66vw)] font-bold xl:!text-[1.8rem] !leading-[1.3] !mb-3 !text-center">
|
||||
{data.title}
|
||||
</h2>
|
||||
<p className="lead !leading-[1.65] text-[0.9rem] font-medium !text-center !mb-10">
|
||||
{data.description}
|
||||
</p>
|
||||
<form className="contact-form needs-validation" onSubmit={handleSubmit}>
|
||||
<div className="messages" />
|
||||
{modal.open && (
|
||||
<div style={{
|
||||
position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh',
|
||||
background: 'rgba(0,0,0,0.3)', display: 'flex', alignItems: 'center',
|
||||
justifyContent: 'center', zIndex: 9999
|
||||
}}>
|
||||
<div style={{
|
||||
background: '#fff', borderRadius: '16px', padding: '2.5rem 3.5rem',
|
||||
minWidth: '390px', maxWidth: '96vw', boxShadow: '0 8px 32px rgba(0,0,0,0.18)',
|
||||
textAlign: 'center', color: modal.type === 'success' ? '#1fc76f' : '#e53e3e',
|
||||
fontWeight: 500
|
||||
}}>
|
||||
<div style={{ fontSize: '1.1rem', marginBottom: '1.5rem' }}>{modal.message}</div>
|
||||
<button type="button" onClick={closeModal} style={{
|
||||
background: modal.type === 'success' ? '#1fc76f' : '#e53e3e',
|
||||
color: '#fff', border: 'none', borderRadius: '24px', padding: '0.5rem 2rem',
|
||||
fontSize: '1rem', cursor: 'pointer', boxShadow: '0 2px 8px rgba(0,0,0,0.08)'
|
||||
}}>OK</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap mx-[-10px]">
|
||||
<div className="xl:w-6/12 lg:w-6/12 md:w-6/12 w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<div className="form-floating relative !mb-4">
|
||||
<input id="form_name" type="text" name="name" className="form-control relative block w-full text-[.75rem] font-medium !text-[#60697b] bg-[#fefefe] bg-clip-padding border shadow-[0_0_1.25rem_rgba(30,34,40,0.04)] rounded-[0.4rem] border-solid border-[rgba(8,60,130,0.07)] transition-[border-color] duration-[0.15s] ease-in-out focus:shadow-[0_0_1.25rem_rgba(30,34,40,0.04),unset] focus-visible:!border-[rgba(63,120,224,0.5)] placeholder:!text-[#959ca9] placeholder:opacity-100 m-0 !pr-9 p-[.6rem_1rem] h-[calc(2.5rem_+_2px)] min-h-[calc(2.5rem_+_2px)] !leading-[1.25]" placeholder="" required value={form.name} onChange={handleInputChange} />
|
||||
<label htmlFor="form_name" className="!text-[#959ca9] !mb-2 !inline-block text-[.75rem] !absolute !z-[2] h-full overflow-hidden text-start text-ellipsis whitespace-nowrap pointer-events-none border origin-[0_0] px-4 py-[0.6rem] border-solid border-transparent left-0 top-0 font-Manrope">Name *</label>
|
||||
<div className="valid-feedback">Looks good!</div>
|
||||
<div className="invalid-feedback">Please enter your first name.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="xl:w-6/12 lg:w-6/12 md:w-6/12 w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<div className="form-floating relative !mb-4">
|
||||
<input id="form_phone" type="text" name="phone" className="form-control relative block w-full text-[.75rem] font-medium !text-[#60697b] bg-[#fefefe] bg-clip-padding border shadow-[0_0_1.25rem_rgba(30,34,40,0.04)] rounded-[0.4rem] border-solid border-[rgba(8,60,130,0.07)] transition-[border-color] duration-[0.15s] ease-in-out focus:shadow-[0_0_1.25rem_rgba(30,34,40,0.04),unset] focus-visible:!border-[rgba(63,120,224,0.5)] placeholder:!text-[#959ca9] placeholder:opacity-100 m-0 !pr-9 p-[.6rem_1rem] h-[calc(2.5rem_+_2px)] min-h-[calc(2.5rem_+_2px)] !leading-[1.25]" placeholder="" value={form.phone} onChange={handleInputChange} />
|
||||
<label htmlFor="form_phone" className="!text-[#959ca9] !mb-2 !inline-block text-[.75rem] !absolute !z-[2] h-full overflow-hidden text-start text-ellipsis whitespace-nowrap pointer-events-none border origin-[0_0] px-4 py-[0.6rem] border-solid border-transparent left-0 top-0 font-Manrope">Phone</label>
|
||||
<div className="valid-feedback">Looks good!</div>
|
||||
<div className="invalid-feedback">Please enter your phone number.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="xl:w-6/12 lg:w-6/12 md:w-6/12 w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<div className="form-floating relative !mb-4">
|
||||
<input id="form_email" type="email" name="email" className="form-control relative block w-full text-[.75rem] font-medium !text-[#60697b] bg-[#fefefe] bg-clip-padding border shadow-[0_0_1.25rem_rgba(30,34,40,0.04)] rounded-[0.4rem] border-solid border-[rgba(8,60,130,0.07)] transition-[border-color] duration-[0.15s] ease-in-out focus:shadow-[0_0_1.25rem_rgba(30,34,40,0.04),unset] focus-visible:!border-[rgba(63,120,224,0.5)] placeholder:!text-[#959ca9] placeholder:opacity-100 m-0 !pr-9 p-[.6rem_1rem] h-[calc(2.5rem_+_2px)] min-h-[calc(2.5rem_+_2px)] !leading-[1.25]" placeholder="" required value={form.email} onChange={handleInputChange} />
|
||||
<label htmlFor="form_email" className="!text-[#959ca9] !mb-2 !inline-block text-[.75rem] !absolute !z-[2] h-full overflow-hidden text-start text-ellipsis whitespace-nowrap pointer-events-none border origin-[0_0] px-4 py-[0.6rem] border-solid border-transparent left-0 top-0 font-Manrope">Email *</label>
|
||||
<div className="valid-feedback">Looks good!</div>
|
||||
<div className="invalid-feedback">Please provide a valid email address.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="xl:w-6/12 lg:w-6/12 md:w-6/12 w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<div className="form-select-wrapper !mb-4">
|
||||
<select className="form-select" id="form-select" name="department" value={form.department} onChange={handleInputChange}>
|
||||
<option value="">Select a department</option>
|
||||
<option value="Sales">Sales</option>
|
||||
<option value="Marketing">Marketing</option>
|
||||
<option value="Customer Support">Customer Support</option>
|
||||
</select>
|
||||
<div className="valid-feedback">Looks good!</div>
|
||||
<div className="invalid-feedback">Please select a department.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex-[0_0_auto] !px-[15px] max-w-full">
|
||||
<div className="form-floating relative !mb-4">
|
||||
<textarea id="form_message" name="message" className="form-control relative block w-full text-[.75rem] font-medium !text-[#60697b] bg-[#fefefe] bg-clip-padding border shadow-[0_0_1.25rem_rgba(30,34,40,0.04)] rounded-[0.4rem] border-solid border-[rgba(8,60,130,0.07)] transition-[border-color] duration-[0.15s] ease-in-out focus:shadow-[0_0_1.25rem_rgba(30,34,40,0.04),unset] focus-visible:!border-[rgba(63,120,224,0.5)] placeholder:!text-[#959ca9] placeholder:opacity-100 m-0 !pr-9 p-[.6rem_1rem] h-[calc(2.5rem_+_2px)] min-h-[calc(2.5rem_+_2px)] !leading-[1.25]" placeholder="" style={{ height: 150 }} required value={form.message} onChange={handleInputChange} />
|
||||
<label htmlFor="form_message" className="!text-[#959ca9] !mb-2 !inline-block text-[.75rem] !absolute !z-[2] h-full overflow-hidden text-start text-ellipsis whitespace-nowrap pointer-events-none border origin-[0_0] px-4 py-[0.6rem] border-solid border-transparent left-0 top-0 font-Manrope">Message *</label>
|
||||
<div className="valid-feedback">Looks good!</div>
|
||||
<div className="invalid-feedback">Please enter your messsage.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex-[0_0_auto] !px-[15px] max-w-full !text-center">
|
||||
<input type="submit" className="btn btn-primary !text-white !bg-[#1fc76f] border-[#1fc76f] hover:text-white hover:bg-[#1fc76f] hover:!border-[#1fc76f] active:text-white active:bg-[#1fc76f] active:border-[#1fc76f] disabled:text-white disabled:bg-[#1fc76f] disabled:border-[#1fc76f] !rounded-[50rem] btn-send !mb-3 hover:translate-y-[-0.15rem] hover:shadow-[0_0.25rem_0.75rem_rgba(30,34,40,0.15)]" value={submitting ? 'Sending...' : (data.button_text || 'Submit')} disabled={submitting} />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
16
components/contact/Contact1/index.jsx
Normal file
16
components/contact/Contact1/index.jsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { getComponentName } from "@/utils/getComponentName";
|
||||
import { fetchComponentData } from "@/utils/data";
|
||||
import UI from "./UI";
|
||||
|
||||
const componentName = getComponentName(import.meta.url);
|
||||
|
||||
export default async function Component() {
|
||||
const result = await fetchComponentData(componentName);
|
||||
|
||||
if (result.error || !result.data) {
|
||||
if (result.error) console.error(`Failed to fetch ${componentName} data:`, result.error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return <UI data={result.data} />;
|
||||
}
|
||||
@ -2,7 +2,7 @@ import React from "react";
|
||||
import Nav from "./Nav";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import LoginButton from "./LoginButton";
|
||||
import LanguageSelect from "./LanguageSelect";
|
||||
import SocialLinks from "../contact/SocialLinks";
|
||||
import { getSiteSettings } from "@/utils/data";
|
||||
|
||||
@ -16,7 +16,7 @@ export default async function Header() {
|
||||
return (
|
||||
<header className="relative w-full !bg-[#fefefe]">
|
||||
<nav className="navbar navbar-expand-lg transparent navbar-light w-full">
|
||||
<div className="w-full flex flex-row flex-nowrap items-center px-4">
|
||||
<div className="container mx-auto flex flex-row flex-nowrap items-center px-4">
|
||||
<div className="navbar-brand">
|
||||
<Link href={`/`}>
|
||||
<Image
|
||||
@ -67,7 +67,7 @@ export default async function Header() {
|
||||
<div className="navbar-other w-full !flex !ml-auto">
|
||||
<ul className="navbar-nav !flex-row !items-center !ml-auto">
|
||||
<li className="nav-item dropdown language-select uppercase group">
|
||||
<LoginButton color="#1fc76f" />
|
||||
<LanguageSelect color="#1fc76f" />
|
||||
</li>
|
||||
<li className="nav-item xl:!hidden lg:!hidden">
|
||||
<button className="hamburger offcanvas-nav-btn">
|
||||
|
||||
BIN
public/files/banner-contact.jpg
Normal file
BIN
public/files/banner-contact.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
BIN
public/files/fd0rn90q0g_d86aae5b.png
Normal file
BIN
public/files/fd0rn90q0g_d86aae5b.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
public/files/g6j7t53bkf_d0e03f08.png
Normal file
BIN
public/files/g6j7t53bkf_d0e03f08.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
@ -315,4 +315,4 @@ export async function getSiteSettings() {
|
||||
site_name: "Jsite"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user