> ## Documentation Index
> Fetch the complete documentation index at: https://devperez08-platform-list-anime-54.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# User Profile & Settings

> Manage your EpiNeko profile, view statistics, and customize your account settings

## Overview

EpiNeko provides a comprehensive profile system where users can view their anime statistics, manage account settings, and track their viewing habits. The profile system is built on Supabase Auth with custom profile data stored in a separate profiles table.

<CardGroup cols={2}>
  <Card title="Profile Dashboard" icon="chart-simple">
    View personalized stats and viewing analytics
  </Card>

  <Card title="Account Settings" icon="gear">
    Update profile information and preferences
  </Card>

  <Card title="Library Stats" icon="book-open">
    Track progress across different watch statuses
  </Card>

  <Card title="Activity Summary" icon="clock-rotate-left">
    Monitor viewing efficiency and completion rates
  </Card>
</CardGroup>

## Profile Structure

User profiles extend Supabase Auth with additional metadata:

```typescript theme={null}
interface Profile {
  id: string;              // UUID matching auth.users.id
  username: string;        // Unique username
  full_name: string;       // Display name
  avatar_url: string;      // Profile picture URL
  website: string;         // Optional website link
  updated_at: string;      // Last modification timestamp
}
```

**Database Schema** (supabase/migrations/20260218\_initial\_schema.sql:2-11):

```sql theme={null}
create table public.profiles (
  id uuid references auth.users on delete cascade not null primary key,
  updated_at timestamp with time zone,
  username text unique,
  full_name text,
  avatar_url text,
  website text,
  
  constraint username_length check (char_length(username) >= 3)
);
```

## Profile Statistics

### Calculated Metrics

The profile page calculates real-time statistics from your library:

```typescript theme={null}
interface ProfileStats {
  total: number;          // Total anime in library
  watching: number;       // Currently watching
  completed: number;      // Finished series
  planToWatch: number;    // Plan to watch
  dropped: number;        // Dropped series
}
```

**Implementation** (src/app/profile/page.tsx:48-56):

```typescript theme={null}
const library = await getLibrary();

const stats = {
  total: library.length,
  watching: library.filter(i => i.status === 'watching').length,
  completed: library.filter(i => i.status === 'completed').length,
  planToWatch: library.filter(i => i.status === 'plan_to_watch').length,
  dropped: library.filter(i => i.status === 'dropped').length,
};
```

### Viewing Efficiency

Track completion rate as a percentage:

```typescript theme={null}
const efficiency = stats.total > 0 
  ? Math.round((stats.completed / stats.total) * 100) 
  : 0;

console.log(`Completion rate: ${efficiency}%`);
```

**Display** (src/app/profile/page.tsx:157-163):

* Large percentage display
* "completado versus total" subtitle
* Visual prominence on profile page

## Profile Page Components

### Profile Card

The left sidebar displays user identity and basic info:

<Steps>
  <Step title="Avatar">
    Gradient-bordered circular avatar with fallback to first letter of name
  </Step>

  <Step title="Display Name">
    Shows `full_name` or email username as fallback
  </Step>

  <Step title="Username">
    Displays @username handle
  </Step>

  <Step title="Member Since">
    Shows account creation date in Spanish format
  </Step>

  <Step title="Edit Button">
    Links to settings page for profile updates
  </Step>
</Steps>

**Code Reference** (src/app/profile/page.tsx:92-132):

```tsx theme={null}
<div className="w-32 h-32 rounded-full bg-gradient-to-tr from-primary to-purple-600 p-1">
  <div className="w-full h-full rounded-full bg-zinc-900 flex items-center justify-center">
    {profile?.avatar_url ? (
      <img src={profile.avatar_url} alt={profile.full_name} />
    ) : (
      <span className="text-4xl font-black text-white italic">
        {profile?.full_name?.charAt(0) || user.email?.charAt(0).toUpperCase()}
      </span>
    )}
  </div>
</div>
```

### Statistics Cards

Four summary cards showing key metrics:

<CardGroup cols={2}>
  <Card title="Total Animes" icon="chart-simple">
    Total count of all library items

    Icon: 📊
  </Card>

  <Card title="Viendo" icon="tv">
    Currently watching count

    Icon: 📺
  </Card>

  <Card title="Completado" icon="check">
    Completed series count

    Icon: ✅
  </Card>

  <Card title="Pendiente" icon="clock">
    Plan to watch count

    Icon: ⏳
  </Card>
