{"id":50297185,"url":"https://github.com/erickdc7/sorai-app","last_synced_at":"2026-05-28T09:34:37.726Z","repository":{"id":346020103,"uuid":"1164401219","full_name":"erickdc7/sorai-app","owner":"erickdc7","description":"A modern, full-stack web application to explore, search, and organize your personal anime list. Built with Next.js 14, Supabase, and the Jikan API.","archived":false,"fork":false,"pushed_at":"2026-05-17T18:57:16.000Z","size":2187,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-17T20:53:12.358Z","etag":null,"topics":["anime-tracker","jikan-api","lucide-react","nextjs","responsive-design","sonner","tailwindcss","typescript","vercel-deployment"],"latest_commit_sha":null,"homepage":"https://sorai-app.vercel.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/erickdc7.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-23T03:19:15.000Z","updated_at":"2026-05-17T18:57:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/erickdc7/sorai-app","commit_stats":null,"previous_names":["erickdc7/sorai-app"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/erickdc7/sorai-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erickdc7%2Fsorai-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erickdc7%2Fsorai-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erickdc7%2Fsorai-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erickdc7%2Fsorai-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/erickdc7","download_url":"https://codeload.github.com/erickdc7/sorai-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erickdc7%2Fsorai-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33603475,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anime-tracker","jikan-api","lucide-react","nextjs","responsive-design","sonner","tailwindcss","typescript","vercel-deployment"],"created_at":"2026-05-28T09:34:36.185Z","updated_at":"2026-05-28T09:34:37.711Z","avatar_url":"https://github.com/erickdc7.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sorai — Your Personal Anime Tracker\n\nA modern, full-stack web application to explore, search, and organize your personal anime list. Built with Next.js 14, Supabase, and the Jikan API.\n\n![Next.js](https://img.shields.io/badge/Next.js-14-black?logo=next.js)\n![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript\u0026logoColor=white)\n![Tailwind CSS](https://img.shields.io/badge/Tailwind_CSS-3-06B6D4?logo=tailwindcss\u0026logoColor=white)\n![Supabase](https://img.shields.io/badge/Supabase-Auth_%2B_DB-3FCF8E?logo=supabase\u0026logoColor=white)\n\n![Sorai Preview](public/images/preview.png)\n\n---\n\n## Features\n\n### Browse \u0026 Discover\n- **Hero Carousel** — Rotating spotlight of featured anime on the home page\n- **Browse Categories** — Popular, Seasonal, Upcoming, Top Airing, Movies, OVAs, ONAs, Specials\n- **Season Archives** — Browse anime by season and year (Winter, Spring, Summer, Fall)\n- **Genre Filtering** — Action, Romance, Shounen, Sci-Fi, Fantasy, and more\n- **Search** — Full-text search with pagination and estimated total count\n\n### Anime Detail\n- Full synopsis, trailer, characters with voice actors, and episode list\n- **Related Anime** and **Similar Anime** carousels with progressive loading\n- **Theme Songs** — Clickable openings/endings that search YouTube in a new tab\n- Add to list, change status, rate with score (1–10)\n\n### Personal Anime List\n- **Grid view** and **List view** with status-based title hover colors\n- Filter and search within your list\n- Status management: Watching, Completed, On Hold, Dropped, Plan to Watch\n- Score rating with interactive dropdowns\n\n### User Settings\n- Profile management: avatar upload/remove, username and email update\n- Password change\n- Sensitive content toggle (integrates with Jikan API `sfw` parameter)\n- Data export (JSON) of your full anime list\n- Account deactivation\n\n### Performance \u0026 Reliability\n- **Rate-Limited Fetch Queue** — Automatic request throttling (~3 req/s) prevents 429 errors from the Jikan API\n- **API Caching** — `sessionStorage` caching layer with 10-minute TTL\n- **Progressive Loading** — Skeleton states and `fetchSequential` with `onProgress` callbacks for incremental UI updates\n- **Deduplication** — Overfetch + dedup logic ensures unique cards per page\n- **Supabase Singleton** — Single client instance shared across the entire app via AuthContext\n- **Suspense Boundaries** — Proper wrapping for `useSearchParams()` to pass Next.js build requirements\n\n### Security\n- **Row Level Security (RLS)** — Users can only access their own data\n- **Input Validation** — Client-side validation for usernames, emails, passwords, search queries, and file uploads\n- **XSS Protection** — All user-generated content is safely rendered\n- **Session Validation** — Protected API routes verify authentication\n- **Secure File Uploads** — MIME type, size, and extension checks for avatar uploads\n\n---\n\n## Tech Stack\n\n| Technology | Purpose |\n|---|---|\n| [Next.js 14](https://nextjs.org/) (App Router) | React framework with hybrid rendering |\n| [TypeScript](https://www.typescriptlang.org/) | Static type checking |\n| [Tailwind CSS 3](https://tailwindcss.com/) | Utility-first styling with CSS custom properties |\n| [Supabase](https://supabase.com/) | PostgreSQL database + Authentication + Storage |\n| [Jikan API v4](https://jikan.moe/) | Anime data from MyAnimeList — no API key required |\n| [Lucide React](https://lucide.dev/) | Icon library |\n| [Sonner](https://sonner.emilkowal.dev/) | Toast notifications |\n\n---\n\n## Project Structure\n\n```\nsrc/\n├── app/\n│   ├── layout.tsx                  # Root layout (fonts, AuthProvider, Navbar, Footer, Toaster)\n│   ├── page.tsx                    # Home — Hero carousel + Popular + Seasonal\n│   ├── globals.css                 # CSS custom properties + Tailwind config\n│   ├── browse/page.tsx             # Browse by category, genre, or season archive\n│   ├── search/page.tsx             # Search with pagination \u0026 estimated results\n│   ├── anime/[id]/page.tsx         # Anime detail (synopsis, trailer, characters, etc.)\n│   ├── my-list/page.tsx            # Personal anime list (private route)\n│   ├── settings/page.tsx           # User settings \u0026 account management\n│   └── api/update-email/route.ts   # Server-side email update endpoint\n│\n├── components/\n│   ├── Navbar.tsx                  # Navigation bar with search \u0026 user menu\n│   ├── Footer.tsx                  # Site footer with quick links\n│   ├── HeroCarousel.tsx            # Animated hero banner on home page\n│   ├── AnimeCard.tsx               # Reusable anime card (with React.memo)\n│   ├── AnimeCardSkeleton.tsx       # Skeleton loading placeholder\n│   ├── AnimeGridSkeleton.tsx       # Grid of skeleton cards\n│   ├── AnimeHorizontalCarousel.tsx # Horizontal scrollable carousel\n│   ├── Pagination.tsx              # Reusable pagination with ellipsis\n│   ├── AuthModal.tsx               # Auth modal shell (delegates to forms)\n│   ├── LoginForm.tsx               # Login form component\n│   ├── RegisterForm.tsx            # Registration form component\n│   ├── DeleteConfirmModal.tsx      # Deletion confirmation dialog\n│   ├── anime-detail/              # Anime detail sub-components\n│   │   ├── AnimeHeroBanner.tsx     #   Hero section with poster \u0026 actions\n│   │   ├── AnimeSynopsis.tsx       #   Synopsis section\n│   │   ├── AnimeTrailer.tsx        #   YouTube trailer embed\n│   │   ├── AnimeCharacters.tsx     #   Characters grid with VAs\n│   │   ├── AnimeEpisodes.tsx       #   Episode list\n│   │   └── AnimeInfoSidebar.tsx    #   Info card + theme songs\n│   ├── my-list/                   # My-list sub-components\n│   │   ├── GridCard.tsx            #   Card for grid view\n│   │   └── ListCard.tsx            #   Card for list view with dropdowns\n│   └── settings/                  # Settings sub-components\n│       ├── ProfilePhotoSection.tsx  #   Avatar upload/remove\n│       ├── AccountSection.tsx       #   Username \u0026 email management\n│       ├── PasswordSection.tsx      #   Password change\n│       ├── PreferencesSection.tsx   #   Sensitive content toggle\n│       └── DangerZoneSection.tsx    #   Export data \u0026 deactivation\n│\n├── hooks/\n│   ├── useAnimeDetail.ts           # Data fetching for anime detail page\n│   └── useAnimeListActions.ts      # Add/update/remove anime list actions\n│\n├── constants/\n│   ├── anime-status.ts             # Status labels, colors, and order\n│   └── filters.ts                  # Type filters (TV, Movie, OVA, etc.)\n│\n├── context/\n│   └── AuthContext.tsx              # Global auth provider (Supabase singleton)\n│\n├── lib/\n│   ├── supabase.ts                 # Supabase client (lazy singleton)\n│   ├── jikan.ts                    # Jikan API wrapper with cache \u0026 rate limiting\n│   ├── mappers.ts                  # Data transformation (mapToCardData, dedup)\n│   ├── rate-limit.ts               # API rate limit middleware\n│   ├── user-anime-list.ts          # CRUD operations for user anime list\n│   ├── user-profile.ts             # User profile management\n│   └── validators.ts               # Input validation (search, auth, uploads)\n│\n├── types/\n│   ├── anime.ts                    # App types (AnimeStatus, UserAnimeListItem)\n│   └── jikan.ts                    # Jikan API response types\n│\n└── middleware.ts                   # Next.js middleware (session refresh)\n```\n\n---\n\n## Getting Started\n\n### Prerequisites\n\n- **Node.js** 18 or higher\n- **npm** (included with Node.js)\n- A [Supabase](https://supabase.com) account (free tier works)\n\n### 1. Clone and install\n\n```bash\ngit clone https://github.com/erickdc7/sorai-app.git\ncd sorai-app\nnpm install\n```\n\n### 2. Set up Supabase\n\n#### 2.1 Create a project\n\n1. Go to [app.supabase.com](https://app.supabase.com)\n2. Create a new project (or use an existing one)\n3. Wait for initialization to complete\n\n#### 2.2 Run the database schema\n\n1. In your Supabase dashboard, go to **SQL Editor**\n2. Copy and paste the contents of `supabase/schema.sql`\n3. Execute the query — this creates the required tables with RLS policies\n\n#### 2.3 Disable email confirmation (Important)\n\n\u003e **⚠️ Without this step, registration won't work as expected.** Users will be created but won't be able to sign in until they confirm their email.\n\n1. In Supabase, go to **Authentication** → **Providers** → **Email**\n2. **Disable** the **\"Confirm email\"** option\n3. Save changes\n\nThis allows users to register and sign in immediately without email verification.\n\n#### 2.4 Get your API credentials\n\n1. Go to **Settings** → **API** in your Supabase project\n2. Copy:\n   - **Project URL** → `NEXT_PUBLIC_SUPABASE_URL`\n   - **anon public key** → `NEXT_PUBLIC_SUPABASE_ANON_KEY`\n\n### 3. Configure environment variables\n\nCreate a `.env.local` file in the project root:\n\n```env\nNEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co\nNEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJI...your_key_here\n```\n\n### 4. Run in development\n\n```bash\nnpm run dev\n```\n\nThe application will be available at **http://localhost:3000**\n\n### 5. Production build (optional)\n\n```bash\nnpm run build\nnpm start\n```\n\n---\n\n## Pages\n\n| Route | Access | Description |\n|---|---|---|\n| `/` | Public | Home — Hero carousel, popular anime, current season |\n| `/browse?type=popular` | Public | Most popular anime of all time |\n| `/browse?type=season` | Public | Currently airing anime |\n| `/browse?type=upcoming` | Public | Upcoming anime |\n| `/browse?type=airing` | Public | Top airing anime |\n| `/browse?type=movies` | Public | Top rated anime movies |\n| `/browse?type=ova` | Public | Original Video Animations |\n| `/browse?type=ona` | Public | Original Net Animations |\n| `/browse?type=special` | Public | Special episodes |\n| `/browse?type=season-archive\u0026year=2026\u0026season=winter` | Public | Season archive |\n| `/browse?genre=1` | Public | Browse by genre |\n| `/search?q=term` | Public | Full-text search with pagination |\n| `/anime/[id]` | Public | Anime detail page |\n| `/my-list` | Private | User's personal anime list |\n| `/settings` | Private | Account settings \u0026 preferences |\n\n---\n\n## Authentication\n\n- **Register** with email, password, and username (with real-time validation)\n- **Sign in** with email and password\n- Private routes (`/my-list`, `/settings`) redirect to home if no session exists\n- User avatar displays profile photo or initial of username\n- Account deactivation prevents sign-in and immediately signs out\n\n---\n\n## External APIs\n\n### Jikan API v4\n\n| | |\n|---|---|\n| **Base URL** | `https://api.jikan.moe/v4` |\n| **Authentication** | None (free public API) |\n| **Rate Limit** | 3 req/s — handled with built-in rate-limiting fetch queue |\n\n**Endpoints used:**\n\n| Endpoint | Purpose |\n|---|---|\n| `/top/anime` | Popular and top airing anime |\n| `/seasons/now` | Current season anime |\n| `/seasons/upcoming` | Upcoming anime |\n| `/seasons/{year}/{season}` | Season archives |\n| `/anime?q=` | Full-text search |\n| `/anime/{id}/full` | Complete anime details |\n| `/anime/{id}/characters` | Character list with voice actors |\n| `/anime/{id}/episodes` | Episode listing |\n| `/anime/{id}/relations` | Related anime |\n| `/anime/{id}/recommendations` | Similar anime recommendations |\n| `/anime?genres={id}` | Browse by genre |\n\n---\n\n## Database\n\n### `user_anime_list`\n\nStores the user's personal anime tracking data.\n\n```sql\nuser_anime_list\n├── id (UUID, PK)\n├── user_id (UUID, FK → auth.users)\n├── mal_id (INTEGER)\n├── status (TEXT: watching | completed | paused | dropped | planned)\n├── score (INTEGER, 1–10, nullable)\n├── anime_title (TEXT)\n├── anime_image_url (TEXT)\n├── anime_year (INTEGER)\n├── anime_type (TEXT)\n└── created_at (TIMESTAMP)\n```\n\n### `user_profiles`\n\nStores user profile data and preferences.\n\n```sql\nuser_profiles\n├── id (UUID, PK, FK → auth.users)\n├── username (TEXT)\n├── avatar_url (TEXT, nullable)\n├── show_sensitive_content (BOOLEAN, default false)\n├── deactivated_at (TIMESTAMP, nullable)\n├── created_at (TIMESTAMP)\n└── updated_at (TIMESTAMP)\n```\n\nRow Level Security (RLS) ensures each user can only read and modify their own data.\n\n---\n\n## Architecture\n\n### Component Composition\n\nPages are composed from focused, single-responsibility sub-components:\n\n- **Anime Detail** — `AnimeHeroBanner` + `AnimeSynopsis` + `AnimeTrailer` + `AnimeCharacters` + `AnimeEpisodes` + `AnimeInfoSidebar`\n- **Auth Modal** — `AuthModal` (shell) → `LoginForm` / `RegisterForm`\n- **My List** — `GridCard` + `ListCard` (extracted from the page)\n- **Settings** — `ProfilePhotoSection` + `AccountSection` + `PasswordSection` + `PreferencesSection` + `DangerZoneSection`\n\n### Shared Layout\n\n`Navbar`, `AuthModal`, and `Footer` are rendered once in `layout.tsx` — pages only define their unique content.\n\n### Data Flow\n\n```\nAuthContext (Supabase singleton + user state)\n    ├── Pages consume via useAuth()\n    ├── Custom hooks encapsulate data fetching\n    │   ├── useAnimeDetail — core + relations + recommendations\n    │   └── useAnimeListActions — add / update / remove with optimistic updates\n    └── lib/ handles API communication\n        ├── jikan.ts — cached, rate-limited Jikan API calls\n        ├── user-anime-list.ts — Supabase CRUD\n        └── user-profile.ts — profile management\n```\n\n### Rate Limiting Strategy\n\nThe Jikan API allows ~3 requests per second. Sorai handles this with:\n\n1. **`sessionStorage` cache** (10-min TTL) — avoids redundant requests entirely\n2. **`rateLimitedFetch()`** — enforces a minimum 334ms gap between API calls\n3. **`fetchSequential()`** — processes enrichment loops one-by-one with `onProgress` callbacks for progressive UI updates\n4. **`getAnimeByIdThrottled()`** — rate-limited variant of `getAnimeById` for enrichment loops\n\n---\n\n## Design System\n\nAll colors are defined as CSS custom properties in `globals.css` for consistency and easy theming:\n\n- **Primary palette** — Purple theme (`--color-primary`, `--color-primary-light`, etc.)\n- **Status colors** — Watching (green), Completed (purple), On Hold (yellow), Dropped (red), Plan to Watch (blue)\n- **Browse categories** — Each category has its own icon and background color token\n- **Season icons** — Winter (❄️ sky), Spring (🌸 pink), Summer (☀️ amber), Fall (🍂 orange)\n- **Typography** — Nunito Sans (body), Bebas Neue (headings), Marck Script (logo)\n\n---\n\n## Troubleshooting\n\n| Problem | Solution |\n|---|---|\n| \"Invalid supabaseUrl\" | Check that `.env.local` has valid URLs (`https://...`) |\n| Can't sign in after registering | Disable \"Confirm email\" in Supabase Authentication → Email |\n| Images not loading | Check internet connection (images come from `cdn.myanimelist.net`) |\n| 429 API error | Wait a few seconds and retry — the rate-limiting queue handles this automatically for most cases |\n| Port 3000 in use | Next.js will automatically use 3001 |\n| Build error with `useSearchParams` | Ensure components using it are wrapped in a `\u003cSuspense\u003e` boundary |\n| Font override warning | `adjustFontFallback: false` is set for Nunito Sans |\n\n---\n\n## License\n\nThis project is for personal and educational use. Anime data is provided by the [Jikan API](https://jikan.moe/) (unofficial MyAnimeList API).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferickdc7%2Fsorai-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferickdc7%2Fsorai-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferickdc7%2Fsorai-app/lists"}