7 Commits
Author SHA1 Message Date
Najjar\NajjarV02andClaude Opus 5.5 60d39e857a chore(contact): update office phone to +971 4 289 6095
CI/CD / test-and-build (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:35:36 +04:00
mohammad 78aec0e08d Merge pull request 'fix(checkout): skip loadStripe when no publishable key is set' (#3) from seo-round-2 into main
CI/CD / test-and-build (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s
Reviewed-on: #3
2026-10-19 13:11:41 +00:00
Najjar\NajjarV02andClaude Opus 5.5 81358b0815 fix(checkout): skip loadStripe when no publishable key is set
CI/CD / test-and-build (pull_request) Canceled after 0s
CI/CD / deploy (pull_request) Canceled after 0s
loadStripe(undefined) threw "Expected publishable key to be of type
string" on every page that imports CheckoutOverlay. Production sets the
key, so live behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:18:46 +04:00
mohammad 1d9d620d67 Merge pull request 'feat(seo): coming-soon industry pages and round-2 title fixes' (#2) from seo-round-2 into main
CI/CD / test-and-build (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s
Reviewed-on: #2
2026-10-19 09:52:53 +00:00
Najjar\NajjarV02andClaude Opus 5.5 bcd7bd2c7b 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>
2026-10-19 13:03:48 +04:00
mohammad 38cc2025de Merge pull request 'feat(seo): fix titles, canonicals, share tags and soft 404s' (#1) from seo-batch into main
CI/CD / test-and-build (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s
Reviewed-on: #1
2026-10-14 11:15:30 +00:00
Najjar\NajjarV02andClaude Opus 5.5 8382501598 feat(seo): fix titles, canonicals, share tags and soft 404s
CI/CD / test-and-build (pull_request) Canceled after 0s
CI/CD / deploy (pull_request) Canceled after 0s
- Titles carried the brand twice (page title plus layout template) and Pudu
  items read "Pudu PUDU". Pages now go through pageMetadata() and displayName().
- /configure, /privacy-policy and /terms-of-service reused the homepage title.
- Eight pages had no canonical and inherited the homepage og:url.
- Unknown /robots, /accessories and /industries slugs returned 200; they are
  now a real 404 (dynamicParams = false) with a not-found page.
- Events, Smart Buildings and Government have no page content yet and rendered
  not-found: moved to UPCOMING_INDUSTRIES until it is written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:57:18 +04:00
28 changed files with 490 additions and 99 deletions
+1 -1
View File
@@ -29,6 +29,6 @@ Hospitality, restaurants and cafes, hotels and resorts, shopping malls, healthca
## Contact ## Contact
- Email: info@yslootahrobotics.com - Email: info@yslootahrobotics.com
- Phone: +971 55 948 2728, +971 4 349 9319 - Phone: +971 55 948 2728, +971 4 289 6095
- WhatsApp: https://wa.me/971559482728 - WhatsApp: https://wa.me/971559482728
- Location: Office 408, City Bay Business Center, Dubai, United Arab Emirates - Location: Office 408, City Bay Business Center, Dubai, United Arab Emirates
+5 -3
View File
@@ -7,12 +7,14 @@ import { WhyUs } from '@/components/robotics/WhyUs';
import { FloatingTechPanel } from '@/components/robotics/FloatingTechPanel'; import { FloatingTechPanel } from '@/components/robotics/FloatingTechPanel';
import { CompanyStory } from '@/components/robotics/CompanyStory'; import { CompanyStory } from '@/components/robotics/CompanyStory';
import { FounderSection } from '@/components/robotics/FounderSection'; import { FounderSection } from '@/components/robotics/FounderSection';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'About YS Lootah Robotics UAE Destination for Unitree & Pudu Robotics', title: 'About Us · UAE Destination for Unitree & Pudu Robotics',
description: description:
'YS Lootah Robotics is part of the Yousuf Saeed Lootah Investment Group a trusted UAE robotics partner delivering AI, automation, and intelligent robotics across Dubai and the UAE.', 'YS Lootah Robotics is part of the Yousuf Saeed Lootah Investment Group a trusted UAE robotics partner delivering AI, automation, and intelligent robotics across Dubai and the UAE.',
}; path: '/about/',
});
export default function AboutPage() { export default function AboutPage() {
return ( return (
+10 -11
View File
@@ -10,9 +10,14 @@ import { FooterAndContact } from '@/components/FooterAndContact';
import { MotionSection } from '@/components/ui/MotionSection'; import { MotionSection } from '@/components/ui/MotionSection';
import { CTAButton } from '@/components/ui/CTAButton'; import { CTAButton } from '@/components/ui/CTAButton';
import { ACCESSORIES, GROUP_LABELS, getAccessoryBySlug, type Accessory } from '@/data/accessories'; import { ACCESSORIES, GROUP_LABELS, getAccessoryBySlug, type Accessory } from '@/data/accessories';
import { displayName, pageMetadata } from '@/lib/seo';
type Params = { slug: string }; type Params = { slug: string };
// Only the slugs below exist. Anything else is a real 404 from the router, not a
// page that renders "not found" in the browser with status 200.
export const dynamicParams = false;
export function generateStaticParams() { export function generateStaticParams() {
return ACCESSORIES.map((a) => ({ slug: a.slug })); return ACCESSORIES.map((a) => ({ slug: a.slug }));
} }
@@ -20,18 +25,12 @@ export function generateStaticParams() {
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> { export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
const { slug } = await params; const { slug } = await params;
const a = getAccessoryBySlug(slug); const a = getAccessoryBySlug(slug);
if (!a) return { title: 'Accessory not found · YS Lootah Robotics' }; if (!a) return { title: 'Accessory not found' };
return { return pageMetadata({
title: `${a.brandLabel} ${a.name} · Available in UAE | YS Lootah Robotics`, title: `${displayName(a.brandLabel, a.name)} · Available in UAE`,
description: `${a.tagline} ${a.description} Available through YS Lootah Robotics in Dubai.`, description: `${a.tagline} ${a.description} Available through YS Lootah Robotics in Dubai.`,
alternates: { canonical: `/accessories/${a.slug}/` }, path: `/accessories/${a.slug}`,
openGraph: { });
title: `${a.brandLabel} ${a.name}`,
description: a.tagline,
url: `/accessories/${a.slug}/`,
type: 'website',
},
};
} }
export default async function AccessoryDetailPage({ params }: { params: Promise<Params> }) { export default async function AccessoryDetailPage({ params }: { params: Promise<Params> }) {
+5 -4
View File
@@ -5,13 +5,14 @@ import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { AccessoriesShowcase } from '@/components/robotics/AccessoriesShowcase'; import { AccessoriesShowcase } from '@/components/robotics/AccessoriesShowcase';
import { ACCESSORIES } from '@/data/accessories'; import { ACCESSORIES } from '@/data/accessories';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Robotics Accessories · Dexterous Hands, Arms, Sensors, IoT | YS Lootah Robotics', title: 'Robotics Accessories · Dexterous Hands, Arms, Sensors, IoT',
description: description:
'Browse Unitree dexterous hands, robotic arms, perception sensors, and Pudu dispatch, docking, modular attachments, and building IoT modules — available in the UAE through YS Lootah Robotics.', 'Browse Unitree dexterous hands, robotic arms, perception sensors, and Pudu dispatch, docking, modular attachments, and building IoT modules — available in the UAE through YS Lootah Robotics.',
alternates: { canonical: '/accessories/' }, path: '/accessories/',
}; });
export default function AccessoriesIndexPage() { export default function AccessoriesIndexPage() {
return ( return (
+5 -3
View File
@@ -2,12 +2,14 @@ import type { Metadata } from 'next';
import { Navbar } from '@/components/Navbar'; import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { InquiryForm } from '@/components/robotics/InquiryForm'; import { InquiryForm } from '@/components/robotics/InquiryForm';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Book a Robotics Demo in Dubai YS Lootah Robotics', title: 'Book a Robotics Demo in Dubai',
description: description:
'Book a live robot demo at our Dubai showroom or at your venue. See selected Unitree and Pudu Robotics solutions in action, available in the UAE through YS Lootah Robotics.', 'Book a live robot demo at our Dubai showroom or at your venue. See selected Unitree and Pudu Robotics solutions in action, available in the UAE through YS Lootah Robotics.',
}; path: '/book-demo/',
});
export default function BookDemoPage() { export default function BookDemoPage() {
return ( return (
+5 -3
View File
@@ -5,12 +5,14 @@ import { BRANDS, ROBOTS, type RobotBrand } from '@/data/robots';
import { RobotProductCard } from '@/components/robotics/RobotProductCard'; import { RobotProductCard } from '@/components/robotics/RobotProductCard';
import { MotionSection } from '@/components/ui/MotionSection'; import { MotionSection } from '@/components/ui/MotionSection';
import { DemoCTA } from '@/components/robotics/DemoCTA'; import { DemoCTA } from '@/components/robotics/DemoCTA';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Robotics Brands Unitree & Pudu | UAE Access via YS Lootah Robotics', title: 'Robotics Brands in the UAE · Unitree & Pudu',
description: description:
'Selected Unitree and Pudu Robotics solutions available in the UAE through YS Lootah Robotics. Humanoid, quadruped, service, delivery, and cleaning robots.', 'Selected Unitree and Pudu Robotics solutions available in the UAE through YS Lootah Robotics. Humanoid, quadruped, service, delivery, and cleaning robots.',
}; path: '/brands/',
});
const ORDER: RobotBrand[] = ['unitree', 'pudu']; const ORDER: RobotBrand[] = ['unitree', 'pudu'];
+5 -10
View File
@@ -23,22 +23,17 @@ import {
} from '@/data/bu-sunaidah'; } from '@/data/bu-sunaidah';
import { BU_SUNAIDAH_PRESS } from '@/data/bu-sunaidah-press'; import { BU_SUNAIDAH_PRESS } from '@/data/bu-sunaidah-press';
import { getLatestBuSunaidahPosts } from '@/lib/bu-sunaidah-fetch'; import { getLatestBuSunaidahPosts } from '@/lib/bu-sunaidah-fetch';
import { pageMetadata } from '@/lib/seo';
// Re-fetch latest Instagram posts every 30 minutes on the server. // Re-fetch latest Instagram posts every 30 minutes on the server.
export const revalidate = 1800; export const revalidate = 1800;
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Bu Sunaidah · Emirati Robotics Persona | YS Lootah Robotics', title: 'Bu Sunaidah · Emirati Robotics Persona',
description: description:
'Meet Bu Sunaidah — the Emirati culture-inspired robotics character from YS Lootah Robotics. Media activations, exhibitions, and community storytelling across the UAE.', 'Meet Bu Sunaidah — the Emirati culture-inspired robotics character from YS Lootah Robotics. Media activations, exhibitions, and community storytelling across the UAE.',
alternates: { canonical: '/bu-sunaidah/' }, path: '/bu-sunaidah/',
openGraph: { });
title: 'Bu Sunaidah · Emirati Robotics Persona',
description: 'Emirati culture-inspired robotics character — UAE media, exhibitions, community.',
url: '/bu-sunaidah/',
type: 'website',
},
};
const PILLARS = [ const PILLARS = [
{ {
+14
View File
@@ -0,0 +1,14 @@
import type { Metadata } from 'next';
import { pageMetadata } from '@/lib/seo';
// The page itself is a client component and cannot export metadata.
export const metadata: Metadata = pageMetadata({
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/',
});
export default function ConfigureLayout({ children }: { children: React.ReactNode }) {
return children;
}
+6 -4
View File
@@ -2,12 +2,14 @@ import type { Metadata } from 'next';
import { Navbar } from '@/components/Navbar'; import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { InquiryForm } from '@/components/robotics/InquiryForm'; import { InquiryForm } from '@/components/robotics/InquiryForm';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Contact YS Lootah Robotics Dubai Robotics Sales & Demo Inquiries', title: 'Contact · Dubai Robotics Sales & Demo Inquiries',
description: description:
'Dubai robotics sales, support, and demo inquiries. Contact YS Lootah Robotics, the UAE destination for selected Unitree and Pudu Robotics solutions, by phone, email, or WhatsApp.', 'Dubai robotics sales, support, and demo inquiries. Contact YS Lootah Robotics, the UAE destination for selected Unitree and Pudu Robotics solutions, by phone, email, or WhatsApp.',
}; path: '/contact/',
});
export default function ContactPage() { export default function ContactPage() {
return ( return (
@@ -27,7 +29,7 @@ export default function ContactPage() {
<div className="card" style={{ padding: 'clamp(1.5rem, 4vw, 2rem)', display: 'flex', flexDirection: 'column', gap: '1rem' }}> <div className="card" style={{ padding: 'clamp(1.5rem, 4vw, 2rem)', display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<ContactRow label="Phone" value="+971 55 948 2728" href="tel:+971559482728" /> <ContactRow label="Phone" value="+971 55 948 2728" href="tel:+971559482728" />
<ContactRow label="Office" value="+971 4 349 9319" href="tel:+97143499319" /> <ContactRow label="Office" value="+971 4 289 6095" href="tel:+97142896095" />
<ContactRow label="Email" value="info@yslootahrobotics.com" href="mailto:info@yslootahrobotics.com" /> <ContactRow label="Email" value="info@yslootahrobotics.com" href="mailto:info@yslootahrobotics.com" />
<ContactRow label="WhatsApp" value="+971 55 948 2728" href="https://wa.me/971559482728" external /> <ContactRow label="WhatsApp" value="+971 55 948 2728" href="https://wa.me/971559482728" external />
<ContactRow label="Address" value="Showroom No 03, Sheikh Zayed Rd Trade Center Second - DIFC - Dubai, UAE" href="https://maps.google.com/?q=Showroom+No+03+Sheikh+Zayed+Rd+Trade+Center+Second+DIFC+Dubai" external /> <ContactRow label="Address" value="Showroom No 03, Sheikh Zayed Rd Trade Center Second - DIFC - Dubai, UAE" href="https://maps.google.com/?q=Showroom+No+03+Sheikh+Zayed+Rd+Trade+Center+Second+DIFC+Dubai" external />
+180 -13
View File
@@ -24,8 +24,10 @@ import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { MotionSection } from '@/components/ui/MotionSection'; import { MotionSection } from '@/components/ui/MotionSection';
import { CTAButton } from '@/components/ui/CTAButton'; 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 { INDUSTRY_PAGES, type IndustrySolution } from '@/data/industry-pages';
import { displayName, pageMetadata } from '@/lib/seo';
type Params = { slug: string }; type Params = { slug: string };
@@ -44,24 +46,37 @@ const ICONS: Record<IndustrySolution['iconKey'], React.ComponentType<{ size?: nu
tray: UtensilsCrossed, tray: UtensilsCrossed,
}; };
// Only the slugs below exist. Anything else is a real 404 from the router, not a
// page that renders "not found" in the browser with status 200.
export const dynamicParams = false;
export function generateStaticParams() { 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> { export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
const { slug } = await params; 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); const industry = getIndustryBySlug(slug);
if (!industry) return {}; if (!industry) return {};
const content = INDUSTRY_PAGES[industry.id]; const content = INDUSTRY_PAGES[industry.id];
if (!content) return {}; if (!content) return {};
const url = `/industries/${industry.slug}/`; return pageMetadata({
return {
title: content.seoTitle, title: content.seoTitle,
description: content.seoDescription, description: content.seoDescription,
alternates: { canonical: url }, path: `/industries/${industry.slug}`,
openGraph: { title: content.seoTitle, description: content.seoDescription, url, type: 'website' }, image: industry.image,
twitter: { card: 'summary_large_image', title: content.seoTitle, description: content.seoDescription }, imageAlt: industry.imageAlt,
}; });
} }
const TRUST_DEFAULT = [ const TRUST_DEFAULT = [
@@ -73,6 +88,8 @@ const TRUST_DEFAULT = [
export default async function IndustryDetailPage({ params }: { params: Promise<Params> }) { export default async function IndustryDetailPage({ params }: { params: Promise<Params> }) {
const { slug } = await params; const { slug } = await params;
const upcoming = getUpcomingIndustryBySlug(slug);
if (upcoming) return <ComingSoonIndustry industry={upcoming} />;
const industry = getIndustryBySlug(slug); const industry = getIndustryBySlug(slug);
if (!industry) notFound(); if (!industry) notFound();
const content = INDUSTRY_PAGES[industry.id]; const content = INDUSTRY_PAGES[industry.id];
@@ -437,7 +454,160 @@ export default async function IndustryDetailPage({ params }: { params: Promise<P
<FooterAndContact /> <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 { .ip-main {
padding-top: clamp(5rem, 9vw, 7rem); padding-top: clamp(5rem, 9vw, 7rem);
padding-bottom: clamp(2rem, 5vw, 4rem); padding-bottom: clamp(2rem, 5vw, 4rem);
@@ -1094,7 +1264,4 @@ export default async function IndustryDetailPage({ params }: { params: Promise<P
.ip-cta-actions .cta-btn { width: 100%; justify-content: space-between; } .ip-cta-actions .cta-btn { width: 100%; justify-content: space-between; }
.ip-hero-actions .cta-btn { width: 100%; justify-content: space-between; } .ip-hero-actions .cta-btn { width: 100%; justify-content: space-between; }
} }
`}</style> `;
</>
);
}
+6 -4
View File
@@ -9,12 +9,14 @@ import { FooterAndContact } from '@/components/FooterAndContact';
import { IndustryUseCases } from '@/components/robotics/IndustryUseCases'; import { IndustryUseCases } from '@/components/robotics/IndustryUseCases';
import { DemoCTA } from '@/components/robotics/DemoCTA'; import { DemoCTA } from '@/components/robotics/DemoCTA';
import { MotionSection } from '@/components/ui/MotionSection'; import { MotionSection } from '@/components/ui/MotionSection';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Robotics for UAE Industries YS Lootah Robotics Dubai', title: 'Robotics for UAE Industries',
description: description:
'Robotics solutions for hospitality, restaurants, hotels, healthcare, education, security, warehouses, smart buildings, and government across the UAE.', 'Robotics solutions for hospitality, restaurants, hotels, healthcare, education, security, and warehouses across the UAE.',
}; path: '/industries/',
});
const HERO_COLLAGE: { src: string; alt: string }[] = [ const HERO_COLLAGE: { src: string; alt: string }[] = [
{ src: '/industries/cards/hospitality.webp', alt: 'Service robot in a luxury hospitality lobby' }, { src: '/industries/cards/hospitality.webp', alt: 'Service robot in a luxury hospitality lobby' },
+39
View File
@@ -0,0 +1,39 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact';
// Next adds the noindex tag to not-found responses itself.
export const metadata: Metadata = {
title: 'Page not found',
};
export default function NotFound() {
return (
<>
<Navbar />
<main style={{ paddingTop: 'clamp(8rem, 14vw, 11rem)', paddingBottom: 'clamp(5rem, 10vw, 8rem)' }}>
<div
className="container-wide"
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1.5rem', textAlign: 'center' }}
>
<span className="eyebrow">Error 404</span>
<h1 style={{ fontSize: 'clamp(2.2rem, 6vw, 4rem)', fontWeight: 600, letterSpacing: '-0.03em', lineHeight: 1.05, color: '#f5f5f5', maxWidth: '16ch' }}>
This page could not be found.
</h1>
<p style={{ color: '#a8a8a8', maxWidth: 520, lineHeight: 1.7 }}>
The address may have changed or the product may no longer be listed. Browse the catalog or
get in touch and we will point you to the right robot.
</p>
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: '0.75rem 1.5rem', marginTop: '0.5rem' }}>
<Link href="/robots/" style={{ color: '#f5f5f5' }}>Robots</Link>
<Link href="/accessories/" style={{ color: '#f5f5f5' }}>Accessories</Link>
<Link href="/industries/" style={{ color: '#f5f5f5' }}>Industries</Link>
<Link href="/contact/" style={{ color: '#f5f5f5' }}>Contact</Link>
</div>
</div>
</main>
<FooterAndContact />
</>
);
}
+9 -4
View File
@@ -16,11 +16,17 @@ import { FounderSection } from '@/components/robotics/FounderSection';
import { BuSunaidahSection } from '@/components/robotics/BuSunaidahSection'; import { BuSunaidahSection } from '@/components/robotics/BuSunaidahSection';
import { MotionSection } from '@/components/ui/MotionSection'; import { MotionSection } from '@/components/ui/MotionSection';
import { FEATURED_ROBOTS } from '@/data/robots'; 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 = { export const metadata: Metadata = {
title: 'YS Lootah Robotics UAE Destination for Unitree & Pudu Robotics', ...pageMetadata({
description: title: 'Robot Shop and Configurator in the UAE',
'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.', 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: [ keywords: [
'YS Lootah Robotics', 'YS Lootah Robotics',
'robotics Dubai', 'robotics Dubai',
@@ -35,7 +41,6 @@ export const metadata: Metadata = {
'robotics configuration UAE', 'robotics configuration UAE',
'robot sales and demo UAE', 'robot sales and demo UAE',
], ],
alternates: { canonical: '/' },
}; };
export default function HomePage() { export default function HomePage() {
+9
View File
@@ -1,6 +1,15 @@
import type { Metadata } from 'next';
import React from 'react'; import React from 'react';
import { Navbar } from '@/components/Navbar'; import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = pageMetadata({
title: 'Privacy Policy',
description:
'How YS Lootah Robotics collects, uses and protects personal information on this website and in enquiries, demo bookings and orders.',
path: '/privacy-policy/',
});
export default function PrivacyPolicyPage() { export default function PrivacyPolicyPage() {
return ( return (
+17 -14
View File
@@ -12,10 +12,14 @@ import { ProductGallery } from '@/components/robotics/ProductGallery';
import { MotionSection } from '@/components/ui/MotionSection'; import { MotionSection } from '@/components/ui/MotionSection';
import { ROBOTS, getRobotBySlug, BRANDS, CATEGORY_LABELS } from '@/data/robots'; import { ROBOTS, getRobotBySlug, BRANDS, CATEGORY_LABELS } from '@/data/robots';
import JsonLd from '@/components/JsonLd'; import JsonLd from '@/components/JsonLd';
import { canonical, abs, productJsonLd, breadcrumbJsonLd } from '@/lib/seo'; import { displayName, pageMetadata, productJsonLd, breadcrumbJsonLd } from '@/lib/seo';
type Params = { slug: string }; type Params = { slug: string };
// Only the slugs below exist. Anything else is a real 404 from the router, not a
// page that renders "not found" in the browser with status 200.
export const dynamicParams = false;
export function generateStaticParams() { export function generateStaticParams() {
return ROBOTS.map((r) => ({ slug: r.slug })); return ROBOTS.map((r) => ({ slug: r.slug }));
} }
@@ -23,18 +27,17 @@ export function generateStaticParams() {
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> { export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
const { slug } = await params; const { slug } = await params;
const robot = getRobotBySlug(slug); const robot = getRobotBySlug(slug);
if (!robot) return { title: 'Robot not found YS Lootah Robotics' }; if (!robot) return { title: 'Robot not found' };
const url = canonical(`/robots/${robot.slug}`); const name = displayName(robot.brandLabel, robot.name);
const img = abs(robot.image);
const title = `${robot.brandLabel} ${robot.name} Available in Dubai | YS Lootah Robotics`;
const description = `${robot.shortDescription} Available through YS Lootah Robotics in Dubai request a price or book a live demo.`;
return { return {
title, ...pageMetadata({
description, title: `${name} Available in Dubai`,
description: `${robot.shortDescription} Available through YS Lootah Robotics in Dubai request a price or book a live demo.`,
path: `/robots/${robot.slug}`,
image: robot.image,
imageAlt: name,
}),
keywords: [robot.brandLabel, robot.name, 'Dubai', 'UAE', CATEGORY_LABELS[robot.category], 'robotics'], keywords: [robot.brandLabel, robot.name, 'Dubai', 'UAE', CATEGORY_LABELS[robot.category], 'robotics'],
alternates: { canonical: url },
openGraph: { type: 'website', url, title, description, images: [{ url: img, alt: `${robot.brandLabel} ${robot.name}` }] },
twitter: { card: 'summary_large_image', title, description, images: [img] },
}; };
} }
@@ -47,7 +50,7 @@ export default async function RobotDetailPage({ params }: { params: Promise<Para
const related = ROBOTS.filter((r) => r.id !== robot.id && (r.brand === robot.brand || r.category === robot.category)).slice(0, 3); const related = ROBOTS.filter((r) => r.id !== robot.id && (r.brand === robot.brand || r.category === robot.category)).slice(0, 3);
const productLd = productJsonLd({ const productLd = productJsonLd({
name: `${robot.brandLabel} ${robot.name}`, name: displayName(robot.brandLabel, robot.name),
description: robot.shortDescription, description: robot.shortDescription,
image: robot.image, image: robot.image,
brandName: brand.name, brandName: brand.name,
@@ -85,7 +88,7 @@ export default async function RobotDetailPage({ params }: { params: Promise<Para
> >
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<ProductGallery <ProductGallery
images={[{ src: robot.image, alt: `${robot.brandLabel} ${robot.name}` }]} images={[{ src: robot.image, alt: displayName(robot.brandLabel, robot.name) }]}
accent={robot.accent} accent={robot.accent}
/> />
{robot.imageType === 'placeholder' && ( {robot.imageType === 'placeholder' && (
@@ -184,7 +187,7 @@ export default async function RobotDetailPage({ params }: { params: Promise<Para
Tell us about your venue, timeline, and use case. We will respond with availability, configuration options, and pricing for the UAE. Tell us about your venue, timeline, and use case. We will respond with availability, configuration options, and pricing for the UAE.
</p> </p>
</div> </div>
<InquiryForm defaultRobot={`${robot.brandLabel} ${robot.name}`} defaultCategory={robot.category} intent="quotation" /> <InquiryForm defaultRobot={displayName(robot.brandLabel, robot.name)} defaultCategory={robot.category} intent="quotation" />
</div> </div>
<div className="card" style={{ padding: 'clamp(1.5rem, 4vw, 2.25rem)', display: 'flex', flexDirection: 'column', gap: '1rem' }}> <div className="card" style={{ padding: 'clamp(1.5rem, 4vw, 2.25rem)', display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+5 -4
View File
@@ -9,13 +9,14 @@ import { RoboticsSplineShowcase } from '@/components/sections/robotics-spline-sh
import { CTAButton } from '@/components/ui/CTAButton'; import { CTAButton } from '@/components/ui/CTAButton';
import { ROBOTS } from '@/data/robots'; import { ROBOTS } from '@/data/robots';
import { ACCESSORIES } from '@/data/accessories'; import { ACCESSORIES } from '@/data/accessories';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = { export const metadata: Metadata = pageMetadata({
title: 'Robots Catalog · Unitree & Pudu in UAE | YS Lootah Robotics', title: 'Robots Catalog · Unitree & Pudu in UAE',
description: description:
'Explore selected humanoid, quadruped, industrial, service, delivery, hospitality, and cleaning robots — plus dexterous hands, arms, sensors, and accessories — from Unitree and Pudu available in the UAE through YS Lootah Robotics.', 'Explore selected humanoid, quadruped, industrial, service, delivery, hospitality, and cleaning robots — plus dexterous hands, arms, sensors, and accessories — from Unitree and Pudu available in the UAE through YS Lootah Robotics.',
alternates: { canonical: '/robots/' }, path: '/robots/',
}; });
const unitreeCount = ROBOTS.filter((r) => r.brand === 'unitree').length; const unitreeCount = ROBOTS.filter((r) => r.brand === 'unitree').length;
const puduCount = ROBOTS.filter((r) => r.brand === 'pudu').length; const puduCount = ROBOTS.filter((r) => r.brand === 'pudu').length;
+2 -2
View File
@@ -1,6 +1,6 @@
import { MetadataRoute } from 'next'; import { MetadataRoute } from 'next';
import { ROBOTS } from '@/data/robots'; import { ROBOTS } from '@/data/robots';
import { INDUSTRIES } from '@/data/industries'; import { INDUSTRIES, UPCOMING_INDUSTRIES } from '@/data/industries';
import { ACCESSORIES } from '@/data/accessories'; import { ACCESSORIES } from '@/data/accessories';
import { SITE_URL } from '@/lib/seo'; import { SITE_URL } from '@/lib/seo';
@@ -30,7 +30,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
priority: 0.7, priority: 0.7,
})); }));
const industryRoutes: MetadataRoute.Sitemap = INDUSTRIES.map((i) => ({ const industryRoutes: MetadataRoute.Sitemap = [...INDUSTRIES, ...UPCOMING_INDUSTRIES].map((i) => ({
url: u(`/industries/${i.slug}/`), url: u(`/industries/${i.slug}/`),
lastModified: now, lastModified: now,
changeFrequency: 'monthly', changeFrequency: 'monthly',
+9
View File
@@ -1,6 +1,15 @@
import type { Metadata } from 'next';
import React from 'react'; import React from 'react';
import { Navbar } from '@/components/Navbar'; import { Navbar } from '@/components/Navbar';
import { FooterAndContact } from '@/components/FooterAndContact'; import { FooterAndContact } from '@/components/FooterAndContact';
import { pageMetadata } from '@/lib/seo';
export const metadata: Metadata = pageMetadata({
title: 'Terms of Service',
description:
'The terms that apply to using the YS Lootah Robotics website, requesting quotations, booking demos and ordering robots in the UAE.',
path: '/terms-of-service/',
});
export default function TermsOfServicePage() { export default function TermsOfServicePage() {
return ( return (
+4 -1
View File
@@ -9,7 +9,10 @@ import { PaymentStep } from './checkout/PaymentStep';
import { ReviewStep } from './checkout/ReviewStep'; import { ReviewStep } from './checkout/ReviewStep';
import { ConfirmationStep } from './checkout/ConfirmationStep'; import { ConfirmationStep } from './checkout/ConfirmationStep';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!); // Without a publishable key (local builds), loadStripe throws on every page that
// imports this module. Elements accepts null and the payment step stays empty.
const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
const stripePromise = publishableKey ? loadStripe(publishableKey) : null;
const STEPS: { id: CheckoutStep; label: string }[] = [ const STEPS: { id: CheckoutStep; label: string }[] = [
{ id: 'shipping', label: 'Shipping' }, { id: 'shipping', label: 'Shipping' },
+1 -1
View File
@@ -12,7 +12,7 @@ import { CTAButton } from '@/components/ui/CTAButton';
const CONTACT = { const CONTACT = {
phonePrimary: '+971 55 948 2728', phonePrimary: '+971 55 948 2728',
phoneSecondary: '+971 4 349 9319', phoneSecondary: '+971 4 289 6095',
email: 'info@yslootahrobotics.com', email: 'info@yslootahrobotics.com',
address: 'Showroom No 03, Sheikh Zayed Rd Trade Center Second - DIFC - Dubai, UAE', address: 'Showroom No 03, Sheikh Zayed Rd Trade Center Second - DIFC - Dubai, UAE',
whatsapp: 'https://wa.me/971559482728', whatsapp: 'https://wa.me/971559482728',
@@ -4,6 +4,7 @@ import { useMemo, useState } from 'react';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { ACCESSORIES, GROUP_LABELS, type AccessoryGroup, type Accessory } from '@/data/accessories'; import { ACCESSORIES, GROUP_LABELS, type AccessoryGroup, type Accessory } from '@/data/accessories';
import { displayName } from '@/lib/seo';
type Filter = AccessoryGroup | 'all'; type Filter = AccessoryGroup | 'all';
@@ -97,7 +98,7 @@ function AccessoryCard({ accessory }: { accessory: Accessory }) {
> >
<Link <Link
href={detailHref} href={detailHref}
aria-label={`View details for ${accessory.brandLabel} ${accessory.name}`} aria-label={`View details for ${displayName(accessory.brandLabel, accessory.name)}`}
style={{ style={{
position: 'relative', position: 'relative',
display: 'block', display: 'block',
@@ -123,7 +124,7 @@ function AccessoryCard({ accessory }: { accessory: Accessory }) {
/> />
<Image <Image
src={accessory.image} src={accessory.image}
alt={`${accessory.brandLabel} ${accessory.name}`} alt={displayName(accessory.brandLabel, accessory.name)}
fill fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
style={{ style={{
+2 -1
View File
@@ -6,6 +6,7 @@ import Link from 'next/link';
import { PremiumButton } from '@/components/ui/PremiumButton'; import { PremiumButton } from '@/components/ui/PremiumButton';
import { CursorSpotlight } from '@/components/ui/CursorSpotlight'; import { CursorSpotlight } from '@/components/ui/CursorSpotlight';
import { FEATURED_ROBOTS } from '@/data/robots'; import { FEATURED_ROBOTS } from '@/data/robots';
import { displayName } from '@/lib/seo';
const HERO_STATS = [ const HERO_STATS = [
{ value: 'UAE', label: 'Access' }, { value: 'UAE', label: 'Access' },
@@ -341,7 +342,7 @@ export function Hero3DRobotics() {
<div style={{ position: 'relative', width: '100%', height: '100%' }}> <div style={{ position: 'relative', width: '100%', height: '100%' }}>
<Image <Image
src={r.image} src={r.image}
alt={`${r.brandLabel} ${r.name}`} alt={displayName(r.brandLabel, r.name)}
fill fill
sizes="(max-width: 768px) 90vw, 620px" sizes="(max-width: 768px) 90vw, 620px"
style={{ objectFit: 'contain', objectPosition: 'center bottom' }} style={{ objectFit: 'contain', objectPosition: 'center bottom' }}
+3 -3
View File
@@ -15,7 +15,7 @@ import Sparkles from 'lucide-react/dist/esm/icons/sparkles';
import Landmark from 'lucide-react/dist/esm/icons/landmark'; import Landmark from 'lucide-react/dist/esm/icons/landmark';
import ArrowRight from 'lucide-react/dist/esm/icons/arrow-right'; import ArrowRight from 'lucide-react/dist/esm/icons/arrow-right';
import type { LucideIcon } from 'lucide-react'; import type { LucideIcon } from 'lucide-react';
import { INDUSTRIES, type Industry } from '@/data/industries'; import { INDUSTRIES, UPCOMING_INDUSTRIES, type Industry } from '@/data/industries';
const ease = [0.16, 1, 0.3, 1] as const; const ease = [0.16, 1, 0.3, 1] as const;
@@ -53,7 +53,7 @@ const STATS = [
] as const; ] as const;
export function IndustryUseCases({ limit }: { limit?: number }) { export function IndustryUseCases({ limit }: { limit?: number }) {
const list = limit ? INDUSTRIES.slice(0, limit) : INDUSTRIES; const list = limit ? INDUSTRIES.slice(0, limit) : [...INDUSTRIES, ...UPCOMING_INDUSTRIES];
return ( return (
<section className="iu" aria-label="Industries served"> <section className="iu" aria-label="Industries served">
@@ -232,7 +232,7 @@ function IndustryCard({ industry, delay }: { industry: Industry; delay: number }
<h3 className="ic-title">{industry.name}</h3> <h3 className="ic-title">{industry.name}</h3>
<p className="ic-desc">{blurb}</p> <p className="ic-desc">{blurb}</p>
<span className="ic-cta"> <span className="ic-cta">
<span>Learn more</span> <span>{industry.upcoming ? 'Coming soon' : 'Learn more'}</span>
<span className="ic-cta-arrow" aria-hidden> <span className="ic-cta-arrow" aria-hidden>
<ArrowRight size={14} strokeWidth={2} /> <ArrowRight size={14} strokeWidth={2} />
</span> </span>
+3 -2
View File
@@ -3,6 +3,7 @@
import Link from 'next/link'; import Link from 'next/link';
import Image from 'next/image'; import Image from 'next/image';
import { Robot, CATEGORY_LABELS } from '@/data/robots'; import { Robot, CATEGORY_LABELS } from '@/data/robots';
import { displayName } from '@/lib/seo';
type Props = { type Props = {
robot: Robot; robot: Robot;
@@ -24,7 +25,7 @@ export function RobotProductCard({ robot, priority = false }: Props) {
> >
<Link <Link
href={detailHref} href={detailHref}
aria-label={`View details for ${robot.brandLabel} ${robot.name}`} aria-label={`View details for ${displayName(robot.brandLabel, robot.name)}`}
style={{ style={{
position: 'relative', position: 'relative',
display: 'block', display: 'block',
@@ -50,7 +51,7 @@ export function RobotProductCard({ robot, priority = false }: Props) {
/> />
<Image <Image
src={robot.image} src={robot.image}
alt={`${robot.brandLabel} ${robot.name}`} alt={displayName(robot.brandLabel, robot.name)}
fill fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
style={{ style={{
+27
View File
@@ -12,6 +12,8 @@ export interface Industry {
icon: 'building' | 'utensils' | 'hotel' | 'shopping-bag' | 'heart-pulse' | 'graduation-cap' | 'shield' | 'warehouse' | 'sparkles' | 'landmark'; icon: 'building' | 'utensils' | 'hotel' | 'shopping-bag' | 'heart-pulse' | 'graduation-cap' | 'shield' | 'warehouse' | 'sparkles' | 'landmark';
image: string; image: string;
imageAlt: string; imageAlt: string;
/** Sector with a short coming-soon page instead of a full INDUSTRY_PAGES entry. */
upcoming?: { seoTitle: string; seoDescription: string };
} }
export const INDUSTRIES: Industry[] = [ export const INDUSTRIES: Industry[] = [
@@ -119,6 +121,12 @@ export const INDUSTRIES: Industry[] = [
image: '/industries/cards/warehouses-logistics.webp', image: '/industries/cards/warehouses-logistics.webp',
imageAlt: 'Industrial robot for UAE warehouses and logistics operations', imageAlt: 'Industrial robot for UAE warehouses and logistics operations',
}, },
];
// Sectors with no full detail page content yet (no entry in INDUSTRY_PAGES).
// Each renders a short coming-soon page from its `upcoming` copy. Move an entry
// up into INDUSTRIES once its INDUSTRY_PAGES content is written.
export const UPCOMING_INDUSTRIES: Industry[] = [
{ {
id: 'events', id: 'events',
slug: 'events', slug: 'events',
@@ -131,6 +139,11 @@ export const INDUSTRIES: Industry[] = [
icon: 'sparkles', icon: 'sparkles',
image: '/industries/cards/events.webp', image: '/industries/cards/events.webp',
imageAlt: 'Robotics activation for UAE live events and brand experiences', imageAlt: 'Robotics activation for UAE live events and brand experiences',
upcoming: {
seoTitle: 'Robots for Events and Exhibitions in the UAE',
seoDescription:
'Humanoid and quadruped robot showcases for UAE events, exhibitions and brand activations. Book a demo with YS Lootah Robotics in Dubai.',
},
}, },
{ {
id: 'smart-buildings', id: 'smart-buildings',
@@ -144,6 +157,11 @@ export const INDUSTRIES: Industry[] = [
icon: 'building', icon: 'building',
image: '/industries/cards/smart-buildings.webp', image: '/industries/cards/smart-buildings.webp',
imageAlt: 'Cleaning robot in a modern UAE smart office building', imageAlt: 'Cleaning robot in a modern UAE smart office building',
upcoming: {
seoTitle: 'Robots for Smart Buildings in the UAE',
seoDescription:
'Cleaning, delivery and patrol robots working as one fleet in UAE smart buildings. Talk to YS Lootah Robotics about a deployment in Dubai.',
},
}, },
{ {
id: 'government', id: 'government',
@@ -157,9 +175,18 @@ export const INDUSTRIES: Industry[] = [
icon: 'landmark', icon: 'landmark',
image: '/industries/unitree/utility-room.webp', image: '/industries/unitree/utility-room.webp',
imageAlt: 'Quadruped robots deployed for UAE civic and emergency services', imageAlt: 'Quadruped robots deployed for UAE civic and emergency services',
upcoming: {
seoTitle: 'Robots for Government and Public Sector in the UAE',
seoDescription:
'Humanoid, quadruped and service robots for UAE government and public sector programs, with local deployment and support from YS Lootah Robotics.',
},
}, },
]; ];
export function getIndustryBySlug(slug: string): Industry | undefined { export function getIndustryBySlug(slug: string): Industry | undefined {
return INDUSTRIES.find((i) => i.slug === slug); return INDUSTRIES.find((i) => i.slug === slug);
} }
export function getUpcomingIndustryBySlug(slug: string): Industry | undefined {
return UPCOMING_INDUSTRIES.find((i) => i.slug === slug);
}
+9 -8
View File
@@ -54,6 +54,7 @@ export type IndustryPageContent = {
suggestedRobots: SuggestedRobot[]; suggestedRobots: SuggestedRobot[];
ctaTitle?: string; ctaTitle?: string;
ctaBody?: string; ctaBody?: string;
/** Without the brand: the layout title template appends it. */
seoTitle: string; seoTitle: string;
seoDescription: string; seoDescription: string;
}; };
@@ -141,7 +142,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Humanoid', 'Concierge', 'Premium'], chips: ['Humanoid', 'Concierge', 'Premium'],
}, },
], ],
seoTitle: 'Hospitality Robots in UAE | YS Lootah Robotics', seoTitle: 'Hospitality Robots in UAE',
seoDescription: seoDescription:
'Service, delivery, cleaning, and concierge robots for hotels, lounges, and hospitality venues across the UAE. Pudu and Unitree solutions available in the UAE through YS Lootah Robotics.', 'Service, delivery, cleaning, and concierge robots for hotels, lounges, and hospitality venues across the UAE. Pudu and Unitree solutions available in the UAE through YS Lootah Robotics.',
}, },
@@ -223,7 +224,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Compact', 'Auto-doors', 'Multi-zone'], chips: ['Compact', 'Auto-doors', 'Multi-zone'],
}, },
], ],
seoTitle: 'Restaurant & Café Robots in UAE | YS Lootah Robotics', seoTitle: 'Restaurant & Café Robots in UAE',
seoDescription: seoDescription:
'Delivery, host, and tray-runner robots for UAE restaurants, cafés, and F&B venues. Faster service, lower staff fatigue, premium positioning.', 'Delivery, host, and tray-runner robots for UAE restaurants, cafés, and F&B venues. Faster service, lower staff fatigue, premium positioning.',
}, },
@@ -305,7 +306,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Humanoid', 'Concierge', 'Activation'], chips: ['Humanoid', 'Concierge', 'Activation'],
}, },
], ],
seoTitle: 'Hotel & Resort Robots in UAE | YS Lootah Robotics', seoTitle: 'Hotel & Resort Robots in UAE',
seoDescription: seoDescription:
'Multi-floor delivery, cleaning, and humanoid concierge robots for hotels and resorts across the UAE. Local Dubai support and deployment.', 'Multi-floor delivery, cleaning, and humanoid concierge robots for hotels and resorts across the UAE. Local Dubai support and deployment.',
}, },
@@ -393,7 +394,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Humanoid', 'Activation', 'PR'], chips: ['Humanoid', 'Activation', 'PR'],
}, },
], ],
seoTitle: 'Shopping Mall Robots in UAE | YS Lootah Robotics', seoTitle: 'Shopping Mall Robots in UAE',
seoDescription: seoDescription:
'Cleaning, service, activation, and patrol robots for UAE shopping malls and retail flagships. Pudu and Unitree, deployed and supported locally.', 'Cleaning, service, activation, and patrol robots for UAE shopping malls and retail flagships. Pudu and Unitree, deployed and supported locally.',
}, },
@@ -475,7 +476,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Greeter', 'Wayfinding', 'Ad screen'], chips: ['Greeter', 'Wayfinding', 'Ad screen'],
}, },
], ],
seoTitle: 'Healthcare Robots in UAE | YS Lootah Robotics', seoTitle: 'Healthcare Robots in UAE',
seoDescription: seoDescription:
'Autonomous delivery, cleaning, and visitor-guidance robots for UAE clinics, hospitals, and healthcare networks. Local support from Dubai.', 'Autonomous delivery, cleaning, and visitor-guidance robots for UAE clinics, hospitals, and healthcare networks. Local support from Dubai.',
}, },
@@ -584,7 +585,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
ctaTitle: 'Build a future-ready robotics program.', ctaTitle: 'Build a future-ready robotics program.',
ctaBody: ctaBody:
'Tell us about your school, university, or innovation lab. Our Dubai team will recommend the right robotics setup and arrange a demo or quotation.', 'Tell us about your school, university, or innovation lab. Our Dubai team will recommend the right robotics setup and arrange a demo or quotation.',
seoTitle: 'Education & STEM Robots in UAE | YS Lootah Robotics', seoTitle: 'Education & STEM Robots in UAE',
seoDescription: seoDescription:
'Humanoid and quadruped robots for UAE schools, universities, and innovation labs. Hands-on AI and robotics education with local Dubai support.', 'Humanoid and quadruped robots for UAE schools, universities, and innovation labs. Hands-on AI and robotics education with local Dubai support.',
}, },
@@ -700,7 +701,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Thermal', 'LiDAR', 'AI vision'], chips: ['Thermal', 'LiDAR', 'AI vision'],
}, },
], ],
seoTitle: 'Security & Surveillance Robots in UAE | YS Lootah Robotics', seoTitle: 'Security & Surveillance Robots in UAE',
seoDescription: seoDescription:
'Quadruped patrol, inspection, and perimeter robots for UAE facilities, compounds, and critical sites. Unitree platforms with local support.', 'Quadruped patrol, inspection, and perimeter robots for UAE facilities, compounds, and critical sites. Unitree platforms with local support.',
}, },
@@ -792,7 +793,7 @@ export const INDUSTRY_PAGES: Record<string, IndustryPageContent> = {
chips: ['Thermal', 'LiDAR', 'AI'], chips: ['Thermal', 'LiDAR', 'AI'],
}, },
], ],
seoTitle: 'Warehouse & Logistics Robots in UAE | YS Lootah Robotics', seoTitle: 'Warehouse & Logistics Robots in UAE',
seoDescription: seoDescription:
'Quadruped inspection, autonomous transport, and monitoring robots for UAE warehouses, logistics, and industrial sites. Local Dubai deployment.', 'Quadruped inspection, autonomous transport, and monitoring robots for UAE warehouses, logistics, and industrial sites. Local Dubai deployment.',
}, },
+66
View File
@@ -0,0 +1,66 @@
import { describe, expect, it } from 'vitest';
import { ROBOTS } from '@/data/robots';
import { ACCESSORIES } from '@/data/accessories';
import { INDUSTRIES, UPCOMING_INDUSTRIES } from '@/data/industries';
import { INDUSTRY_PAGES } from '@/data/industry-pages';
import { canonical, displayName, pageMetadata, siteName } from './seo';
describe('displayName', () => {
it('joins brand and name', () => {
expect(displayName('Unitree', 'G1 Humanoid')).toBe('Unitree G1 Humanoid');
});
it('does not repeat a brand the name already starts with', () => {
expect(displayName('Pudu', 'PUDU CC1')).toBe('PUDU CC1');
expect(displayName('Pudu', 'PuduBot 2')).toBe('PuduBot 2');
});
it('never yields a doubled brand for any catalog item', () => {
for (const item of [...ROBOTS, ...ACCESSORIES]) {
const name = displayName(item.brandLabel, item.name).toLowerCase();
const brand = item.brandLabel.toLowerCase();
expect(name.startsWith(`${brand} ${brand}`), name).toBe(false);
}
});
});
describe('pageMetadata', () => {
const meta = pageMetadata({ title: 'Robots Catalog', description: 'd', path: '/robots' });
it('leaves the brand out of the title, because the layout template appends it', () => {
expect(meta.title).not.toContain(siteName);
});
it('adds the brand once to share titles, which get no template', () => {
expect(meta.openGraph.title).toBe(`Robots Catalog | ${siteName}`);
expect(meta.twitter.title).toBe(meta.openGraph.title);
});
it('points canonical and og:url at the page itself, with a trailing slash', () => {
expect(meta.alternates.canonical).toBe(canonical('/robots/'));
expect(meta.openGraph.url).toBe(meta.alternates.canonical);
expect(meta.alternates.canonical.endsWith('/robots/')).toBe(true);
});
});
describe('industries', () => {
it('lists only sectors that have a detail page', () => {
for (const industry of INDUSTRIES) {
expect(INDUSTRY_PAGES[industry.id], industry.slug).toBeDefined();
}
});
it('keeps the brand out of industry titles', () => {
for (const page of Object.values(INDUSTRY_PAGES)) {
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);
}
});
});
+40 -1
View File
@@ -15,10 +15,49 @@ export const siteDescription =
'YS Lootah Robotics is the UAE sales destination for selected Unitree, AgiBot and Pudu robotics. Explore, configure and book a demo of humanoid, quadruped, delivery and cleaning robots across Dubai and the UAE.'; 'YS Lootah Robotics is the UAE sales destination for selected Unitree, AgiBot and Pudu robotics. Explore, configure and book a demo of humanoid, quadruped, delivery and cleaning robots across Dubai and the UAE.';
export const OG_IMAGE = abs('/og.jpg'); export const OG_IMAGE = abs('/og.jpg');
// "Pudu" + "PUDU CC1" would read "Pudu PUDU CC1": drop the label when the name already starts with it.
export const displayName = (brandLabel: string, name: string) =>
name.toLowerCase().startsWith(brandLabel.toLowerCase()) ? name : `${brandLabel} ${name}`;
type PageMeta = {
/** Page title without the brand. The root layout template appends " | YS Lootah Robotics". */
title: string;
description: string;
/** Path on this site: '/robots/', '/about/'. */
path: string;
/** Share image path or URL. Defaults to the site image. */
image?: string;
imageAlt?: string;
};
// One place for title, canonical and share tags, so no page is left with the
// homepage's og:url or without a canonical. The title template does not apply
// to openGraph or twitter titles, so the brand is added to those here.
export function pageMetadata({ title, description, path, image, imageAlt }: PageMeta) {
const url = canonical(path);
const fullTitle = `${title} | ${siteName}`;
const img = image ? (image.startsWith('http') ? image : abs(image)) : OG_IMAGE;
return {
title,
description,
alternates: { canonical: url },
openGraph: {
type: 'website' as const,
url,
title: fullTitle,
description,
siteName,
locale: 'en_AE',
images: [{ url: img, alt: imageAlt ?? siteName }],
},
twitter: { card: 'summary_large_image' as const, title: fullTitle, description, images: [img] },
};
}
export const business = { export const business = {
legalName: 'YS Lootah Robotics', legalName: 'YS Lootah Robotics',
parent: 'Yousuf Saeed Lootah Investment Group', parent: 'Yousuf Saeed Lootah Investment Group',
phones: ['+971559482728', '+97143499319'], phones: ['+971559482728', '+97142896095'],
email: 'info@yslootahrobotics.com', email: 'info@yslootahrobotics.com',
whatsapp: 'https://wa.me/971559482728', whatsapp: 'https://wa.me/971559482728',
street: 'Showroom No 03, Sheikh Zayed Rd, Trade Center Second, DIFC', street: 'Showroom No 03, Sheikh Zayed Rd, Trade Center Second, DIFC',