</CardGroup>

**Component** (src/app/profile/page.tsx:195-202):

```tsx theme={null}
function StatCard({ label, value, icon }: { label: string, value: number, icon: string }) {
  return (
    <div className="bg-zinc-900/50 backdrop-blur-md rounded-2xl p-5 border border-white/5 
                    hover:border-primary/50 transition-all group">
      <div className="text-2xl mb-2">{icon}</div>
      <div className="text-2xl font-black text-white italic">{value}</div>
      <div className="text-zinc-500 text-xs font-bold uppercase">{label}</div>
    </div>
  );
}
```

### Activity Summary

Visual progress bars showing distribution across statuses:

<Tabs>
  <Tab title="Progress Bars">
    ```tsx theme={null}
    <ProgressBar label="Viendo" value={stats.watching} total={stats.total} color="bg-primary" />
    <ProgressBar label="Completado" value={stats.completed} total={stats.total} color="bg-green-500" />
    <ProgressBar label="Plan para ver" value={stats.planToWatch} total={stats.total} color="bg-yellow-500" />
    <ProgressBar label="Abandonado" value={stats.dropped} total={stats.total} color="bg-red-500" />
    ```
  </Tab>

  <Tab title="Implementation">
    ```tsx theme={null}
    function ProgressBar({ label, value, total, color }) {
      const percentage = total > 0 ? (value / total) * 100 : 0;
      
      return (
        <div className="space-y-1.5">
          <div className="flex justify-between">
            <span className="text-zinc-400">{label}</span>
            <span className="text-white">{value}</span>
          </div>
          <div className="w-full h-2 bg-zinc-800 rounded-full">
            <div 
              className={`h-full ${color} transition-all duration-1000`} 
              style={{ width: `${percentage}%` }}
            />
          </div>
        </div>
      );
    }
    ```

    Located at src/app/profile/page.tsx:205-220
  </Tab>
</Tabs>

### Quick Navigation

Two action cards linking to main sections:

1. **Mi Biblioteca** - Navigate to library page
2. **Descubrir** - Navigate to discovery/homepage

**Design** (src/app/profile/page.tsx:168-187):

* Hover effects with color transitions
* Icon arrows with background color change
* Subtle descriptions of each section

## Settings Page

### Editable Profile Fields

Users can update their profile information:

<ParamField path="username" type="string" required>
  Unique identifier, minimum 3 characters

  **Validation:** Database constraint enforces uniqueness and length
</ParamField>

<ParamField path="full_name" type="string">
  Display name shown throughout the app
</ParamField>

<ParamField path="website" type="string">
  Optional personal website URL
</ParamField>

<ParamField path="avatar_url" type="string">
  URL to profile picture image
</ParamField>

### Updating Profile

Profile updates use Supabase upsert for insert-or-update:

```typescript theme={null}
const updateProfile = async () => {
  const { data: { user } } = await supabase.auth.getUser();

  const updates = {
    id: user?.id,
    full_name: fullname,
    username,
    website,
    avatar_url: avatarUrl,
    updated_at: new Date().toISOString(),
  };

  const { error } = await supabase.from('profiles').upsert(updates);

  if (error) throw error;
};
```

**Implementation Details** (src/app/settings/page.tsx:57-82):

* Uses `upsert` to handle both insert and update
* Updates `updated_at` timestamp automatically
* Displays success/error messages to user
* Disables button during save operation

### Form Components

Settings form with controlled inputs:

```tsx theme={null}
<input 
  type="text" 
  placeholder="usuario_ninja" 
  className="input input-bordered bg-zinc-950 border-white/10 
             focus:border-primary focus:ring-1 focus:ring-primary" 
  value={username}
  onChange={(e) => setUsername(e.target.value)}
/>
```

**Features** (src/app/settings/page.tsx:113-169):

* Username field with validation hint
* Email field (disabled, cannot be changed)
* Save button with loading state
* Success/error alert messages
* Back to profile navigation button

## Security Features

### Row-Level Security

Profile access is controlled by PostgreSQL policies:

