Skip to main content

Component Architecture

EpiNeko uses client-side React components with TypeScript for type safety. All components are organized by feature area and follow consistent patterns.

Anime Components

AnimeCard

Display component for anime thumbnails in lists and grids.
interface AnimeCardProps {
  mal_id: number;      // MyAnimeList ID
  image: string;       // Poster image URL
  title: string;       // Anime title
  score?: string;      // Rating score (optional)
  onClick?: () => void; // Click handler (optional)
}
Features:
  • Hover animations with scale and shadow effects
  • Rating badge overlay on hover
  • Fallback image for loading errors
  • Responsive sizing (w-44 on mobile, w-56 on desktop)
  • 2:3 aspect ratio for consistent layout
Usage Example:
import AnimeCard from '@/components/anime/AnimeCard';

<AnimeCard
  mal_id={anime.mal_id}
  image={anime.images.webp.large_image_url}
  title={anime.title}
  score={anime.score?.toString()}
  onClick={() => handleAnimeClick(anime)}
/>
The component includes automatic error handling that displays a placeholder with the anime title if the image fails to load.

AnimeDetailsModal

Full-screen modal for displaying detailed anime information.
interface AnimeDetailsModalProps {
  isOpen: boolean;
  onClose: () => void;
  anime: {
    mal_id: number;
    title: string;
    image: string;
    backdrop?: string;
    synopsis: string;
    score: string;
    episodes: string;
    status: string;
    genres: string[];
  } | null;
}
Features:
  • Backdrop blur with fade-in animation
  • Responsive two-column layout (poster + details)
  • Genre tags display
  • Library integration (add/remove functionality)
  • Auto-dismiss on backdrop click
  • Loading states during library operations
Implementation Details: The modal checks library status on mount and provides add/remove functionality:
// Library status check
useEffect(() => {
  if (isOpen && anime) {
    const item = await getLibraryItem(anime.mal_id);
    setIsInLibrary(!!item);
  }
}, [isOpen, anime]);
Key Sections:
  1. Left Side: Anime poster with gradient overlay
  2. Right Side: Title, metadata, genres, synopsis, and action buttons
  3. Close Button: Positioned top-right with circular styling
The modal requires user authentication for library operations. Unauthenticated users see an alert when attempting to add anime.

EpisodeList

Interactive episode list with watch progress tracking.
interface EpisodeListProps {
  animeId: number;        // Jikan API anime ID
  totalEpisodes: number | null; // Total episode count
  title: string;          // Anime title
  imageUrl: string;       // Cover image URL
}
Features:
  • Fetches episode data from Jikan API
  • Tracks watched episodes count
  • Checkmark toggle for episode completion
  • Visual distinction for watched episodes (emerald theme)
  • Automatic library creation on first interaction
  • Progress counter display
  • Handles missing episode data gracefully
State Management:
const [episodes, setEpisodes] = useState<JikanEpisode[]>([]);
const [watchedCount, setWatchedCount] = useState<number>(0);
const [isInLibrary, setIsInLibrary] = useState(false);
Episode Toggle Logic:
const handleToggleEpisode = async (episodeNum: number) => {
  const newCount = watchedCount === episodeNum ? episodeNum - 1 : episodeNum;
  const isCompleted = totalEpisodes ? newCount >= totalEpisodes : false;
  const newStatus: LibraryStatus = isCompleted ? 'completed' : 'watching';
  
  // Optimistic UI update
  setWatchedCount(newCount);
  
  // Persist to database
  await updateLibraryItem(animeId, { 
    episodes_watched: newCount,
    status: newStatus
  });
};
Episode marking requires authentication. The component shows an alert if a user attempts to mark episodes without logging in.

LibraryButton

Comprehensive library management button with status dropdown.
interface LibraryButtonProps {
  animeId: number;   // Jikan API anime ID
  title: string;     // Anime title
  imageUrl: string;  // Cover image URL
}
Features:
  • Add/remove from library
  • Status dropdown (watching, completed, plan_to_watch, dropped)
  • Visual feedback for library state
  • Loading states during operations
  • Automatic page refresh after updates
Available Statuses:
type LibraryStatus = 'watching' | 'completed' | 'plan_to_watch' | 'dropped';

