feat: Redesign the contact page by adding a contact image and updating the form layout and styling.
This commit is contained in:
@@ -4,314 +4,349 @@
|
|||||||
* Pixel-perfect implementation based on Webflow design
|
* Pixel-perfect implementation based on Webflow design
|
||||||
* Includes form validation, submission handling, and responsive layout
|
* Includes form validation, submission handling, and responsive layout
|
||||||
*/
|
*/
|
||||||
import Layout from '../layouts/Layout.astro'
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
|
||||||
// Metadata for SEO
|
// Metadata for SEO
|
||||||
const title = '聯絡我們 | 恩群數位行銷'
|
const title = "聯絡我們 | 恩群數位行銷";
|
||||||
const description = '有任何問題嗎?歡迎聯絡恩群數位行銷,我們的專業團隊將竭誠為您服務。電話: 02 5570 0527,Email: enchuntaiwan@gmail.com'
|
const description =
|
||||||
|
"有任何問題嗎?歡迎聯絡恩群數位行銷,我們的專業團隊將竭誠為您服務。電話: 02 5570 0527,Email: enchuntaiwan@gmail.com";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={title} description={description}>
|
<Layout title={title} description={description}>
|
||||||
<section class="py-16 bg-background scroll-mt-20 lg:py-8 md:py-8" id="contact">
|
<section class="py-20 bg-[#f4f5f7] scroll-mt-20 lg:py-10" id="contact">
|
||||||
<div class="grid grid-cols-2 gap-12 items-center max-w-[1200px] mx-auto px-5 lg:grid-cols-1 lg:gap-8">
|
<div
|
||||||
|
class="grid grid-cols-2 gap-16 items-start max-w-3xl mx-auto px-5 lg:grid-cols-2 lg:gap-10"
|
||||||
|
>
|
||||||
|
<!-- Contact Image Side -->
|
||||||
|
<div class="flex justify-center items-center w-full lg:order-1 pt-4">
|
||||||
|
<img
|
||||||
|
src="https://enchun-cms.anlstudio.cc/api/media/file/61f24aa108528b532e942d09_Contact%20us-bro%201.svg"
|
||||||
|
alt="聯絡我們插圖"
|
||||||
|
class="w-full h-auto object-contain drop-shadow-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Contact Form Side -->
|
<!-- Contact Form Side -->
|
||||||
<div class="w-full">
|
<div class="w-full lg:order-2">
|
||||||
<h1 class="text-[2.5rem] font-bold leading-tight text-text-primary mb-4 lg:text-2xl md:text-[1.5rem]">聯絡我們</h1>
|
<h2
|
||||||
<p class="text-lg font-normal leading-relaxed text-slate-600 mb-2">
|
class="text-4xl font-bold leading-snug text-[#2a5b83] mb-3 lg:text-3xl"
|
||||||
有任何問題嗎?歡迎聯絡我們,我們將竭誠為您服務。
|
>
|
||||||
|
與我們聯絡
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
class="text-lg font-medium text-[#2a5b83] mb-2 tracking-wide lg:text-base"
|
||||||
|
>
|
||||||
|
任何關於行銷的相關訊息或是詢價,歡迎與我們聯絡
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm italic text-text-muted mb-8">
|
<p class="text-sm italic text-slate-500 mb-8">
|
||||||
* 標註欄位為必填
|
有星號的地方 (*) 是必填欄位
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Contact Form -->
|
<!-- Contact Form -->
|
||||||
<form id="contact-form" class="p-8 bg-surface rounded-lg shadow-lg lg:p-6 md:p-4" novalidate>
|
<form id="contact-form" class="w-full flex flex-col gap-6" novalidate>
|
||||||
<!-- Success Message -->
|
<!-- Success Message -->
|
||||||
<div id="form-success" class="p-4 px-6 rounded-md mt-4 text-center font-medium bg-[#d4edda] text-[#155724] border border-[#c3e6cb] animate-[slideUp_0.3s_ease-out]" style="display: none;">
|
<div
|
||||||
|
id="form-success"
|
||||||
|
class="p-4 px-6 rounded-lg text-center font-medium bg-[#d4edda] text-[#155724] border border-[#c3e6cb] animate-[slideUp_0.3s_ease-out]"
|
||||||
|
style="display: none;"
|
||||||
|
>
|
||||||
感謝您的留言!我們會盡快回覆您。
|
感謝您的留言!我們會盡快回覆您。
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error Message -->
|
<!-- Error Message -->
|
||||||
<div id="form-error" class="p-4 px-6 rounded-md mt-4 text-center font-medium bg-[#f8d7da] text-[#721c24] border border-[#f5c6cb] animate-[slideUp_0.3s_ease-out]" style="display: none;">
|
<div
|
||||||
|
id="form-error"
|
||||||
|
class="p-4 px-6 rounded-lg text-center font-medium bg-[#f8d7da] text-[#721c24] border border-[#f5c6cb] animate-[slideUp_0.3s_ease-out]"
|
||||||
|
style="display: none;"
|
||||||
|
>
|
||||||
送出失敗,請稍後再試或直接來電。
|
送出失敗,請稍後再試或直接來電。
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form Fields -->
|
|
||||||
<div class="grid grid-cols-2 gap-6 mb-6 md:grid-cols-1 md:gap-4">
|
|
||||||
<!-- Name Field -->
|
<!-- Name Field -->
|
||||||
<div class="flex flex-col mb-6">
|
<div class="flex flex-col">
|
||||||
<label for="Name" class="text-sm font-semibold text-text-primary mb-2 block">
|
<label
|
||||||
姓名 <span class="text-primary">*</span>
|
for="Name"
|
||||||
|
class="text-[0.95rem] font-medium text-slate-500 mb-2 block"
|
||||||
|
>
|
||||||
|
姓名*
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="Name"
|
id="Name"
|
||||||
name="Name"
|
name="Name"
|
||||||
class="w-full px-4 py-3 border border-border rounded-md text-base leading-normal bg-background transition-all duration-150 font-sans text-text-primary focus:outline-none focus:border-primary focus:shadow-[0_0_0_3px_rgba(56,152,236,0.1)] focus:-translate-y-0.5 placeholder:text-text-muted [&.error]:border-[#dc3545] [&.error]:bg-[#fff5f5]"
|
class="w-full px-4 py-2.5 border border-[#4a90e2] rounded-lg text-base bg-white focus:outline-none focus:ring-2 focus:ring-[#4a90e2]/20 transition-all shadow-sm [&.error]:border-[#dc3545]"
|
||||||
required
|
required
|
||||||
minlength="2"
|
minlength="2"
|
||||||
maxlength="256"
|
maxlength="256"
|
||||||
placeholder="請輸入您的姓名"
|
|
||||||
/>
|
/>
|
||||||
<span class="error-message text-[#dc3545] text-sm mt-1 hidden" id="Name-error"></span>
|
<span
|
||||||
|
class="error-message text-[#dc3545] text-sm mt-1 hidden"
|
||||||
|
id="Name-error"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Phone Field -->
|
<!-- Phone Field -->
|
||||||
<div class="flex flex-col mb-6">
|
<div class="flex flex-col">
|
||||||
<label for="Phone" class="text-sm font-semibold text-text-primary mb-2 block">
|
<label
|
||||||
聯絡電話 <span class="text-primary">*</span>
|
for="Phone"
|
||||||
|
class="text-[0.95rem] font-medium text-slate-500 mb-2 block"
|
||||||
|
>
|
||||||
|
聯絡電話*
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
id="Phone"
|
id="Phone"
|
||||||
name="Phone"
|
name="Phone"
|
||||||
class="w-full px-4 py-3 border border-border rounded-md text-base leading-normal bg-background transition-all duration-150 font-sans text-text-primary focus:outline-none focus:border-primary focus:shadow-[0_0_0_3px_rgba(56,152,236,0.1)] focus:-translate-y-0.5 placeholder:text-text-muted [&.error]:border-[#dc3545] [&.error]:bg-[#fff5f5]"
|
class="w-full px-4 py-2.5 border border-white shadow-sm rounded-lg text-base bg-white focus:outline-none focus:border-[#4a90e2] focus:ring-2 focus:ring-[#4a90e2]/20 transition-all [&.error]:border-[#dc3545]"
|
||||||
required
|
required
|
||||||
placeholder="請輸入您的電話號碼"
|
|
||||||
/>
|
/>
|
||||||
<span class="error-message text-[#dc3545] text-sm mt-1 hidden" id="Phone-error"></span>
|
<span
|
||||||
</div>
|
class="error-message text-[#dc3545] text-sm mt-1 hidden"
|
||||||
|
id="Phone-error"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Email Field -->
|
<!-- Email Field -->
|
||||||
<div class="flex flex-col mb-6">
|
<div class="flex flex-col">
|
||||||
<label for="Email" class="text-sm font-semibold text-text-primary mb-2 block">
|
<label
|
||||||
Email <span class="text-primary">*</span>
|
for="Email"
|
||||||
|
class="text-[0.95rem] font-medium text-slate-500 mb-2 block"
|
||||||
|
>
|
||||||
|
Email *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
id="Email"
|
id="Email"
|
||||||
name="Email"
|
name="Email"
|
||||||
class="w-full px-4 py-3 border border-border rounded-md text-base leading-normal bg-background transition-all duration-150 font-sans text-text-primary focus:outline-none focus:border-primary focus:shadow-[0_0_0_3px_rgba(56,152,236,0.1)] focus:-translate-y-0.5 placeholder:text-text-muted [&.error]:border-[#dc3545] [&.error]:bg-[#fff5f5]"
|
class="w-full px-4 py-2.5 border border-white shadow-sm rounded-lg text-base bg-white focus:outline-none focus:border-[#4a90e2] focus:ring-2 focus:ring-[#4a90e2]/20 transition-all [&.error]:border-[#dc3545]"
|
||||||
required
|
required
|
||||||
placeholder="請輸入您的 Email"
|
|
||||||
/>
|
/>
|
||||||
<span class="error-message text-[#dc3545] text-sm mt-1 hidden" id="Email-error"></span>
|
<span
|
||||||
|
class="error-message text-[#dc3545] text-sm mt-1 hidden"
|
||||||
|
id="Email-error"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Message Field -->
|
<!-- Message Field -->
|
||||||
<div class="flex flex-col mb-8">
|
<div class="flex flex-col">
|
||||||
<label for="Message" class="text-sm font-semibold text-text-primary mb-2 block">
|
<label
|
||||||
聯絡訊息 <span class="text-primary">*</span>
|
for="Message"
|
||||||
|
class="text-[0.95rem] font-medium text-slate-500 mb-2 block"
|
||||||
|
>
|
||||||
|
聯絡訊息
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="Message"
|
id="Message"
|
||||||
name="Message"
|
name="Message"
|
||||||
class="w-full px-4 py-3 border border-border rounded-md text-base leading-normal bg-background transition-all duration-150 font-sans text-text-primary focus:outline-none focus:border-primary focus:shadow-[0_0_0_3px_rgba(56,152,236,0.1)] focus:-translate-y-0.5 placeholder:text-text-muted min-h-[120px] resize-y [&.error]:border-[#dc3545] [&.error]:bg-[#fff5f5]"
|
class="w-full px-4 py-3 border border-white shadow-sm rounded-lg text-base bg-white focus:outline-none focus:border-[#4a90e2] focus:ring-2 focus:ring-[#4a90e2]/20 transition-all resize-y min-h-[120px] [&.error]:border-[#dc3545]"
|
||||||
minlength="10"
|
|
||||||
maxlength="5000"
|
|
||||||
required
|
required
|
||||||
placeholder="請輸入您的訊息(至少 10 個字元)"
|
minlength="10"
|
||||||
></textarea>
|
maxlength="5000"></textarea>
|
||||||
<span class="error-message text-[#dc3545] text-sm mt-1 hidden" id="Message-error"></span>
|
<span
|
||||||
|
class="error-message text-[#dc3545] text-sm mt-1 hidden"
|
||||||
|
id="Message-error"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Submit Button -->
|
<!-- Submit Button -->
|
||||||
<button type="submit" class="bg-primary text-white border-none rounded-md px-8 py-[0.875rem] text-base font-semibold cursor-pointer transition-all duration-150 text-center inline-flex items-center justify-center min-w-[200px] w-full hover:bg-primary-hover hover:-translate-y-0.5 hover:shadow-md active:translate-y-0 disabled:bg-slate-500 disabled:cursor-not-allowed disabled:opacity-80" id="submit-btn">
|
<div class="flex justify-end mt-2">
|
||||||
<span class="button-text">送出訊息</span>
|
<button
|
||||||
<span class="button-loading" style="display: none;">送出中...</span>
|
type="submit"
|
||||||
|
class="bg-[#2a5b83] text-white border-none rounded-lg px-10 py-[0.6rem] text-[1.05rem] font-medium cursor-pointer transition-all duration-200 hover:bg-[#1f4666] shadow-sm transform hover:-translate-y-0.5 min-w-[120px] disabled:bg-slate-400 disabled:cursor-not-allowed disabled:transform-none"
|
||||||
|
id="submit-btn"
|
||||||
|
>
|
||||||
|
<span class="button-text">送出</span>
|
||||||
|
<span class="button-loading" style="display: none;"
|
||||||
|
>送出中...</span
|
||||||
|
>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Contact Image Side -->
|
|
||||||
<div class="flex flex-col gap-8 justify-center items-center">
|
|
||||||
<div class="w-full rounded-lg overflow-hidden shadow-md bg-slate-100 aspect-[3/2]">
|
|
||||||
<img
|
|
||||||
src="/placeholder-contact.jpg"
|
|
||||||
alt="聯絡恩群數位"
|
|
||||||
width="600"
|
|
||||||
height="400"
|
|
||||||
class="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Contact Info Card -->
|
|
||||||
<div class="w-full p-6 bg-white rounded-lg shadow-lg">
|
|
||||||
<h3 class="text-xl font-semibold text-text-primary mb-4">聯絡資訊</h3>
|
|
||||||
<div class="flex items-center gap-3 mb-4 text-[0.95rem] text-text-secondary">
|
|
||||||
<svg class="w-5 h-5 flex-shrink-0 text-primary" viewBox="0 0 24 24"><path fill="currentColor" d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1.45-.17 2.53-.5.36-.11.74-.47 1.02-.75l2.2-2.2c.27-.27.36-.67.24-1.02-.37-1.12-.57-2.33-.57-3.57 0-.55.17-1.45.5-2.53.36-.11.74.47-1.14.75-1.02L2.05 21.05c.15.35.48.59.84.59l2.2.73c.36.12.74.12 1.06-.05.13-.07.22-.17.28-.28l2.2-2.2c.27-.27.36-.67.24-1.02-.37-1.12-.57-2.33-.57-3.57 0-.55.17-1.45.5-2.53.36-.11.74.47 1.14.75 1.02L2.05 21.05zM19.95 2.95c-.15-.35-.48-.59-.84-.59l-2.2-.73c-.36-.12-.74-.12-1.06.05-.13.07-.22.17-.28.28l-2.2 2.2c-.27.27-.36.67-.24 1.02.37 1.12.57 2.33.57 3.57 0 .55-.17 1.45-.5 2.53-.36.11.74-.47-1.14-.75l1.02-1.02 2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1.45-.17 2.53-.5.36-.11.74-.47 1.02-.75l2.2-2.2c.27-.27.36-.67.24-1.02-.37-1.12-.57-2.33-.57-3.57 0-.55.17-1.45.5-2.53.36-.11.74.47 1.02.75 1.02L2.05 21.05c-.15-.35-.48-.59-.84-.59l-2.2-.73c-.36-.12-.74-.12-1.06.05-.13.07-.22.17-.28.28l-2.2 2.2c-.27.27-.36.67-.24 1.02.37 1.12.57 2.33.57 3.57 0 .55-.17 1.45-.5 2.53-.36-.11-.74.47-1.14.75l1.02-1.02 2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1.45-.17 2.53-.5.36-.11.74-.47 1.02-.75l2.2-2.2c.27-.27.36-.67.24-1.02-.37-1.12-.57-2.33-.57-3.57 0-.55.17-1.45.5-2.53.36-.11-.74.47 1.14.75 1.02L2.05 21.05c.15.35.48.59.84.59l2.2.73c.36.12.74.12 1.06-.05.13-.07.22-.17.28-.28l2.2-2.2c.27-.27.36-.67.24-1.02-.37-1.12-.57-2.33-.57-3.57 0-.55.17-1.45.5-2.53.36-.11.74.47 1.14.75 1.02z"/></svg>
|
|
||||||
<span>諮詢電話: 02 5570 0527</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-3 mb-4 text-[0.95rem] text-text-secondary">
|
|
||||||
<svg class="w-5 h-5 flex-shrink-0 text-primary" viewBox="0 0 24 24"><path fill="currentColor" d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/></svg>
|
|
||||||
<span>Email: <a href="mailto:enchuntaiwan@gmail.com" class="text-primary no-underline hover:underline">enchuntaiwan@gmail.com</a></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Form validation and submission handler
|
// Form validation and submission handler
|
||||||
function initContactForm() {
|
function initContactForm() {
|
||||||
const form = document.getElementById('contact-form') as HTMLFormElement
|
const form = document.getElementById("contact-form") as HTMLFormElement;
|
||||||
const submitBtn = document.getElementById('submit-btn') as HTMLButtonElement
|
const submitBtn = document.getElementById(
|
||||||
const successMsg = document.getElementById('form-success') as HTMLElement
|
"submit-btn",
|
||||||
const errorMsg = document.getElementById('form-error') as HTMLElement
|
) as HTMLButtonElement;
|
||||||
|
const successMsg = document.getElementById("form-success") as HTMLElement;
|
||||||
|
const errorMsg = document.getElementById("form-error") as HTMLElement;
|
||||||
|
|
||||||
if (!form) return
|
if (!form) return;
|
||||||
|
|
||||||
// Validation patterns
|
// Validation patterns
|
||||||
const patterns = {
|
const patterns = {
|
||||||
Name: /^[\u4e00-\u9fa5a-zA-Z\s]{2,256}$/,
|
Name: /^[\u4e00-\u9fa5a-zA-Z\s]{2,256}$/,
|
||||||
Phone: /^[0-9\-\s\+]{6,20}$/,
|
Phone: /^[0-9\-\s\+]{6,20}$/,
|
||||||
Email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
Email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
||||||
Message: /^.{10,5000}$/
|
Message: /^.{10,5000}$/,
|
||||||
}
|
};
|
||||||
|
|
||||||
// Validation function
|
// Validation function
|
||||||
function validateField(input: HTMLInputElement | HTMLTextAreaElement): boolean {
|
function validateField(
|
||||||
const name = input.name
|
input: HTMLInputElement | HTMLTextAreaElement,
|
||||||
const value = input.value.trim()
|
): boolean {
|
||||||
const errorSpan = document.getElementById(`${name}-error`) as HTMLElement
|
const name = input.name;
|
||||||
|
const value = input.value.trim();
|
||||||
|
const errorSpan = document.getElementById(`${name}-error`) as HTMLElement;
|
||||||
|
|
||||||
if (!input.hasAttribute('required') && !value) {
|
if (!input.hasAttribute("required") && !value) {
|
||||||
clearError(input, errorSpan)
|
clearError(input, errorSpan);
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let isValid = true
|
let isValid = true;
|
||||||
let errorMessage = ''
|
let errorMessage = "";
|
||||||
|
|
||||||
// Required check
|
// Required check
|
||||||
if (input.hasAttribute('required') && !value) {
|
if (input.hasAttribute("required") && !value) {
|
||||||
isValid = false
|
isValid = false;
|
||||||
errorMessage = '此欄位為必填'
|
errorMessage = "此欄位為必填";
|
||||||
}
|
}
|
||||||
// Pattern validation
|
// Pattern validation
|
||||||
else if (patterns[name as keyof typeof patterns] && !patterns[name as keyof typeof patterns].test(value)) {
|
else if (
|
||||||
isValid = false
|
patterns[name as keyof typeof patterns] &&
|
||||||
|
!patterns[name as keyof typeof patterns].test(value)
|
||||||
|
) {
|
||||||
|
isValid = false;
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'Name':
|
case "Name":
|
||||||
errorMessage = '請輸入有效的姓名(至少 2 個字元)'
|
errorMessage = "請輸入有效的姓名(至少 2 個字元)";
|
||||||
break
|
break;
|
||||||
case 'Phone':
|
case "Phone":
|
||||||
errorMessage = '請輸入有效的電話號碼'
|
errorMessage = "請輸入有效的電話號碼";
|
||||||
break
|
break;
|
||||||
case 'Email':
|
case "Email":
|
||||||
errorMessage = '請輸入有效的 Email 格式'
|
errorMessage = "請輸入有效的 Email 格式";
|
||||||
break
|
break;
|
||||||
case 'Message':
|
case "Message":
|
||||||
errorMessage = '訊息至少需要 10 個字元'
|
errorMessage = "訊息至少需要 10 個字元";
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show/hide error
|
// Show/hide error
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
input.classList.add('error')
|
input.classList.add("error");
|
||||||
if (errorSpan) {
|
if (errorSpan) {
|
||||||
errorSpan.textContent = errorMessage
|
errorSpan.textContent = errorMessage;
|
||||||
errorSpan.style.display = 'block'
|
errorSpan.style.display = "block";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clearError(input, errorSpan)
|
clearError(input, errorSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isValid
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearError(input: HTMLInputElement | HTMLTextAreaElement, errorSpan: HTMLElement) {
|
function clearError(
|
||||||
input.classList.remove('error')
|
input: HTMLInputElement | HTMLTextAreaElement,
|
||||||
|
errorSpan: HTMLElement,
|
||||||
|
) {
|
||||||
|
input.classList.remove("error");
|
||||||
if (errorSpan) {
|
if (errorSpan) {
|
||||||
errorSpan.textContent = ''
|
errorSpan.textContent = "";
|
||||||
errorSpan.style.display = 'none'
|
errorSpan.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Real-time validation on blur
|
// Real-time validation on blur
|
||||||
form.querySelectorAll('input, textarea').forEach((field) => {
|
form.querySelectorAll("input, textarea").forEach((field) => {
|
||||||
field.addEventListener('blur', () => {
|
field.addEventListener("blur", () => {
|
||||||
validateField(field as HTMLInputElement | HTMLTextAreaElement)
|
validateField(field as HTMLInputElement | HTMLTextAreaElement);
|
||||||
})
|
});
|
||||||
|
|
||||||
field.addEventListener('input', () => {
|
field.addEventListener("input", () => {
|
||||||
const input = field as HTMLInputElement | HTMLTextAreaElement
|
const input = field as HTMLInputElement | HTMLTextAreaElement;
|
||||||
if (input.classList.contains('error')) {
|
if (input.classList.contains("error")) {
|
||||||
validateField(input)
|
validateField(input);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
// Form submission
|
// Form submission
|
||||||
form.addEventListener('submit', async (e) => {
|
form.addEventListener("submit", async (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
|
|
||||||
// Validate all fields
|
// Validate all fields
|
||||||
const inputs = form.querySelectorAll('input, textarea') as NodeListOf<HTMLInputElement | HTMLTextAreaElement>
|
const inputs = form.querySelectorAll("input, textarea") as NodeListOf<
|
||||||
let isFormValid = true
|
HTMLInputElement | HTMLTextAreaElement
|
||||||
|
>;
|
||||||
|
let isFormValid = true;
|
||||||
|
|
||||||
inputs.forEach((input) => {
|
inputs.forEach((input) => {
|
||||||
if (!validateField(input)) {
|
if (!validateField(input)) {
|
||||||
isFormValid = false
|
isFormValid = false;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!isFormValid) {
|
if (!isFormValid) {
|
||||||
// Scroll to first error
|
// Scroll to first error
|
||||||
const firstError = form.querySelector('.input_field.error') as HTMLElement
|
const firstError = form.querySelector(
|
||||||
|
".input_field.error",
|
||||||
|
) as HTMLElement;
|
||||||
if (firstError) {
|
if (firstError) {
|
||||||
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
firstError.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
}
|
}
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show loading state
|
// Show loading state
|
||||||
submitBtn.disabled = true
|
submitBtn.disabled = true;
|
||||||
const buttonText = submitBtn.querySelector('.button-text') as HTMLElement
|
const buttonText = submitBtn.querySelector(".button-text") as HTMLElement;
|
||||||
const buttonLoading = submitBtn.querySelector('.button-loading') as HTMLElement
|
const buttonLoading = submitBtn.querySelector(
|
||||||
if (buttonText) buttonText.style.display = 'none'
|
".button-loading",
|
||||||
if (buttonLoading) buttonLoading.style.display = 'inline'
|
) as HTMLElement;
|
||||||
|
if (buttonText) buttonText.style.display = "none";
|
||||||
|
if (buttonLoading) buttonLoading.style.display = "inline";
|
||||||
|
|
||||||
// Hide previous messages
|
// Hide previous messages
|
||||||
successMsg.style.display = 'none'
|
successMsg.style.display = "none";
|
||||||
errorMsg.style.display = 'none'
|
errorMsg.style.display = "none";
|
||||||
|
|
||||||
// Collect form data
|
// Collect form data
|
||||||
const formData = new FormData(form)
|
const formData = new FormData(form);
|
||||||
const data = {
|
const data = {
|
||||||
name: formData.get('Name'),
|
name: formData.get("Name"),
|
||||||
phone: formData.get('Phone'),
|
phone: formData.get("Phone"),
|
||||||
email: formData.get('Email'),
|
email: formData.get("Email"),
|
||||||
message: formData.get('Message')
|
message: formData.get("Message"),
|
||||||
}
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Submit to backend (via API proxy)
|
// Submit to backend (via API proxy)
|
||||||
const response = await fetch('/api/contact', {
|
const response = await fetch("/api/contact", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data),
|
||||||
})
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// Success
|
// Success
|
||||||
successMsg.style.display = 'block'
|
successMsg.style.display = "block";
|
||||||
form.reset()
|
form.reset();
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Submission failed')
|
throw new Error("Submission failed");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Form submission error:', error)
|
console.error("Form submission error:", error);
|
||||||
errorMsg.style.display = 'block'
|
errorMsg.style.display = "block";
|
||||||
} finally {
|
} finally {
|
||||||
// Reset button state
|
// Reset button state
|
||||||
submitBtn.disabled = false
|
submitBtn.disabled = false;
|
||||||
if (buttonText) buttonText.style.display = 'inline'
|
if (buttonText) buttonText.style.display = "inline";
|
||||||
if (buttonLoading) buttonLoading.style.display = 'none'
|
if (buttonLoading) buttonLoading.style.display = "none";
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize when DOM is ready
|
// Initialize when DOM is ready
|
||||||
document.addEventListener('DOMContentLoaded', initContactForm)
|
document.addEventListener("DOMContentLoaded", initContactForm);
|
||||||
if (document.readyState !== 'loading') {
|
if (document.readyState !== "loading") {
|
||||||
initContactForm()
|
initContactForm();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user