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)¶
- isMobile (
boolean) - Device type detection - 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.mp4poster- Dynamic (desktop:/image.webp, mobile:/imagemob.webp)muted- Opposite ofisPlayingstateautoPlay- Disabled (user-initiated only)loop- Enabled (continuous playback)playsInline- iOS compatibilitypreload="metadata"- Minimal preloadingcontrols- 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-labelon 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:
image,video,altTextdefined 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¶
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:
- Map through sections array
- Look up component by
__componentfield - Return null if component not registered
- Spread section data as props to component
- Use array index as key
Strapi Integration¶
Expected Section Structure:
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:
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:
- Create section component in
./home/ - Import in DynamicPageRenderer
- Add mapping in
componentMap - Configure in Strapi CMS
No Code Changes to Homepage!
Issues/Limitations¶
1. Array Index as Key:
- Not stable for reordering
- Should use unique ID from Strapi
2. Silent Failures:
- No warning for unknown components
- Hard to debug missing mappings
3. Type Safety:
anytype 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:
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:
- Client-side only
- Code splitting
- Prevents SSR issues
2. DOM Ready Check:
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', ...)
} else {
setTimeout(...)
}
3. Initialization Delay:
- 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:
- 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:
- 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¶
- Client Components - All use
"use client"directive - useEffect Hooks - Side effects on mount
- Dynamic Imports - Code splitting for video utils
- Cleanup Functions - Return statements in useEffect
- 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."