feat(seo): coming-soon industry pages and round-2 title fixes
CI/CD / test-and-build (pull_request) Canceled after 0s
CI/CD / deploy (pull_request) Canceled after 0s

- /industries/events, /smart-buildings and /government render a short
  coming-soon page with their own title, description and canonical, and
  are listed in the sitemap and on /industries.
- Homepage title: "Robot Shop and Configurator in the UAE", set as
  absolute so the template does not add the brand twice.
- /configure title: "Configure Your Robot and Request a UAE Quotation".
- Card and hero alt/aria text use displayName() (no "Pudu PUDU").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Najjar\NajjarV02
2026-10-19 13:03:48 +04:00
co-authored by Claude Opus 5.5
parent 38cc2025de
commit bcd7bd2c7b
10 changed files with 227 additions and 27 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ import { pageMetadata } from '@/lib/seo';
// The page itself is a client component and cannot export metadata.
export const metadata: Metadata = pageMetadata({
title: 'Unitree G1 3D Configurator',
title: 'Configure Your Robot and Request a UAE Quotation',
description:
'Build your Unitree G1 in 3D. Choose persona, attire, colours and accessories, preview it live, then request a UAE quotation from YS Lootah Robotics.',
path: '/configure/',
+171 -8
View File
@@ -24,9 +24,10 @@ import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact';
import { MotionSection } from '@/components/ui/MotionSection';
import { CTAButton } from '@/components/ui/CTAButton';
import { INDUSTRIES, getIndustryBySlug } from '@/data/industries';
import { INDUSTRIES, UPCOMING_INDUSTRIES, getIndustryBySlug, getUpcomingIndustryBySlug, type Industry } from '@/data/industries';
import { ROBOTS } from '@/data/robots';
import { INDUSTRY_PAGES, type IndustrySolution } from '@/data/industry-pages';
import { pageMetadata } from '@/lib/seo';
import { displayName, pageMetadata } from '@/lib/seo';
type Params = { slug: string };
@@ -50,11 +51,21 @@ const ICONS: Record<IndustrySolution['iconKey'], React.ComponentType<{ size?: nu
export const dynamicParams = false;
export function generateStaticParams() {
return INDUSTRIES.map((i) => ({ slug: i.slug }));
return [...INDUSTRIES, ...UPCOMING_INDUSTRIES].map((i) => ({ slug: i.slug }));
}
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
const { slug } = await params;
const soon = getUpcomingIndustryBySlug(slug);
if (soon?.upcoming) {
return pageMetadata({
title: soon.upcoming.seoTitle,
description: soon.upcoming.seoDescription,
path: `/industries/${soon.slug}`,
image: soon.image,
imageAlt: soon.imageAlt,
});
}
const industry = getIndustryBySlug(slug);
if (!industry) return {};
const content = INDUSTRY_PAGES[industry.id];
@@ -77,6 +88,8 @@ const TRUST_DEFAULT = [
export default async function IndustryDetailPage({ params }: { params: Promise<Params> }) {
const { slug } = await params;
const upcoming = getUpcomingIndustryBySlug(slug);
if (upcoming) return <ComingSoonIndustry industry={upcoming} />;
const industry = getIndustryBySlug(slug);
if (!industry) notFound();
const content = INDUSTRY_PAGES[industry.id];
@@ -441,7 +454,160 @@ export default async function IndustryDetailPage({ params }: { params: Promise<P
<FooterAndContact />
<style>{`
<style>{IP_STYLES}</style>
</>
);
}
// Short page for a sector whose full content is not written yet. Same layout
// and styles as the full page, built from the card copy and matching robots.
function ComingSoonIndustry({ industry }: { industry: Industry }) {
const accent = industry.accent;
const robots = ROBOTS.filter((r) => r.categories.some((c) => industry.matchCategories.includes(c))).slice(0, 6);
return (
<>
<Navbar />
<main className="ip-main">
<div className="container-wide ip-stack">
<nav aria-label="Breadcrumb" className="ip-breadcrumbs">
<ol>
<li><Link href="/">Home</Link></li>
<li aria-hidden><ChevronRight size={12} /></li>
<li><Link href="/industries/">Industries</Link></li>
<li aria-hidden><ChevronRight size={12} /></li>
<li aria-current="page">{industry.name}</li>
</ol>
</nav>
<MotionSection>
<section className="ip-hero" style={{ ['--acc' as string]: accent }}>
<div className="ip-hero-glow" aria-hidden />
<div className="ip-hero-glow-b" aria-hidden />
<div className="ip-hero-grid" aria-hidden />
<div className="ip-hero-inner">
<div className="ip-hero-copy">
<span className="eyebrow ip-hero-eyebrow">
<span className="ip-dot" />
Industry Solution · Coming soon
</span>
<h1 className="ip-hero-title">
<span className="text-gradient">Robots for {industry.name}</span>
</h1>
<p className="ip-hero-sub">
{industry.solution} A full guide for this sector is coming soon. Our Dubai team can already
advise on robots and arrange a demo.
</p>
<div className="ip-hero-actions">
<CTAButton href="/book-demo/" variant="primary" size="lg" arrow="up-right">
Book a Demo
</CTAButton>
<CTAButton href="/contact/" variant="secondary" size="lg" arrow="right">
Talk to Our Team
</CTAButton>
</div>
</div>
<aside className="ip-hero-visual">
<Image
src={industry.image}
alt={industry.imageAlt}
fill
sizes="(max-width: 920px) 100vw, 520px"
className="ip-hero-visual-img"
style={{ objectFit: 'cover', objectPosition: 'center' }}
priority
/>
<span className="ip-hero-visual-overlay" aria-hidden />
<div className="ip-hero-visual-meta">
<span className="ip-mono ip-live">
<span className="ip-live-dot" />Coming soon · UAE
</span>
<span className="ip-mono">Dubai Showroom · Live Demo</span>
</div>
</aside>
</div>
</section>
</MotionSection>
<MotionSection>
<section className="ip-block">
<header className="ip-block-head">
<span className="eyebrow">At a glance</span>
<h2 className="ip-block-title">What robots change for {industry.name.toLowerCase()}.</h2>
</header>
<ul className="ip-problems" role="list">
{[industry.problem, industry.solution, industry.benefit].map((p, i) => (
<li key={i} className="ip-problem">
<span className="ip-problem-num">{String(i + 1).padStart(2, '0')}</span>
<p>{p}</p>
</li>
))}
</ul>
</section>
</MotionSection>
{robots.length > 0 && (
<MotionSection>
<section className="ip-block" style={{ ['--acc' as string]: accent }}>
<header className="ip-block-head">
<span className="eyebrow">Suggested robots</span>
<h2 className="ip-block-title">Robots we would start with.</h2>
</header>
<div className="ip-robots">
{robots.map((r) => (
<article key={r.slug} className="ip-robot">
<div className="ip-robot-head">
<span className="ip-robot-brand">{r.brandLabel}</span>
<span className="ip-robot-arrow"><ArrowRight size={14} strokeWidth={1.8} /></span>
</div>
<h3>{displayName(r.brandLabel, r.name)}</h3>
<p>{r.tagline}</p>
<Link href={`/robots/${r.slug}/`} className="ip-robot-link">
View robot <ArrowRight size={13} strokeWidth={2} />
</Link>
</article>
))}
</div>
</section>
</MotionSection>
)}
<MotionSection>
<section className="ip-cta">
<div className="ip-cta-glow" aria-hidden />
<div className="ip-cta-grid" aria-hidden />
<div className="ip-cta-inner">
<div className="ip-cta-text">
<span className="eyebrow">Next step</span>
<h2 className="ip-cta-title">Planning robots for {industry.name.toLowerCase()}?</h2>
<p>Tell us about your venue and goals. Our Dubai team will recommend robots and arrange a live demo or quotation.</p>
</div>
<div className="ip-cta-actions">
<CTAButton href="/book-demo/" variant="primary" size="lg" arrow="up-right">
Book a Live Demo
</CTAButton>
<CTAButton href="/contact/" variant="secondary" size="lg" arrow="right">
Request UAE Quotation
</CTAButton>
</div>
</div>
</section>
</MotionSection>
</div>
</main>
<FooterAndContact />
<style>{IP_STYLES}</style>
</>
);
}
const IP_STYLES = `
.ip-main {
padding-top: clamp(5rem, 9vw, 7rem);
padding-bottom: clamp(2rem, 5vw, 4rem);
@@ -1098,7 +1264,4 @@ export default async function IndustryDetailPage({ params }: { params: Promise<P
.ip-cta-actions .cta-btn { width: 100%; justify-content: space-between; }
.ip-hero-actions .cta-btn { width: 100%; justify-content: space-between; }
}
`}</style>
</>
);
}
`;
+9 -4
View File
@@ -16,11 +16,17 @@ import { FounderSection } from '@/components/robotics/FounderSection';
import { BuSunaidahSection } from '@/components/robotics/BuSunaidahSection';
import { MotionSection } from '@/components/ui/MotionSection';
import { FEATURED_ROBOTS } from '@/data/robots';
import { pageMetadata } from '@/lib/seo';
// `absolute` keeps the layout template from adding the brand a second time.
export const metadata: Metadata = {
title: 'YS Lootah Robotics UAE Destination for Unitree & Pudu Robotics',
description:
'YS Lootah Robotics is the UAE sales destination for selected Unitree and Pudu Robotics solutions. Explore, configure, book demos, and deploy advanced robots across Dubai and the UAE.',
...pageMetadata({
title: 'Robot Shop and Configurator in the UAE',
description:
'YS Lootah Robotics is the UAE sales destination for selected Unitree and Pudu Robotics solutions. Explore, configure, book demos, and deploy advanced robots across Dubai and the UAE.',
path: '/',
}),
title: { absolute: 'Robot Shop and Configurator in the UAE | YS Lootah Robotics' },
keywords: [
'YS Lootah Robotics',
'robotics Dubai',
@@ -35,7 +41,6 @@ export const metadata: Metadata = {
'robotics configuration UAE',
'robot sales and demo UAE',
],
alternates: { canonical: '/' },
};
export default function HomePage() {
+2 -2
View File
@@ -1,6 +1,6 @@
import { MetadataRoute } from 'next';
import { ROBOTS } from '@/data/robots';
import { INDUSTRIES } from '@/data/industries';
import { INDUSTRIES, UPCOMING_INDUSTRIES } from '@/data/industries';
import { ACCESSORIES } from '@/data/accessories';
import { SITE_URL } from '@/lib/seo';
@@ -30,7 +30,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
priority: 0.7,
}));
const industryRoutes: MetadataRoute.Sitemap = INDUSTRIES.map((i) => ({
const industryRoutes: MetadataRoute.Sitemap = [...INDUSTRIES, ...UPCOMING_INDUSTRIES].map((i) => ({
url: u(`/industries/${i.slug}/`),
lastModified: now,
changeFrequency: 'monthly',