Skip to main content

Overview

EpiNeko’s library management system allows users to organize their anime collection across different viewing statuses. The library is built on top of Supabase with row-level security, ensuring each user’s data is private and secure.

Add to Library

Save anime to your collection with a single click

Update Status

Change viewing status as you progress through series

Track Progress

Monitor episodes watched and completion percentage

Remove Items

Clean up your library by removing unwanted entries

Library Item Structure

Each item in your library contains comprehensive information about the anime and your viewing progress:
export interface LibraryItem {
  id?: string;                    // Unique identifier (UUID)
  user_id?: string;               // User who owns this item
  anime_id_jikan: number;         // MyAnimeList ID from Jikan API
  title: string;                  // Anime title
  image_url?: string;             // Poster image URL
  status: LibraryStatus;          // Current viewing status
  score?: number;                 // User rating (0-10)
  episodes_watched?: number;      // Progress tracking
}

export type LibraryStatus = 'watching' | 'completed' | 'dropped' | 'plan_to_watch';

Core Functions

Adding to Library

Add a new anime to your personal collection using the addToLibrary function:
import { addToLibrary, LibraryItem } from '@/services/library';

// Add an anime with initial status
const newItem: LibraryItem = {
  anime_id_jikan: 52991,
  title: "Sousou no Frieren",
  image_url: "https://cdn.myanimelist.net/images/anime/...",
  status: 'watching',
  episodes_watched: 5
};

const result = await addToLibrary(newItem);
The user_id is automatically populated from the authenticated user’s session. Users must be logged in to add items to their library.
Implementation Details (src/services/library.ts:16-30):
  • Retrieves the current user from Supabase Auth
  • Inserts the item into the user_library table
  • Returns the created item with all fields populated
  • Throws an error if the user is not authenticated

Updating Library Items

Change the status or progress of an existing library item:
import { updateLibraryItem } from '@/services/library';

// Update viewing status
await updateLibraryItem(52991, { 
  status: 'completed',
  score: 9,
  episodes_watched: 28
});

// Update only specific fields
await updateLibraryItem(52991, { 
  episodes_watched: 15 
});
Function Signature (src/services/library.ts:46-61):
export const updateLibraryItem = async (
  animeIdJikan: number, 
  updates: Partial<LibraryItem>
) => Promise<LibraryItem>

Removing from Library

Remove an anime from your library completely:
import { removeFromLibrary } from '@/services/library';

// Remove by Jikan anime ID
await removeFromLibrary(52991);
Implementation (src/services/library.ts:32-44):
  • Deletes the item matching both user_id and anime_id_jikan
  • Ensures users can only remove their own items
  • No error thrown if item doesn’t exist

Retrieving Library Data

Fetch your entire library or a specific item:
import { getLibrary } from '@/services/library';

// Get all library items for the current user
const items = await getLibrary();

// Items are sorted by most recently updated
console.log(`You have ${items.length} anime in your library`);
Returns all items ordered by updated_at descending (src/services/library.ts:63-72).

UI Components

LibraryButton Component

The LibraryButton component provides a complete interface for managing library items with status dropdown:
import LibraryButton from '@/components/anime/LibraryButton';

<LibraryButton 
  animeId={52991}
  title="Sousou no Frieren"
  imageUrl="https://cdn.myanimelist.net/images/anime/..."
/>
1

Initial State

When not in library, displays ”+ AÑADIR A MI LISTA” button
2

Added State

After adding, shows ”✓ EN MI LISTA” and a status dropdown appears
3

Status Selection

Users can change between watching, completed, plan to watch, and dropped
4

Removal

Clicking the main button when in library removes the item
Status Labels (src/components/anime/LibraryButton.tsx:73-78):
  • 📺 VIENDO (watching)
  • ✅ COMPLETADO (completed)
  • ⏳ PENDIENTE (plan_to_watch)
  • ❌ ABANDONADO (dropped)

Library Page

The library page displays all items in a responsive grid layout:
  • Responsive grid (2-6 columns based on screen size)
  • Loading skeletons during data fetch
  • Empty state with call-to-action
  • Direct links to anime detail pages
  • Sorted by most recently updated

Database Schema

The library is backed by a PostgreSQL table with row-level security:
create table public.user_library (
  id uuid default gen_random_uuid() primary key,
  user_id uuid references public.profiles(id) on delete cascade not null,
  anime_id_jikan integer not null,
  title text not null,
  image_url text,
  status public.library_status default 'watching' not null,
  score integer check (score >= 0 and score <= 10),
  episodes_watched integer default 0,
  created_at timestamp with time zone default timezone('utc'::text, now()) not null,
  updated_at timestamp with time zone default timezone('utc'::text, now()) not null,
  
  unique(user_id, anime_id_jikan)
);
The unique constraint on (user_id, anime_id_jikan) prevents duplicate entries. Attempting to add the same anime twice will result in a database error.
Security Policies (supabase/migrations/20260218_initial_schema.sql:65-75):
  • Users can only view, insert, update, and delete their own library items
  • All operations are protected by auth.uid() = user_id checks
  • Row-level security is enabled on the table

Error Handling

All library functions properly handle authentication and database errors:
try {
  await addToLibrary(item);
} catch (error) {
  if (error.message.includes('logged in')) {
    // User is not authenticated
    alert('Please log in to add items to your library');
  }
}

Best Practices

Optimistic Updates

Update the UI immediately before API calls complete for better UX
setIsInLibrary(true); // Update UI
await addToLibrary(item); // Then sync

Error Rollback

Revert optimistic updates if operations fail
try {
  // Update
} catch (error) {
  setIsInLibrary(false); // Rollback
}

Check Before Add

Use getLibraryItem to check if an anime is already in the library before attempting to add it

Partial Updates

Only include changed fields in updateLibraryItem calls to minimize data transfer