6 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
15 changed files with 235 additions and 32 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
+1 -1
View File
@@ -3,7 +3,7 @@ import { pageMetadata } from '@/lib/seo';
// The page itself is a client component and cannot export metadata. // The page itself is a client component and cannot export metadata.
export const metadata: Metadata = pageMetadata({ export const metadata: Metadata = pageMetadata({
title: 'Unitree G1 3D Configurator', title: 'Configure Your Robot and Request a UAE Quotation',
description: 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.', '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/', path: '/configure/',
+1 -1
View File
@@ -29,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 />
+171 -8
View File
@@ -24,9 +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 { pageMetadata } from '@/lib/seo'; import { displayName, pageMetadata } from '@/lib/seo';
type Params = { slug: string }; type Params = { slug: string };
@@ -50,11 +51,21 @@ const ICONS: Record<IndustrySolution['iconKey'], React.ComponentType<{ size?: nu
export const dynamicParams = false; 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];
@@ -77,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];
@@ -441,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);
@@ -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-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> `;
</>
);
}
+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() {
+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',
+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={{
+24 -3
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[] = [
@@ -121,9 +123,9 @@ export const INDUSTRIES: Industry[] = [
}, },
]; ];
// Sectors with no detail page content yet (no entry in INDUSTRY_PAGES). They are // Sectors with no full detail page content yet (no entry in INDUSTRY_PAGES).
// kept out of INDUSTRIES so no card, sitemap entry or route points at an empty // Each renders a short coming-soon page from its `upcoming` copy. Move an entry
// page. Move an entry back up once its content is written. // up into INDUSTRIES once its INDUSTRY_PAGES content is written.
export const UPCOMING_INDUSTRIES: Industry[] = [ export const UPCOMING_INDUSTRIES: Industry[] = [
{ {
id: 'events', id: 'events',
@@ -137,6 +139,11 @@ export const UPCOMING_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',
@@ -150,6 +157,11 @@ export const UPCOMING_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',
@@ -163,9 +175,18 @@ export const UPCOMING_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 -1
View File
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { ROBOTS } from '@/data/robots'; import { ROBOTS } from '@/data/robots';
import { ACCESSORIES } from '@/data/accessories'; 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 { INDUSTRY_PAGES } from '@/data/industry-pages';
import { canonical, displayName, pageMetadata, siteName } from './seo'; import { canonical, displayName, pageMetadata, siteName } from './seo';
@@ -55,4 +55,12 @@ describe('industries', () => {
expect(page.seoTitle).not.toContain(siteName); 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);
}
});
}); });
+1 -1
View File
@@ -57,7 +57,7 @@ export function pageMetadata({ title, description, path, image, imageAlt }: Page
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',