Skip to main content
The portfolio uses a modular component architecture built with React and TypeScript. Components are organized into three main categories: layout components, section components, and UI primitives.

Component Structure

The component directory is organized as follows:
src/components/
├── ui/                    # shadcn/ui primitives
│   ├── button.tsx
│   ├── accordion.tsx
│   ├── tooltip.tsx
│   ├── toast.tsx
│   ├── toaster.tsx
│   ├── sonner.tsx
│   └── use-toast.ts
├── AboutSection.tsx       # About section with dynamic stats
├── ContactSection.tsx     # Contact form and info
├── DifferentialsSection.tsx
├── ErrorBoundary.tsx      # Error handling wrapper
├── Footer.tsx
├── Header.tsx             # Fixed navigation header
├── HeroSection.tsx        # Landing section with video
├── MarqueeSection.tsx     # Scrolling marquee
├── NavLink.tsx            # Navigation link component
├── ProjectsSection.tsx    # Portfolio projects grid
├── ResumeSection.tsx      # Resume/CV section
├── ScrollReveal.tsx       # Scroll animation wrapper
├── SkillsSection.tsx      # Skills display
├── WhatsAppButton.tsx     # Floating WhatsApp chat
└── WhySiteSection.tsx     # Benefits section

Core Layout Components

Fixed navigation header with scroll-based glassmorphism effect and mobile menu.
import { useState, useEffect } from "react";
import { Menu, X } from "lucide-react";
import { Button } from "@/components/ui/button";

const Header = () => {
  const [isScrolled, setIsScrolled] = useState(false);
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);

  useEffect(() => {
    const handleScroll = () => {
      setIsScrolled(window.scrollY > 50);
    };
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  return (
    <header className={`fixed top-0 z-[100] transition-all ${
      isScrolled ? "glass py-3" : "py-4 md:py-6"
    }`}>
      {/* Navigation content */}
    </header>
  );
};
Key Features:
  • Scroll-triggered glassmorphism effect
  • Responsive mobile menu with hamburger icon
  • Smooth anchor link navigation
  • Fixed positioning with z-index layering
Section footer with social links and branding.
import ScrollReveal from "./ScrollReveal";

const Footer = () => (
  <footer className="py-8 border-t border-border">
    <ScrollReveal>
      {/* Footer content with social links */}
    </ScrollReveal>
  </footer>
);

Section Components

HeroSection

Landing section featuring an intro video with play button and memoji fallback.
const HeroSection = () => {
  const [hasPlayed, setHasPlayed] = useState(false);
  const [showPlayButton, setShowPlayButton] = useState(true);
  const videoRef = useRef(null);

  const handlePlayClick = () => {
    if (videoRef.current) {
      videoRef.current.currentTime = 0;
      videoRef.current.play();
      setShowPlayButton(false);
    };
  };

  return (
    <video
      ref={videoRef}
      playsInline
      onEnded={() => setHasPlayed(true)}
      className="w-full h-full object-cover"
    >
      <source src={introVideo} type="video/mp4" />
    </video>
  );
};
Features:
  • IntersectionObserver for video autoplay control
  • Custom play button overlay
  • Fallback to static memoji after video ends
  • Animated entrance with staggered delays
  • Social media icon links

AboutSection

Dynamic about section with real-time GitHub API integration and photo toggle.
AboutSection.tsx
const AboutSection = () => {
  const [yearsOfExperience, setYearsOfExperience] = useState("1.5");
  const [projectsCount, setProjectsCount] = useState("15");
  const [showRealPhoto, setShowRealPhoto] = useState(false);

  useEffect(() => {
    // Calculate years of experience from start date
    const startDate = new Date("2024-01-01");
    const diffInMonths = /* calculation */;
    setYearsOfExperience((diffInMonths / 12).toFixed(1));
  }, []);

  useEffect(() => {
    // Fetch GitHub repository count
    fetch('https://api.github.com/users/AndreRuperto')
      .then(res => res.json())
      .then(user => setProjectsCount(user.public_repos));
  }, []);
};
Features:
  • Auto-calculated years of experience
  • GitHub API integration for project count
  • Toggle between memoji and professional photo
  • ScrollReveal animations for entrance effects

WhatsAppButton

Floating WhatsApp chat widget with typing indicator animation.
WhatsAppButton.tsx
const WhatsAppButton = () => {
  const [isOpen, setIsOpen] = useState(false);
  const [isTyping, setIsTyping] = useState(false);
  const [showMessage, setShowMessage] = useState(false);

  useEffect(() => {
    if (isOpen) {
      setIsTyping(true);
      setTimeout(() => {
        setIsTyping(false);
        setShowMessage(true);
      }, 2500);
    }
  }, [isOpen]);

  return (
    <button className="fixed bottom-6 right-6 z-50 group">
      {/* WhatsApp icon with pulse animation */}
    </button>
  );
};
Features:
  • Floating button with bounce animation
  • Chat window with WhatsApp background image
  • Typing indicator with animated dots
  • Message bubble with timestamp and read receipts
  • Direct WhatsApp web integration

Error Handling

ErrorBoundary

React error boundary component for graceful error handling.
ErrorBoundary.tsx
import { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode;
}

interface State {
  hasError: boolean;
}

class ErrorBoundary extends Component<Props, State> {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: any) {
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

Application Setup

The main App component sets up global providers and routing:
App.tsx
import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

const App = () => (
  <QueryClientProvider client={queryClient}>
    <TooltipProvider>
      <Toaster />
      <Sonner />
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Index />} />
          <Route path="/admin/login" element={<AdminLogin />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </BrowserRouter>
    </TooltipProvider>
  </QueryClientProvider>
);
Global Providers:
  • QueryClientProvider - React Query for data fetching
  • TooltipProvider - Radix UI tooltip context
  • Toaster & Sonner - Toast notification systems
  • BrowserRouter - React Router navigation

Next Steps

UI Library

Explore the shadcn/ui components used throughout the app

Animations

Learn about ScrollReveal and animation patterns

Build docs developers (and LLMs) love