Skip to content

Top-Level Components

Location: src/components/
Count: 3 components
Tier: 1 (Deep Dive)
Purpose: Utility components for video management and CMS rendering


Overview

High-level reusable components that provide core functionality across the application.


1. AboutUsVideo Component

File: src/components/AboutUsVideo.tsx (133 lines)
Type: Client Component
Purpose: Video player with mobile-first playback management

💡 Smart Video Player - Single Video Control + Mobile Optimization


Props Interface

interface VideoDemoSectionProps {
  image: string; // Poster image URL
  video: string; // Video source URL
  altText?: string; // Accessibility text (optional)
}

Note: Props defined but NOT used in implementation - uses hardcoded values


State Variables (2)

  1. isMobile (boolean) - Device type detection
  2. isPlaying (boolean) - Video playback state

Refs (1)

videoRef (useRef<HTMLVideoElement>) - Video element reference


Mobile Detection Logic

User Agent + Width Check:

const checkIsMobile = () => {
  const userAgent = navigator.userAgent;
  const mobileRegex =
    /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
  return mobileRegex.test(userAgent) || window.innerWidth <= 768;
};

Breakpoint: ≤ 768px considered mobile

Responsive Handling:

  • Window resize listener
  • Dynamic poster image switch
  • Cleanup on unmount

Mobile Video Manager Integration

Registration:

useEffect(() => {
  if (videoRef.current) {
    mobileVideoManager.registerVideo(videoRef.current);
  }

  return () => {
    if (videoRef.current) {
      mobileVideoManager.unregisterVideo(videoRef.current);
    }
  };
}, []);

Purpose: Ensure only ONE video plays at a time across the app

Import: @/utils/mobileVideoManager


Playback State Sync

Event Listeners:

const handlePlay = () => setIsPlaying(true);
const handlePause = () => setIsPlaying(false);

vid.addEventListener("play", handlePlay);
vid.addEventListener("pause", handlePause);

Cleanup: Removes listeners on unmount


Mute/Unmute Toggle

Function: handleMuteUnmute()

Logic:

if (isPlaying) {
  // Pause the video
  mobileVideoManager.allowUserPause(videoRef.current);
  videoRef.current.pause();
} else {
  // Play the video
  videoRef.current.muted = false;
  videoRef.current.currentTime = 0; // Restart from beginning
  videoRef.current.play().catch((error) => {
    // Fallback: Try muted play if unmuted fails
    videoRef.current.muted = true;
    videoRef.current
      .play()
      .catch((err) => console.warn("Muted play also failed:", err));
  });
}

Error Handling:

  • Primary attempt: Unmuted play
  • Fallback: Muted play (autoplay policy)
  • Console warnings for both failure cases

Video Configuration

Hardcoded Values:

<video
  ref={videoRef}
  src="/videos/about-us-video-new.mp4"
  poster={isMobile ? "/imagemob.webp" : "/image.webp"}
  muted={!isPlaying}
  autoPlay={false}
  loop
  playsInline
  preload="metadata"
  className="w-full h-full rounded-xl"
  controls={false}
/>

Key Attributes:

  • src - Hardcoded to /videos/about-us-video-new.mp4
  • poster - Dynamic (desktop: /image.webp, mobile: /imagemob.webp)
  • muted - Opposite of isPlaying state
  • autoPlay - Disabled (user-initiated only)
  • loop - Enabled (continuous playback)
  • playsInline - iOS compatibility
  • preload="metadata" - Minimal preloading
  • controls - Disabled (custom UI)

UI Components

Button Overlay:

<button
  onClick={handleMuteUnmute}
  className={`absolute md:top-5 top-4 md:left-5 left-5 z-20 flex items-center gap-2 px-2 md:px-4 py-1 md:py-2 rounded-lg shadow-lg transition font-semibold text-base ${
    !isPlaying ? "bg-white text-gray-400" : "bg-white text-orange-600"
  }`}
  aria-label={!isPlaying ? "Unmute audio" : "Mute audio"}
>
  <HiOutlineSpeakerWave />
  <span>{!isPlaying ? "Unmute" : "Mute"}</span>