const statusLabels: Record<LibraryStatus, string> = {
  watching: '📺 VIENDO',
  completed: '✅ COMPLETADO',
  plan_to_watch: '⏳ PENDIENTE',
  dropped: '❌ ABANDONADO'
};
Button States:
  1. Not in Library: Shows ”➕ AÑADIR A MI LISTA” (primary button)
  2. In Library: Shows ”✓ EN MI LISTA” (success outline) + status dropdown
Usage Example:
<LibraryButton
  animeId={anime.mal_id}
  title={anime.title}
  imageUrl={anime.images.webp.large_image_url}
/>

Autocomplete search with live results from Jikan API. Features:
  • Debounced search (500ms delay)
  • Live suggestions with anime posters
  • Click-outside to close
  • Loading indicator
  • Links to anime detail pages
  • “View all results” footer link
Implementation:
// Debounced search effect
useEffect(() => {
  const timer = setTimeout(async () => {
    if (query.length > 2) {
      setIsLoading(true);
      const res = await searchAnime(query);
      setResults(res.data.slice(0, 5)); // Show top 5 results
      setIsOpen(true);
    }
  }, 500);
  
  return () => clearTimeout(timer);
}, [query]);
Result Display:
  • Anime poster thumbnail (10x14 pixels)
  • Title in bold
  • Type and score metadata
  • Hover states for better UX
The search activates after typing 3+ characters to reduce API calls and improve performance.

Layout Components

Responsive navigation bar with authentication state.
interface NavbarProps {
  user: User | null; // Supabase user object
}
Features:
  • Fixed positioning with scroll-based styling
  • Background blur on scroll
  • Logo with hover animations
  • Desktop navigation links (Inicio, Tendencias, Mi Lista)
  • Integrated SearchBar
  • User dropdown menu or Login button
  • Profile avatar with initial letter
Scroll Effect:
const [scrolled, setScrolled] = useState(false);

useEffect(() => {
  const handleScroll = () => {
    const isScrolled = window.scrollY > 10;
    setScrolled(isScrolled);
  };
  
  window.addEventListener("scroll", handleScroll);
  return () => window.removeEventListener("scroll", handleScroll);
}, []);
User Menu Items:
  • Profile (with icon)
  • Settings (with icon)
  • Sign Out (red theme)

Site footer with navigation and branding. Sections:
  1. Logo & Description: Brand identity and tagline
  2. Quick Links: Navigation to main pages
  3. Legal: Terms and Privacy links
  4. Bottom Bar: Copyright and attribution
Responsive Layout:
  • Single column on mobile
  • 4-column grid on desktop
  • 2 columns for logo section

MainLayout

Root layout wrapper component. Responsibilities:
  • Renders Navbar with current user
  • Wraps children with main content area
  • Renders Footer
  • Fetches user authentication state from Supabase
export default async function MainLayout({ children }: { children: ReactNode }) {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();

  return (
    <>
      <Navbar user={user} />
      <main className="pt-20">
        {children}
      </main>
      <Footer />
    </>
  );
}

Component Patterns

Client Components

All interactive components use "use client" directive

TypeScript Props

Every component has a typed props interface

Error Handling

Graceful fallbacks for API failures and missing data

Loading States

Visual feedback during async operations

Styling Conventions

1

Tailwind Utilities

Primary styling method with responsive modifiers
2

DaisyUI Components

Pre-styled buttons, dropdowns, and badges
3

Custom Animations

Smooth transitions with Tailwind animate utilities
4

Dark Theme

Zinc color palette with primary accent color

Best Practices

  • Keep components focused on a single responsibility
  • Extract reusable logic into custom hooks
  • Use TypeScript interfaces for all props
  • Document complex prop structures
  • Use useState for component-local state
  • Fetch data in useEffect hooks
  • Implement optimistic UI updates for better UX
  • Handle loading and error states explicitly
  • Debounce expensive operations (search, API calls)
  • Use useCallback for event handlers passed to children
  • Implement click-outside detection with refs
  • Clean up event listeners in useEffect returns

Services

Learn about API integration layers

Project Structure

Understand the codebase organization