Add owner portal, i18n, contact API, and region/footer assets
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import type { Metadata } from "next";
|
||||
import "@/components/portal/portal.css";
|
||||
import { LanguageProvider } from "@/components/i18n";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "YEIDA Portal — Yamuna Expressway Property Management",
|
||||
description:
|
||||
"Allottee portal for the Yamuna Expressway Industrial Development Authority — manage your plot, payments, documents, construction and more.",
|
||||
};
|
||||
|
||||
export default function PortalRootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
{/* Same fonts as the main website. Loaded via <link> (not @import) so
|
||||
they survive Next's CSS processing. */}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Heebo:wght@300;400;500;600;700&family=Parkinsans:wght@300..800&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Philosopher:wght@400;700&family=Gloock&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body className="anim-on"><LanguageProvider>{children}</LanguageProvider></body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Assistant } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function AssistantPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <Assistant go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Construction } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function ConstructionPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <Construction go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Dashboard } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <Dashboard go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Documents } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function DocumentsPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <Documents go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Journey } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function JourneyPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <Journey go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
'use client';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { usePortalNav, routeKeyFromPath } from '@/components/portal/nav';
|
||||
import { useToast } from '@/components/portal/ui';
|
||||
import { Shell } from '@/components/portal/pages';
|
||||
import { PortalProvider } from '@/components/portal/portal-context';
|
||||
|
||||
export default function PortalAppLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const go = usePortalNav();
|
||||
const pathname = usePathname();
|
||||
const route = routeKeyFromPath(pathname);
|
||||
const [toastNode, toast] = useToast();
|
||||
|
||||
return (
|
||||
<PortalProvider go={go} toast={toast}>
|
||||
<Shell route={route} go={go}>
|
||||
{children}
|
||||
</Shell>
|
||||
{toastNode}
|
||||
</PortalProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { MyJourney } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function MyJourneyPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <MyJourney go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Notices } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function NoticesPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <Notices go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Payments } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function PaymentsPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <Payments go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { MyPlot } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function PlotPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <MyPlot go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Profile } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function ProfilePage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <Profile go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { SamplePlans } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function SamplePlansPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <SamplePlans go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { SectorMaps } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function SectorMapsPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <SectorMaps go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Support } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function SupportPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <Support go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Transfer } from '@/components/portal/pages';
|
||||
import { usePortal } from '@/components/portal/portal-context';
|
||||
|
||||
export default function TransferPage() {
|
||||
const { go, toast } = usePortal();
|
||||
return <Transfer go={go} toast={toast} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { AllotmentLookup } from '@/components/portal/onboarding';
|
||||
import { usePortalNav } from '@/components/portal/nav';
|
||||
|
||||
export default function AllotmentLookupPage() {
|
||||
const go = usePortalNav();
|
||||
return <AllotmentLookup go={go} />;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
'use client';
|
||||
import '@/components/portal/site-header.css';
|
||||
import Header from '@/components/Header';
|
||||
import { LoginSelect } from '@/components/portal/auth';
|
||||
import { usePortalNav } from '@/components/portal/nav';
|
||||
|
||||
export default function LoginPage() {
|
||||
const go = usePortalNav();
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<LoginSelect go={go} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
'use client';
|
||||
import { Icon, Logo, PageHead, SectorMaps } from '@/components/portal/pages';
|
||||
import { usePortalNav } from '@/components/portal/nav';
|
||||
|
||||
export default function PublicMapsPage() {
|
||||
const go = usePortalNav();
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', background: 'var(--bg)' }}>
|
||||
<header
|
||||
style={{
|
||||
height: 64,
|
||||
background: '#fff',
|
||||
borderBottom: '1px solid var(--line)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0 32px',
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 50,
|
||||
}}
|
||||
>
|
||||
<button onClick={() => go('welcome')} style={{ background: 'none', border: 'none', cursor: 'pointer' }}>
|
||||
<Logo size={36} />
|
||||
</button>
|
||||
<div className="row gap-3">
|
||||
<button className="btn btn-ghost" onClick={() => go('welcome')}>
|
||||
<Icon name="chevL" size={16} />
|
||||
Home
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={() => go('login')}>
|
||||
Login to Portal
|
||||
<Icon name="arrowR" size={17} />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div style={{ maxWidth: 1600, margin: '0 auto', padding: '28px 36px' }}>
|
||||
<PageHead
|
||||
title="Explore Sector Maps"
|
||||
subtitle="Browse plotted layouts and master plans across YEIDA sectors — no login required"
|
||||
/>
|
||||
<SectorMaps go={go} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
// The portal has no public landing page — entering /portal sends users
|
||||
// straight to the login screen.
|
||||
export default function PortalIndex() {
|
||||
redirect('/portal/login');
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Register } from '@/components/portal/onboarding';
|
||||
import { usePortalNav } from '@/components/portal/nav';
|
||||
|
||||
export default function RegisterPage() {
|
||||
const go = usePortalNav();
|
||||
return <Register go={go} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
import { Verify } from '@/components/portal/auth';
|
||||
import { usePortalNav } from '@/components/portal/nav';
|
||||
|
||||
export default function VerifyPage() {
|
||||
const go = usePortalNav();
|
||||
return <Verify go={go} />;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Fragment } from "react";
|
||||
|
||||
export const metadata: Metadata = { title: "About Us — Yamuna Expressway Authority" };
|
||||
|
||||
@@ -16,23 +17,23 @@ const LEADERS = [
|
||||
];
|
||||
|
||||
const TEAM = [
|
||||
{ name: "Pankaj Mishra", role: "Project Manager", email: "pankaj@yamunaexpressway.com", img: "0b7c87261b26ce5b05ffa1512e77607ea822a40f" },
|
||||
{ name: "Priya Sharma", role: "Operations Head", email: "pankaj@yamunaexpressway.com", img: "6ec9dc1d3bec2b6daa67f499e88e658014d4391d" },
|
||||
{ name: "Pankaj Mishra", role: "Project Manager", email: "pankaj@yamunaexpressway.com", img: "a19d44ed523529a5b722a9d59a23264b7664bf27" },
|
||||
{ name: "Priya Sharma", role: "Operations Head", email: "pankaj@yamunaexpressway.com", img: "4f31d0bb3ef9597350034b4f2e52797a2b73fece" },
|
||||
{ name: "Amit Patel", role: "Chief Engineer", email: "pankaj@yamunaexpressway.com", img: "a19d44ed523529a5b722a9d59a23264b7664bf27" },
|
||||
{ name: "Sneha Reddy", role: "Finance Director", email: "pankaj@yamunaexpressway.com", img: "4f31d0bb3ef9597350034b4f2e52797a2b73fece" },
|
||||
];
|
||||
|
||||
const EXPERTS = [
|
||||
{ n: "25+", t: "Architectural Team", d: "Licensed architects with international awards and 15+ years of luxury villa design experience" },
|
||||
{ n: "150+", t: "Construction Experts", d: "Skilled engineers, supervisors, and craftsmen ensuring premium quality at every stage" },
|
||||
{ n: "40+", t: "Project Managers", d: "Dedicated professionals managing timelines, budgets, and client communication with precision" },
|
||||
{ n: "25+", t: "Architectural Team", d: "Licensed architects with international awards and 15+ years of luxury villa design experience", icon: "25.svg" },
|
||||
{ n: "150+", t: "Construction Experts", d: "Skilled engineers, supervisors, and craftsmen ensuring premium quality at every stage", icon: "150.svg" },
|
||||
{ n: "40+", t: "Project Managers", d: "Dedicated professionals managing timelines, budgets, and client communication with precision", icon: "40.svg" },
|
||||
];
|
||||
|
||||
const BADGES = [
|
||||
{ n: "9001:2015", t: "ISO Certified" },
|
||||
{ n: "Verified", t: "RERA Registered" },
|
||||
{ n: "12+", t: "Industry Awards" },
|
||||
{ n: "IGBC Rated", t: "Green Building" },
|
||||
{ n: "9001:2015", t: "ISO Certified", icon: "isocertified.svg" },
|
||||
{ n: "Verified", t: "RERA Registered", icon: "verified.svg" },
|
||||
{ n: "12+", t: "Industry Awards", icon: "12.svg" },
|
||||
{ n: "IGBC Rated", t: "Green Building", icon: "igbc.svg" },
|
||||
];
|
||||
|
||||
const STEPS = [
|
||||
@@ -113,22 +114,22 @@ export default function AboutPage() {
|
||||
</div>
|
||||
<div className="ab-leaders">
|
||||
<article className="ab-leader">
|
||||
<img src={`${IMG}${LEADERS[0].img}.png`} alt={LEADERS[0].name} />
|
||||
<div className="teamwrap"> <img src={`${IMG}${LEADERS[0].img}.png`} alt={LEADERS[0].name} /></div>
|
||||
<div className="ab-leader-info">
|
||||
<h3>{LEADERS[0].name}</h3>
|
||||
<span>{LEADERS[0].role}</span>
|
||||
<div className="leadcont"> <h3>{LEADERS[0].name}</h3>
|
||||
<span>{LEADERS[0].role}</span></div>
|
||||
<a href={`mailto:${LEADERS[0].email}`}>{LEADERS[0].email}</a>
|
||||
</div>
|
||||
</article>
|
||||
<div className="ab-leader-emblem">
|
||||
<img src={`${IMG}30626e48a07e8dee69dff426122797a6f4b26a5b.png`} alt="" />
|
||||
<span>Registered Contractor</span>
|
||||
<span>Registered <br/> Contractor</span>
|
||||
</div>
|
||||
<article className="ab-leader right">
|
||||
<img src={`${IMG}${LEADERS[1].img}.png`} alt={LEADERS[1].name} />
|
||||
<div className="teamwrap"> <img src={`${IMG}${LEADERS[1].img}.png`} alt={LEADERS[1].name} /></div>
|
||||
<div className="ab-leader-info">
|
||||
<h3>{LEADERS[1].name}</h3>
|
||||
<span>{LEADERS[1].role}</span>
|
||||
<div className="leadcont"> <h3>{LEADERS[1].name}</h3>
|
||||
<span>{LEADERS[1].role}</span> </div>
|
||||
<a href={`mailto:${LEADERS[1].email}`}>{LEADERS[1].email}</a>
|
||||
</div>
|
||||
</article>
|
||||
@@ -145,9 +146,12 @@ export default function AboutPage() {
|
||||
{TEAM.map((m) => (
|
||||
<article key={m.name} className="ab-member">
|
||||
<div className="ab-member-photo"><img src={`${IMG}${m.img}.png`} alt={m.name} /></div>
|
||||
<h4>{m.name}</h4>
|
||||
<span>{m.role}</span>
|
||||
<div className="leadcontwrap">
|
||||
<div className="leadcont">
|
||||
<h4>{m.name}</h4>
|
||||
<span>{m.role}</span> </div>
|
||||
<a href={`mailto:${m.email}`}>{m.email}</a>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
@@ -157,7 +161,7 @@ export default function AboutPage() {
|
||||
{/* Experts */}
|
||||
<section className="section ab-experts">
|
||||
<div className="container">
|
||||
<div className="why-head" style={{ marginInline: "auto", textAlign: "center" }}>
|
||||
<div className="why-head" >
|
||||
<p className="eyebrow">Expert Team</p>
|
||||
<h2 className="section-title">Meet Our Experts</h2>
|
||||
<p className="lead">Award-winning architects, experienced engineers, and dedicated construction professionals committed to excellence</p>
|
||||
@@ -170,15 +174,18 @@ export default function AboutPage() {
|
||||
<h4>{e.t}</h4>
|
||||
<p>{e.d}</p>
|
||||
</div>
|
||||
<span className="ab-expert-icon">◈</span>
|
||||
<img className="ab-expert-icon" src={`${IMG}${e.icon}`} alt="" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="ab-badge-grid">
|
||||
{BADGES.map((b) => (
|
||||
<div key={b.t} className="ab-cert">
|
||||
<strong>{b.n}</strong>
|
||||
<span>{b.t}</span>
|
||||
<div>
|
||||
<strong>{b.n}</strong>
|
||||
<span>{b.t}</span>
|
||||
</div>
|
||||
<img className="ab-cert-icon" src={`${IMG}${b.icon}`} alt="" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -195,12 +202,19 @@ export default function AboutPage() {
|
||||
</div>
|
||||
<div className="ab-steps">
|
||||
{STEPS.map((s, i) => (
|
||||
<div key={s.n} className="ab-step">
|
||||
{i === 3 && <img className="ab-step-img" src={`${IMG}c376c66c882591b36ac351bdd4592497b8acd713.png`} alt="" />}
|
||||
<strong>{s.n}</strong>
|
||||
<h4>{s.t}</h4>
|
||||
<p>{s.d}</p>
|
||||
</div>
|
||||
<Fragment key={s.n}>
|
||||
{i === 5 && (
|
||||
<div className="ab-step ab-step-card">
|
||||
<img className="ab-step-cardimg" src={`${IMG}c376c66c882591b36ac351bdd4592497b8acd713.png`} alt="" />
|
||||
</div>
|
||||
)}
|
||||
<div className="ab-step">
|
||||
{i === 3 && <img className="ab-step-img" src={`${IMG}c376c66c882591b36ac351bdd4592497b8acd713.png`} alt="" />}
|
||||
<strong>{s.n}</strong>
|
||||
<h4>{s.t}</h4>
|
||||
<p>{s.d}</p>
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,188 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
const EMPTY = {
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
mobile: "",
|
||||
subject: "",
|
||||
message: "",
|
||||
};
|
||||
|
||||
export default function ContactForm() {
|
||||
const [form, setForm] = useState(EMPTY);
|
||||
const [status, setStatus] = useState<"idle" | "sending" | "error">("idle");
|
||||
const [error, setError] = useState("");
|
||||
const [done, setDone] = useState(false);
|
||||
|
||||
const update =
|
||||
(key: keyof typeof EMPTY) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
|
||||
setForm((f) => ({ ...f, [key]: e.target.value }));
|
||||
|
||||
async function onSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setStatus("sending");
|
||||
setError("");
|
||||
try {
|
||||
const res = await fetch("/api/contact", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(form),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
throw new Error(data.error || "Something went wrong. Please try again.");
|
||||
}
|
||||
setForm(EMPTY);
|
||||
setStatus("idle");
|
||||
setDone(true);
|
||||
} catch (err) {
|
||||
setStatus("error");
|
||||
setError(err instanceof Error ? err.message : "Something went wrong.");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<form className="ct-form" onSubmit={onSubmit}>
|
||||
<h2 className="section-title" style={{ fontSize: 40 }}>
|
||||
Let’s connect!
|
||||
</h2>
|
||||
<p className="ct-form-sub">
|
||||
Have questions or need assistance? <br /> We’re here to help!
|
||||
</p>
|
||||
<div className="ct-row">
|
||||
<div className="ct-field">
|
||||
<span className="ct-icon">
|
||||
<img src="/img/firstname.svg" alt="" />
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="First Name"
|
||||
value={form.firstName}
|
||||
onChange={update("firstName")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="ct-field">
|
||||
<span className="ct-icon">
|
||||
<img src="/img/firstname.svg" alt="" />
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Last Name"
|
||||
value={form.lastName}
|
||||
onChange={update("lastName")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ct-row">
|
||||
<div className="ct-field">
|
||||
<span className="ct-icon">
|
||||
<img src="/img/emailform.svg" alt="" />
|
||||
</span>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email Address"
|
||||
value={form.email}
|
||||
onChange={update("email")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="ct-field">
|
||||
<span className="ct-icon">
|
||||
<img src="/img/mobilenumber.svg" alt="" />
|
||||
</span>
|
||||
<input
|
||||
type="tel"
|
||||
placeholder="Mobile Number"
|
||||
value={form.mobile}
|
||||
onChange={update("mobile")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ct-field ct-field-full">
|
||||
<span className="ct-icon">
|
||||
<img src="/img/subject.svg" alt="" />
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Subjects"
|
||||
value={form.subject}
|
||||
onChange={update("subject")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ct-field ct-field-full ct-textarea-wrap">
|
||||
<span className="ct-icon" style={{ alignSelf: "flex-start", marginTop: "2px" }}>
|
||||
<img src="/img/message.svg" alt="" />
|
||||
</span>
|
||||
<textarea
|
||||
rows={5}
|
||||
placeholder="Write your message here..."
|
||||
value={form.message}
|
||||
onChange={update("message")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{status === "error" && <p className="ct-error">{error}</p>}
|
||||
|
||||
<div className="ct-submit-row">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-dark ct-submit"
|
||||
disabled={status === "sending"}
|
||||
>
|
||||
{status === "sending" ? "Sending…" : "Submit"}{" "}
|
||||
<span className="arrow">→</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{done && (
|
||||
<div
|
||||
className="ct-thanks-overlay"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
onClick={() => setDone(false)}
|
||||
>
|
||||
<div className="ct-thanks-card" onClick={(e) => e.stopPropagation()}>
|
||||
<button
|
||||
className="ct-thanks-close"
|
||||
aria-label="Close"
|
||||
onClick={() => setDone(false)}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<div className="ct-thanks-check">
|
||||
<svg viewBox="0 0 24 24" width="34" height="34" aria-hidden="true">
|
||||
<path
|
||||
d="M20 6 9 17l-5-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Thank you!</h3>
|
||||
<p>
|
||||
Your message has been received. Our team will get back to you
|
||||
shortly.
|
||||
</p>
|
||||
<button className="btn btn-dark" onClick={() => setDone(false)}>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import "./style.css";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import ScrollFX from "@/components/ScrollFX";
|
||||
import { LanguageProvider } from "@/components/i18n";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Yamuna Expressway Authority — Build Your Dream Home",
|
||||
@@ -18,10 +19,12 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
<ScrollFX />
|
||||
<LanguageProvider>
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
<ScrollFX />
|
||||
</LanguageProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
@@ -77,12 +77,12 @@ const COMPANIES = [
|
||||
];
|
||||
|
||||
const WHY = [
|
||||
{ t: "Transparent Pricing", d: "No hidden costs. Detailed BOQ with itemized pricing for every material and service." },
|
||||
{ t: "Contract-Based Construction", d: "Legal agreements protecting your investment with clear deliverables and timelines." },
|
||||
{ t: "Milestone Payments", d: "Pay only based on verified completion stages. Your money stays safe with escrow options." },
|
||||
{ t: "Dedicated Project Manager", d: "Single point of contact managing your entire project from conception to completion." },
|
||||
{ t: "Authority Support", d: "We handle all approvals, NOCs, and legal documentation with government authorities." },
|
||||
{ t: "Live Project Tracking", d: "Real-time updates via app with photos, videos, and milestone completion reports." },
|
||||
{ t: "Transparent Pricing", d: "No hidden costs. Detailed BOQ with itemized pricing for every material and service.", img: "631d471a32ee1dcc3f6e79f1d5eba77011cf4a50" },
|
||||
{ t: "Contract-Based Construction", d: "Legal agreements protecting your investment with clear deliverables and timelines.", img: "6529219216839b0700cdffee82ac9490aba78014" },
|
||||
{ t: "Milestone Payments", d: "Pay only based on verified completion stages. Your money stays safe with escrow options.", img: "b531d04468f7eb1eee6b5e3f79b73ed1b8d100ed" },
|
||||
{ t: "Dedicated Project Manager", d: "Single point of contact managing your entire project from conception to completion.", img: "5cbc2b713ab3173098792ba280d0f70b6b91b370" },
|
||||
{ t: "Authority Support", d: "We handle all approvals, NOCs, and legal documentation with government authorities.", img: "ca01253b7173e27cd1fc5ac3280f55a300f96bcc" },
|
||||
{ t: "Live Project Tracking", d: "Real-time updates via app with photos, videos, and milestone completion reports.", img: "888fe17730be5dc5dfc0f500a63c3ff6092ada9a" },
|
||||
];
|
||||
|
||||
const TESTIMONIALS = [
|
||||
@@ -101,6 +101,30 @@ function Stars({ n }: { n: number }) {
|
||||
);
|
||||
}
|
||||
|
||||
type Testimonial = (typeof TESTIMONIALS)[number];
|
||||
|
||||
function TestiQuote({ t, size }: { t: Testimonial; size: "large" | "medium" }) {
|
||||
return (
|
||||
<article className={`testi-card ${size}`}>
|
||||
<span className="quote-mark"><img src="img/testquote.svg" alt="" /></span>
|
||||
<Stars n={t.stars} />
|
||||
<p className="testi-quote">{t.quote}</p>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
function TestiAuthor({ t, top }: { t: Testimonial; top?: boolean }) {
|
||||
return (
|
||||
<div className={`testi-author-card ${top ? "top-author" : ""}`}>
|
||||
<img src={`${IMG}${t.avatar}.png`} alt={t.name} />
|
||||
<div>
|
||||
<strong>{t.name}</strong>
|
||||
<span>{t.role}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const [walk, setWalk] = useState(0);
|
||||
const [projTab, setProjTab] = useState<"current" | "upcoming">("current");
|
||||
@@ -110,6 +134,9 @@ export default function Home() {
|
||||
const [pinH, setPinH] = useState(0);
|
||||
const journeyRef = useRef<HTMLDivElement | null>(null);
|
||||
const [why, setWhy] = useState(0);
|
||||
const [whyPinned, setWhyPinned] = useState(false);
|
||||
const [whyPinH, setWhyPinH] = useState(0);
|
||||
const whyRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// Each "stop" = one scroll step. Walk every card, revealing its items one by one.
|
||||
const journeyStops = useMemo(() => {
|
||||
@@ -161,6 +188,47 @@ export default function Home() {
|
||||
};
|
||||
}, [journeyStops]);
|
||||
|
||||
// Pin the Why section; scroll advances the open row (+ its image) on the right side.
|
||||
useEffect(() => {
|
||||
const STEP_VH = 0.7; // viewport-heights of scroll per row
|
||||
const small = window.matchMedia("(max-width: 768px)");
|
||||
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
|
||||
const layout = () => {
|
||||
const on = !small.matches && !reduce.matches;
|
||||
setWhyPinned(on);
|
||||
setWhyPinH(on ? Math.round(WHY.length * STEP_VH * window.innerHeight + window.innerHeight) : 0);
|
||||
};
|
||||
|
||||
let raf = 0;
|
||||
const onScroll = () => {
|
||||
if (raf) return;
|
||||
raf = requestAnimationFrame(() => {
|
||||
raf = 0;
|
||||
const el = whyRef.current;
|
||||
if (!el || small.matches || reduce.matches) return;
|
||||
const total = el.offsetHeight - window.innerHeight;
|
||||
const scrolled = Math.min(Math.max(-el.getBoundingClientRect().top, 0), total);
|
||||
const p = total > 0 ? scrolled / total : 0;
|
||||
setWhy(Math.min(WHY.length - 1, Math.max(0, Math.floor(p * WHY.length))));
|
||||
});
|
||||
};
|
||||
|
||||
layout();
|
||||
onScroll();
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
window.addEventListener("resize", layout);
|
||||
small.addEventListener("change", layout);
|
||||
reduce.addEventListener("change", layout);
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
window.removeEventListener("resize", layout);
|
||||
small.removeEventListener("change", layout);
|
||||
reduce.removeEventListener("change", layout);
|
||||
if (raf) cancelAnimationFrame(raf);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* ============================ HERO ============================ */}
|
||||
@@ -187,7 +255,7 @@ export default function Home() {
|
||||
<br />a single accountable team for your YEIDA villa.
|
||||
</p>
|
||||
<Link href="/contact" className="btn btn-light hero-cta">
|
||||
Free Consultation <span className="arrow">→</span>
|
||||
Free Consultation <span className="arrow"><img src="/img/arrowblack.svg" alt="" /></span>
|
||||
</Link>
|
||||
</div>
|
||||
<span className="hero-note">Yamuna Expressway<br />is the new NCR.</span>
|
||||
@@ -244,7 +312,7 @@ export default function Home() {
|
||||
<div className="container plots-pager">
|
||||
<span className="pager-count">01 / 09</span>
|
||||
<div className="pager-line"><span /></div>
|
||||
<div className="pager-btns"><button>←</button><button>→</button></div>
|
||||
<div className="pager-btns"><button className="pager-prev"><img src="/img/arrowchoosecanvas.svg" alt="Previous" /></button><button><img src="/img/arrowchoosecanvas.svg" alt="Next" /></button></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -416,7 +484,7 @@ export default function Home() {
|
||||
</div>
|
||||
|
||||
{/* Bottom Right Small Image */}
|
||||
<div className="journey-thumb"></div>
|
||||
<div className="journey-thumb"><img src={`${IMG}${j.img}.png`} alt={j.title} /></div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -435,7 +503,7 @@ export default function Home() {
|
||||
</div>
|
||||
<div className="virtual-stage">
|
||||
<img src={`${IMG}f470f21a29fb7fe287ce1d5ffb94d0b4845b5367.png`} alt="500 sqm Contemporary Villa" />
|
||||
<div className="virtual-overlay" />
|
||||
|
||||
<button className="play-btn" aria-label="Play"><span>▶</span></button>
|
||||
<div className="virtual-caption">
|
||||
<h3>500 sqm Contemporary Villa</h3>
|
||||
@@ -443,7 +511,7 @@ export default function Home() {
|
||||
</div>
|
||||
<div className="virtual-features">
|
||||
{WALK_FEATURES.map((f) => (
|
||||
<span key={f}>● {f}</span>
|
||||
<span key={f}><img src="img/tick.svg" alt="" /> {f}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -480,27 +548,39 @@ export default function Home() {
|
||||
|
||||
{/* ======================= WHY CHOOSE US ====================== */}
|
||||
<section className="section why">
|
||||
<div className="container">
|
||||
<div className="why-head">
|
||||
<p className="eyebrow">Excellence Guaranteed</p>
|
||||
<h2 className="section-title">Why Choose Us</h2>
|
||||
<p className="lead">Industry-leading standards combined with personalized service for an unmatched construction experience</p>
|
||||
</div>
|
||||
<div className="why-grid">
|
||||
<div className="why-visual">
|
||||
<img className="why-tall" src={`${IMG}1cf7daea2239eeca5e11497628834804a0b5ec46.png`} alt="" />
|
||||
</div>
|
||||
<div className="why-list">
|
||||
<img className="why-wide" src={`${IMG}888fe17730be5dc5dfc0f500a63c3ff6092ada9a.png`} alt="" />
|
||||
{WHY.map((w, i) => (
|
||||
<div key={w.t} className={`why-row ${i === why ? "open" : ""}`} onClick={() => setWhy(i)}>
|
||||
<span className="why-no">{String(i + 1).padStart(2, "0")}</span>
|
||||
<div className="why-text">
|
||||
<h4>{w.t}</h4>
|
||||
{i === why && <p>{w.d}</p>}
|
||||
</div>
|
||||
<div
|
||||
ref={whyRef}
|
||||
className={`why-pin ${whyPinned ? "is-pinned" : ""}`}
|
||||
style={whyPinned && whyPinH ? { height: whyPinH } : undefined}
|
||||
>
|
||||
<div className="why-sticky">
|
||||
<div className="container">
|
||||
<div className="why-head">
|
||||
<p className="eyebrow">Excellence Guaranteed</p>
|
||||
<h2 className="section-title">Why Choose Us</h2>
|
||||
<p className="lead">Industry-leading standards combined with personalized service for an unmatched construction experience</p>
|
||||
</div>
|
||||
<div className="why-grid">
|
||||
<div className="why-visual">
|
||||
<img className="why-tall" src={`${IMG}1cf7daea2239eeca5e11497628834804a0b5ec46.png`} alt="" />
|
||||
</div>
|
||||
))}
|
||||
<div className="why-list">
|
||||
{WHY.map((w, i) => (
|
||||
<div key={w.t} className={`why-row ${i === why ? "open" : ""}`} onClick={() => setWhy(i)}>
|
||||
<div className="why-media">
|
||||
<img className="why-wide" src={`${IMG}${w.img}.png`} alt="" />
|
||||
</div>
|
||||
<div className="why-row-head">
|
||||
<span className="why-no">{String(i + 1).padStart(2, "0")}</span>
|
||||
<div className="why-text">
|
||||
<h4>{w.t}</h4>
|
||||
<p className="why-desc">{w.d}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -512,15 +592,15 @@ export default function Home() {
|
||||
<div className="showcase-overlay" />
|
||||
<span className="showcase-brand">HOUSECOIN</span>
|
||||
<div className="showcase-card">
|
||||
<img src={`${IMG}dac66fb1980459d72c43173854d68cab518070cd.png`} alt="The PIQUE of Home Design" />
|
||||
<div className="showcase-bar">
|
||||
<span className="sc-menu">☰ MENU</span>
|
||||
<span className="sc-logo">PIQUE</span>
|
||||
<span className="sc-contact">✦ CONTACT</span>
|
||||
</div>
|
||||
<div className="showcase-caption">
|
||||
<h3>The PIQUE of<br />Home Design</h3>
|
||||
</div>
|
||||
<video
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
poster={`${IMG}dac66fb1980459d72c43173854d68cab518070cd.png`}
|
||||
>
|
||||
<source src={`${IMG}PIQUE.mp4`} type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -535,20 +615,21 @@ export default function Home() {
|
||||
<p className="lead">Every owner here is reachable for a reference call. Every plot is registered to verifiable YEIDA records.</p>
|
||||
</div>
|
||||
<div className="testi-grid">
|
||||
{TESTIMONIALS.map((t) => (
|
||||
<article key={t.name} className="testi-card">
|
||||
<span className="quote-mark">“</span>
|
||||
<Stars n={t.stars} />
|
||||
<p className="testi-quote">{t.quote}</p>
|
||||
<div className="testi-author">
|
||||
<img src={`${IMG}${t.avatar}.png`} alt={t.name} />
|
||||
<div>
|
||||
<strong>{t.name}</strong>
|
||||
<span>{t.role}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
{/* LEFT */}
|
||||
<div className="testi-col">
|
||||
<TestiQuote t={TESTIMONIALS[0]} size="large" />
|
||||
<TestiAuthor t={TESTIMONIALS[0]} />
|
||||
</div>
|
||||
{/* CENTER */}
|
||||
<div className="testi-col">
|
||||
<TestiAuthor t={TESTIMONIALS[1]} top />
|
||||
<TestiQuote t={TESTIMONIALS[1]} size="medium" />
|
||||
</div>
|
||||
{/* RIGHT */}
|
||||
<div className="testi-col">
|
||||
<TestiQuote t={TESTIMONIALS[2]} size="large" />
|
||||
<TestiAuthor t={TESTIMONIALS[2]} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="testi-more"><Link href="/blog">View all Stories</Link></div>
|
||||
</div>
|
||||
@@ -26,10 +26,10 @@ const VILLAS = [
|
||||
];
|
||||
|
||||
const ACROSS = [
|
||||
{ n: "12", t: "Uttar Pradesh", icon: "🏛" },
|
||||
{ n: "5", t: "Uttarakhand", icon: "⛰" },
|
||||
{ n: "8", t: "Delhi NCR", icon: "🏙" },
|
||||
{ n: "6", t: "Haryana", icon: "🌾" },
|
||||
{ n: "12", t: "Uttar Pradesh" },
|
||||
{ n: "5", t: "Uttarakhand" },
|
||||
{ n: "8", t: "Delhi NCR" },
|
||||
{ n: "6", t: "Haryana" },
|
||||
];
|
||||
|
||||
const COMPANIES = [
|
||||
@@ -43,10 +43,10 @@ export default function ProjectsPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="pr-hero">
|
||||
<section className="faq-hero">
|
||||
<img src={`${IMG}34f8cb9b04c0ce5ee0362f88dd763769431d82d8.png`} alt="" className="pr-hero-bg" />
|
||||
<div className="pr-hero-overlay" />
|
||||
<div className="container pr-hero-inner">
|
||||
<div className="faq-hero-overlay" />
|
||||
<div className="container faq-hero-inner">
|
||||
<h1>Our Projects</h1>
|
||||
<p>Showcasing the landmarks, infrastructure, and developments we have successfully delivered with quality, precision, and excellence.</p>
|
||||
</div>
|
||||
@@ -129,12 +129,12 @@ export default function ProjectsPage() {
|
||||
<article key={v.name} className="villa-card">
|
||||
<div className="villa-media">
|
||||
<img src={`${IMG}${v.img}.png`} alt={v.name} />
|
||||
<span className="villa-tags"><span>{v.bhk}</span><span>{v.sqm}</span></span>
|
||||
<span className="villa-floor">{v.floor}</span>
|
||||
<span className="villa-tags"><span>{v.bhk}</span> <span className="villa-floor">{v.floor}</span><span>{v.sqm}</span></span>
|
||||
|
||||
</div>
|
||||
<div className="villa-body">
|
||||
<h3>{v.name}</h3>
|
||||
<p className="villa-desc">{v.desc}</p>
|
||||
<span> <h3>{v.name}</h3>
|
||||
<p className="villa-desc">{v.desc}</p></span>
|
||||
<div className="villa-foot">
|
||||
<div className="villa-price"><small>FROM</small>{v.price}</div>
|
||||
<span className="villa-months">{v.months}</span>
|
||||
@@ -166,7 +166,9 @@ export default function ProjectsPage() {
|
||||
<span>Projects</span>
|
||||
<h4>{a.t}</h4>
|
||||
</div>
|
||||
<span className="pr-across-icon">{a.icon}</span>
|
||||
<span className="pr-across-icon">
|
||||
<img src={`${IMG}${encodeURIComponent(a.t)}.png`} alt={a.t} />
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
type ContactBody = {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
mobile?: string;
|
||||
subject?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const token = process.env.TEABLE_API_TOKEN;
|
||||
const tableId = process.env.TEABLE_TABLE_ID;
|
||||
|
||||
if (!token || !tableId) {
|
||||
return NextResponse.json(
|
||||
{ error: "Server is not configured for submissions." },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
let body: ContactBody;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid request body." }, { status: 400 });
|
||||
}
|
||||
|
||||
const firstName = (body.firstName ?? "").trim();
|
||||
const lastName = (body.lastName ?? "").trim();
|
||||
const email = (body.email ?? "").trim();
|
||||
const mobile = (body.mobile ?? "").trim();
|
||||
const subject = (body.subject ?? "").trim();
|
||||
const message = (body.message ?? "").trim();
|
||||
|
||||
const name = `${firstName} ${lastName}`.trim();
|
||||
|
||||
if (!name || !email || !message) {
|
||||
return NextResponse.json(
|
||||
{ error: "Name, email and message are required." },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// The Teable table is job-application shaped; map contact details onto the
|
||||
// free-text fields that fit. Subject + mobile are folded into why_role so no
|
||||
// information is lost.
|
||||
const why_role = [
|
||||
subject && `Subject: ${subject}`,
|
||||
mobile && `Mobile: ${mobile}`,
|
||||
message,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
|
||||
const res = await fetch(
|
||||
`https://app.teable.ai/api/table/${tableId}/record`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
fieldKeyType: "name",
|
||||
records: [{ fields: { name, email, why_role } }],
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const detail = await res.text();
|
||||
console.error("Teable error:", res.status, detail);
|
||||
return NextResponse.json(
|
||||
{ error: "Could not save your message. Please try again." },
|
||||
{ status: 502 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export default function ContactForm() {
|
||||
return (
|
||||
<form className="ct-form" onSubmit={(e) => e.preventDefault()}>
|
||||
<h2 className="section-title" style={{ fontSize: 40 }}>Let’s connect!</h2>
|
||||
<p className="ct-form-sub">Have questions or need assistance? <br /> We’re here to help!</p>
|
||||
<div className="ct-row">
|
||||
<div className="ct-field">
|
||||
<span className="ct-icon">
|
||||
<img src="/img/firstname.svg" alt="" />
|
||||
</span>
|
||||
<input type="text" placeholder="First Name" />
|
||||
</div>
|
||||
<div className="ct-field">
|
||||
<span className="ct-icon">
|
||||
<img src="/img/firstname.svg" alt="" />
|
||||
</span>
|
||||
<input type="text" placeholder="Last Name" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ct-row">
|
||||
<div className="ct-field">
|
||||
<span className="ct-icon">
|
||||
<img src="/img/emailform.svg" alt="" />
|
||||
</span>
|
||||
<input type="email" placeholder="Email Address" />
|
||||
</div>
|
||||
<div className="ct-field">
|
||||
<span className="ct-icon">
|
||||
<img src="/img/mobilenumber.svg" alt="" />
|
||||
</span>
|
||||
<input type="tel" placeholder="Mobile Number" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ct-field ct-field-full">
|
||||
<span className="ct-icon">
|
||||
<img src="/img/subject.svg" alt="" />
|
||||
</span>
|
||||
<input type="text" placeholder="Subjects" />
|
||||
</div>
|
||||
|
||||
<div className="ct-field ct-field-full ct-textarea-wrap">
|
||||
<span className="ct-icon" style={{ alignSelf: "flex-start", marginTop: "2px" }}>
|
||||
<img src="/img/message.svg" alt="" />
|
||||
</span>
|
||||
<textarea rows={5} placeholder="Write your message here..." />
|
||||
</div>
|
||||
|
||||
<div className="ct-submit-row">
|
||||
<button type="submit" className="btn btn-dark ct-submit">Submit <span className="arrow">→</span></button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
-1424
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user