</button>

States:

  • Not Playing: Gray icon + "Unmute" text
  • Playing: Orange icon + "Mute" text

Icon: HiOutlineSpeakerWave from react-icons/hi2

Positioning: Absolute top-left overlay


Styling

Container:

  • Max width: 7xl (1280px)
  • Padding: 4 (mobile) → 10-16 (desktop)
  • Background: Gradient (from-[#D9F5F8] to-[#E6E1F7])

Video Wrapper:

  • Rounded corners (xl)
  • Shadow: lg
  • Group for hover effects

Responsive:

  • Mobile: Smaller padding, compact button
  • Desktop: Larger padding, full-size button

Accessibility

ARIA:

  • aria-label on button (dynamic)
  • Fallback text in video tag

No Keyboard Controls:

  • Custom controls disable native keyboard
  • Toggle button is keyboard accessible

Dependencies

External:

  • react (useRef, useState, useEffect)
  • react-icons/hi2 (HiOutlineSpeakerWave)

Internal:

  • @/utils/mobileVideoManager - Single video playback

Issues/Limitations

1. Unused Props:

export const AboutUsVideo = ({ }: VideoDemoSectionProps) => {
  // Props destructured but never used
  • image, video, altText defined but ignored
  • Hardcoded values used instead
  • Breaking change if props become required

2. Button Label Inconsistency:

  • Button says "Mute/Unmute"
  • Actually controls play/pause
  • Should be "Play/Pause" for clarity

3. No Loading State:

  • No spinner during video load
  • No error state for failed loads

4. Single Video Limitation:

  • Works well for About Us page
  • May need multi-instance support

2. DynamicPageRenderer Component

File: src/components/DynamicPageRenderer.tsx (38 lines)
Type: Server Component
Purpose: Render Strapi CMS sections dynamically

🎨 Component Mapper - Strapi to React


Props Interface

type Props = {
  sections: any[]; // Array of Strapi section data
};

Component Map

Strapi Component ↔ React Component:

const componentMap: Record<string, any> = {
  "landing-page.hero-section": HeroSection,
  "landing-page.video-section": VideoDemoSection,
  "landing-page.our-ai-agents": OurAiAgents,
  // 'landing-page.try-free-section': TryFreeAgentSection,  // Commented out
  "landing-page.our-platforms": PlugAndPlaySection,
  "landing-page.how-it-work": HowItWorksSection,
  "landing-page.our-language": LanguageSelectorSection,
  "landing-page.our-vision": OurVision,
  "landing-page.use-cases": UseCases,
};

Registered Sections: 8
Commented: 1 (TryFreeAgentSection)


Rendering Logic

export const DynamicPageRenderer = ({ sections }: Props) => {
  return (
    <>
      {sections.map((section, index) => {
        const Component = componentMap[section.__component];
        if (!Component) return null; // Skip unknown components
        return <Component key={index} {...section} />;
      })}
    </>
  );
};

Process:

  1. Map through sections array
  2. Look up component by __component field
  3. Return null if component not registered
  4. Spread section data as props to component
  5. Use array index as key

Strapi Integration

Expected Section Structure:

{
  __component: string; // e.g., "landing-page.hero-section"
  // ... other section-specific fields
}

Component Field: __component - Strapi's component identifier


Imports

Home Sections:

import { PlugAndPlaySection } from "./home/PlugAndPlaySection";
import { HowItWorksSection } from "./home/HowItWorksSection";
import { LanguageSelectorSection } from "./home/LanguageSelectorSection";
import { HeroSection } from "./home/HeroSection";
import { VideoDemoSection } from "./home/VideoDemoSection";
import { OurAiAgents } from "./home/OurAiAgents";
import { OurVision } from "./home/OurVision";
import { UseCases } from "./home/UseCases";

Commented:

// import { CTASection } from './home/CTASection'

Missing: TryFreeAgentSection (referenced in map but not imported)


Usage

From Homepage:

import { DynamicPageRenderer } from "@/components/DynamicPageRenderer";

// In MachinePage
const page = await getPageData(); // Strapi API call
return <DynamicPageRenderer sections={page.sections} />;

Flexibility

Add New Section:

  1. Create section component in ./home/
  2. Import in DynamicPageRenderer
  3. Add mapping in componentMap
  4. Configure in Strapi CMS

No Code Changes to Homepage!


Issues/Limitations

1. Array Index as Key:

<Component key={index} {...section} />
  • Not stable for reordering
  • Should use unique ID from Strapi

2. Silent Failures:

if (!Component) return null;
  • No warning for unknown components
  • Hard to debug missing mappings

3. Type Safety:

type Props = { sections: any[] };
  • any type loses type checking
  • Should define Strapi section interface

4. TryFreeAgentSection:

  • Commented in map
  • Never imported
  • Dead code reference

3. GlobalVideoController Component

File: src/components/GlobalVideoController.tsx (36 lines)
Type: Client Component
Purpose: Initialize global video management system

🎬 Video Orchestrator - App-Wide Coordination


Component Type

Invisible Component:

return null; // This component doesn't render anything

Purpose: Side effects only (initialization)


Initialization Logic

useEffect(() => {
  const initializeVideoManager = async () => {
    // Dynamic import for client-side only
    const { default: globalVideoManager } = await import(
      "@/utils/globalVideoManager"
    );

    // Wait for DOM to be ready
    const waitForDOM = () => {
      if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", () => {
          setTimeout(() => {
            globalVideoManager.initialize();
          }, 500); // 500ms delay
        });
      } else {
        setTimeout(() => {
          globalVideoManager.initialize();
        }, 500); // 500ms delay
      }
    };

    waitForDOM();
  };

  initializeVideoManager();
}, []);

