From bcd7bd2c7b304b8a4c9eb3aa1bc10d72401f10b4 Mon Sep 17 00:00:00 2001 From: "Najjar\\NajjarV02" Date: Mon, 19 Oct 2026 13:03:48 +0400 Subject: [PATCH] feat(seo): coming-soon industry pages and round-2 title fixes - /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 --- src/app/configure/layout.tsx | 2 +- src/app/industries/[slug]/page.tsx | 179 +++++++++++++++++- src/app/page.tsx | 13 +- src/app/sitemap.ts | 4 +- .../robotics/AccessoriesShowcase.tsx | 5 +- src/components/robotics/Hero3DRobotics.tsx | 3 +- src/components/robotics/IndustryUseCases.tsx | 6 +- src/components/robotics/RobotProductCard.tsx | 5 +- src/data/industries.ts | 27 ++- src/lib/seo.test.ts | 10 +- 10 files changed, 227 insertions(+), 27 deletions(-) diff --git a/src/app/configure/layout.tsx b/src/app/configure/layout.tsx index 1a12641..7ddac19 100644 --- a/src/app/configure/layout.tsx +++ b/src/app/configure/layout.tsx @@ -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/', diff --git a/src/app/industries/[slug]/page.tsx b/src/app/industries/[slug]/page.tsx index f469aa3..d306d49 100644 --- a/src/app/industries/[slug]/page.tsx +++ b/src/app/industries/[slug]/page.tsx @@ -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 ({ slug: i.slug })); + return [...INDUSTRIES, ...UPCOMING_INDUSTRIES].map((i) => ({ slug: i.slug })); } export async function generateMetadata({ params }: { params: Promise }): Promise { 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 }) { const { slug } = await params; + const upcoming = getUpcomingIndustryBySlug(slug); + if (upcoming) return ; 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

- + + ); +} + +// 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 ( + <> + + +

+
+ + + +
+
+
+
+ +
+
+ + + Industry Solution · Coming soon + +

+ Robots for {industry.name} +

+

+ {industry.solution} A full guide for this sector is coming soon. Our Dubai team can already + advise on robots and arrange a demo. +

+ +
+ + Book a Demo + + + Talk to Our Team + +
+
+ + +
+
+
+ + +
+
+ At a glance +

What robots change for {industry.name.toLowerCase()}.

+
+
    + {[industry.problem, industry.solution, industry.benefit].map((p, i) => ( +
  • + {String(i + 1).padStart(2, '0')} +

    {p}

    +
  • + ))} +
+
+
+ + {robots.length > 0 && ( + +
+
+ Suggested robots +

Robots we would start with.

+
+
+ {robots.map((r) => ( +
+
+ {r.brandLabel} + +
+

{displayName(r.brandLabel, r.name)}

+

{r.tagline}

+ + View robot + +
+ ))} +
+
+
+ )} + + +
+
+
+
+
+ Next step +

Planning robots for {industry.name.toLowerCase()}?

+

Tell us about your venue and goals. Our Dubai team will recommend robots and arrange a live demo or quotation.

+
+
+ + Book a Live Demo + + + Request UAE Quotation + +
+
+
+
+
+
+ + + + + + ); +} + +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

- - ); -} +`; diff --git a/src/app/page.tsx b/src/app/page.tsx index 07b5985..e0824a5 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -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() { diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index fbd2782..7f75f9d 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -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', diff --git a/src/components/robotics/AccessoriesShowcase.tsx b/src/components/robotics/AccessoriesShowcase.tsx index faf4a44..151558e 100644 --- a/src/components/robotics/AccessoriesShowcase.tsx +++ b/src/components/robotics/AccessoriesShowcase.tsx @@ -4,6 +4,7 @@ import { useMemo, useState } from 'react'; import Image from 'next/image'; import Link from 'next/link'; import { ACCESSORIES, GROUP_LABELS, type AccessoryGroup, type Accessory } from '@/data/accessories'; +import { displayName } from '@/lib/seo'; type Filter = AccessoryGroup | 'all'; @@ -97,7 +98,7 @@ function AccessoryCard({ accessory }: { accessory: Accessory }) { > {`${accessory.brandLabel} {`${r.brandLabel} @@ -232,7 +232,7 @@ function IndustryCard({ industry, delay }: { industry: Industry; delay: number }

{industry.name}

{blurb}

- Learn more + {industry.upcoming ? 'Coming soon' : 'Learn more'} diff --git a/src/components/robotics/RobotProductCard.tsx b/src/components/robotics/RobotProductCard.tsx index 056a33a..0f251eb 100644 --- a/src/components/robotics/RobotProductCard.tsx +++ b/src/components/robotics/RobotProductCard.tsx @@ -3,6 +3,7 @@ import Link from 'next/link'; import Image from 'next/image'; import { Robot, CATEGORY_LABELS } from '@/data/robots'; +import { displayName } from '@/lib/seo'; type Props = { robot: Robot; @@ -24,7 +25,7 @@ export function RobotProductCard({ robot, priority = false }: Props) { > {`${robot.brandLabel} i.slug === slug); } + +export function getUpcomingIndustryBySlug(slug: string): Industry | undefined { + return UPCOMING_INDUSTRIES.find((i) => i.slug === slug); +} diff --git a/src/lib/seo.test.ts b/src/lib/seo.test.ts index 8639271..8f6ba90 100644 --- a/src/lib/seo.test.ts +++ b/src/lib/seo.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { ROBOTS } from '@/data/robots'; import { ACCESSORIES } from '@/data/accessories'; -import { INDUSTRIES } from '@/data/industries'; +import { INDUSTRIES, UPCOMING_INDUSTRIES } from '@/data/industries'; import { INDUSTRY_PAGES } from '@/data/industry-pages'; import { canonical, displayName, pageMetadata, siteName } from './seo'; @@ -55,4 +55,12 @@ describe('industries', () => { expect(page.seoTitle).not.toContain(siteName); } }); + + it('gives every upcoming sector its own coming-soon title', () => { + for (const industry of UPCOMING_INDUSTRIES) { + expect(industry.upcoming?.seoTitle, industry.slug).toBeTruthy(); + expect(industry.upcoming?.seoTitle).not.toContain(siteName); + expect(INDUSTRIES.some((i) => i.slug === industry.slug), industry.slug).toBe(false); + } + }); });