```sql theme={null}
-- Anyone can view profiles
create policy "Public profiles are viewable by everyone." on public.profiles
  for select using (true);

-- Users can only insert their own profile
create policy "Users can insert their own profile." on public.profiles
  for insert with check (auth.uid() = id);

-- Users can only update their own profile
create policy "Users can update own profile." on public.profiles
  for update using (auth.uid() = id);
```

**Security Guarantees** (supabase/migrations/20260218\_initial\_schema.sql:16-23):

* Users cannot modify other users' profiles
* Profile creation is automatic via trigger
* All operations validated at database level

### Automatic Profile Creation

New users automatically get a profile via database trigger:

```sql theme={null}
create or replace function public.handle_new_user()
returns trigger as $$
begin
  insert into public.profiles (id, full_name, username, avatar_url)
  values (
    new.id, 
    new.raw_user_meta_data->>'full_name', 
    new.raw_user_meta_data->>'username',
    new.raw_user_meta_data->>'avatar_url'
  );
  return new;
end;
$$ language plpgsql security definer;

create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();
```

**How It Works** (supabase/migrations/20260218\_initial\_schema.sql:26-42):

1. User signs up via Supabase Auth
2. Trigger fires after user creation
3. Profile row is automatically inserted
4. Metadata from signup is copied to profile

## Authentication Flow

### Checking Login Status

Profile and settings pages verify authentication:

```typescript theme={null}
const { data: { user } } = await supabase.auth.getUser();

if (!user) {
  // Redirect to login or show message
  return (
    <div>
      <h2>Debes iniciar sesión para ver tu perfil</h2>
      <Link href="/login">Iniciar Sesión</Link>
    </div>
  );
}
```

**Implementation** (src/app/profile/page.tsx:76-83):

* Checks for authenticated user
* Shows login prompt if not authenticated
* Displays loading spinner during check

### Fetching User Data

Combine auth data with profile data:

```typescript theme={null}
useEffect(() => {
  const fetchData = async () => {
    // Get authenticated user
    const { data: { user } } = await supabase.auth.getUser();
    
    if (user) {
      setUser(user);
      
      // Fetch profile data
      const { data: profileData } = await supabase
        .from('profiles')
        .select('*')
        .eq('id', user.id)
        .single();
      
      setProfile(profileData);
      
      // Fetch library stats
      const library = await getLibrary();
      // Calculate stats...
    }
  };
  
  fetchData();
}, []);
```

## Loading States

Both pages implement proper loading states:

<CodeGroup>
  ```tsx Profile Loading theme={null}
  if (isLoading) {
    return (
      <div className="min-h-screen bg-zinc-950 flex items-center justify-center">
        <span className="loading loading-spinner loading-lg text-primary"></span>
      </div>
    );
  }
  ```

  ```tsx Settings Loading theme={null}
  {saving ? (
    <button disabled className="btn btn-primary">
      Guardando...
    </button>
  ) : (
    <button className="btn btn-primary">
      GUARDAR CAMBIOS
    </button>
  )}
  ```
</CodeGroup>

## Message System

Settings page shows feedback messages:

```typescript theme={null}
const [message, setMessage] = useState({ type: '', text: '' });

// After save success
setMessage({ 
  type: 'success', 
  text: '¡Perfil actualizado correctamente!' 
});

// After error
setMessage({ 
  type: 'error', 
  text: error.message || 'Error al actualizar el perfil' 
});
```

**Display** (src/app/settings/page.tsx:153-158):

```tsx theme={null}
{message.text && (
  <div className={`alert ${
    message.type === 'success' 
      ? 'alert-success bg-green-500/10 border-green-500/50' 
      : 'alert-error bg-red-500/10 border-red-500/50'
  }`}>
    <span>{message.text}</span>
  </div>
)}
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Username Validation" icon="shield-check">
    Validate username format client-side before submission

    ```typescript theme={null}
    if (username.length < 3) {
      alert('Username must be at least 3 characters');
      return;
    }
    ```
  </Card>

  <Card title="Optimistic Updates" icon="bolt">
    Update UI immediately while save is in progress
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation">
    Display user-friendly error messages for all failure cases
  </Card>

  <Card title="Loading States" icon="spinner">
    Always show loading indicators during async operations
  </Card>
</CardGroup>

## Related Features

* [Library Management](/features/library-management) - Stats source
* [Authentication](/authentication) - Login system
* [Database Schema](/integration/database-schema) - Table structures