Key Features

1. Dynamic Import:

await import("@/utils/globalVideoManager");
  • Client-side only
  • Code splitting
  • Prevents SSR issues

2. DOM Ready Check:

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', ...)
} else {
  setTimeout(...)
}

3. Initialization Delay:

setTimeout(() => globalVideoManager.initialize(), 500);
  • 500ms delay
  • Ensures all components mounted
  • Prevents race conditions

Dependency

Global Video Manager:

  • Import: @/utils/globalVideoManager
  • Purpose: Coordinate video playback across app
  • Method: initialize() - Set up global event system

Usage

App Layout:

import GlobalVideoController from "@/components/GlobalVideoController";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <GlobalVideoController />
        {children}
      </body>
    </html>
  );
}

Placement: Top level, before app content


Design Pattern

Initialization Component:

  • Mounts once
  • Runs side effects
  • Returns null (no UI)
  • Common pattern for global setup

Issues/Limitations

1. Hardcoded Delay:

setTimeout(() => globalVideoManager.initialize(), 500);
  • 500ms may be too long/short
  • No verification components are ready

2. No Error Handling:

const { default: globalVideoManager } = await import(
  "@/utils/globalVideoManager"
);
globalVideoManager.initialize();
  • Import failure not caught
  • Initialize errors not handled

3. No Cleanup:

useEffect(() => {
  initializeVideoManager();
}, []);
// No return statement
  • Should clean up manager on unmount
  • May cause memory leaks

4. Single Initialization:

  • No re-initialization logic
  • Could be stale after navigation

Summary Comparison

Component Lines Purpose Complexity Render
AboutUsVideo 133 Video player ⭐⭐⭐⭐ Video + Button
DynamicPageRenderer 38 CMS mapper ⭐⭐⭐ Dynamic sections
GlobalVideoController 36 Video init ⭐⭐ null (invisible)

Common Patterns

  1. Client Components - All use "use client" directive
  2. useEffect Hooks - Side effects on mount
  3. Dynamic Imports - Code splitting for video utils
  4. Cleanup Functions - Return statements in useEffect
  5. Type Safety - TypeScript interfaces defined

Dependencies

React:

  • useRef, useState, useEffect

External:

  • react-icons/hi2 - Speaker icon

Internal:

  • @/utils/mobileVideoManager - Single video control
  • @/utils/globalVideoManager - App-wide coordination
  • ./home/* - Section components

"Three components, three purposes, one goal: seamless video experience."