diff --git a/skill/erika/SKILL.md b/skill/erika/SKILL.md index e69de29..1a19875 100644 --- a/skill/erika/SKILL.md +++ b/skill/erika/SKILL.md @@ -0,0 +1,277 @@ +--- +name: erika-project +description: Project-specific rules, conventions, and file reference for the Erika website (linktree + custom form system). +--- + +## 1. Think Before Coding + +**Don't assume. Don't hide confusion. Surface tradeoffs.** + +Before implementing: +- State your assumptions explicitly. If uncertain, ask. +- If multiple interpretations exist, present them - don't pick silently. +- If a simpler approach exists, say so. Push back when warranted. +- If something is unclear, stop. Name what's confusing. Ask. + +## 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +## 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you notice unrelated dead code, mention it - don't delete it. + +When your changes create orphans: +- Remove imports/variables/functions that YOUR changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: Every changed line should trace directly to the user's request. + +## 4. Goal-Driven Execution + +**Define success criteria. Loop until verified.** + +Transform tasks into verifiable goals: +- "Add validation" → "Write tests for invalid inputs, then make them pass" +- "Fix the bug" → "Write a test that reproduces it, then make it pass" +- "Refactor X" → "Ensure tests pass before and after" + +For multi-step tasks, state a brief plan: +``` +1. [Step] → verify: [check] +2. [Step] → verify: [check] +3. [Step] → verify: [check] +``` + +Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. + +## 5. UI and Design + +**Leverage existing design systems. Avoid custom CSS.** + +- Build all UI exclusively using ShadCN components. +- Rely on the built-in component styling; it's already well-designed. +- Do not add custom CSS or excessive utility classes unless absolutely necessary. +- Avoid excessive inline styles on HTML elements (e.g., `
`). Keep styling clean and minimal to prevent an "AI slop" appearance. + +## 6. Type Checking + +**Use lightweight tools for validation. Avoid full builds.** + +- Do not run `bun build` or `npm run build` solely to check for type errors. +- Use `npx tsc --noEmit` instead for fast and focused type validation. + +## 7. Docker Compose + +**Standardize on Docker Compose. Avoid standalone Docker commands.** + +- Use Docker Compose exclusively for container management. +- Map external ports explicitly to the internal application port `3000` (e.g., `"8710:3000"`). +- Keep the `docker-compose.yml` file clean and well-organized. + +## 8. Commands + +**Avoid inline code execution.** + +- Do not pass code strings directly to execution tools (e.g., `bun -e 'console.log("hello world")'`). +- Always write scripts to dedicated files before executing them. + +## 9. Database + +**Use standard tools for external databases.** + +- Assume the use of an external database for persistent storage. +- Use Drizzle ORM exclusively for database interactions. + +## 10. Explicit Approval for Plans + +**Achieve alignment before execution.** + +- Ensure the implementation plan is completely clear and mutually understood. +- Do not begin executing the plan until explicit approval (e.g., "let's do it") is given by the user. + +## 11. Communication Style + +**Be concise and professional. No fluff.** + +- Keep responses short, direct, and straightforward. +- Do not use emojis, unnecessary pleasantries, or filler words. +- Maintain a clean and simple text format. + +--- + +## Project Overview + +Erika is a Next.js website for the content creator "Erika!". It has two main features: + +1. **Linktree** (`/`) — A landing page showing profile info, social links, and live follower counts from YouTube, Roblox, Discord, and TikTok. +2. **Custom Form System** (`/form`, `/admin/form`) — A full form builder where admins create forms with various question types, and users submit responses via Discord OAuth login. + +### Tech Stack + +- **Runtime**: Bun +- **Framework**: Next.js (App Router) +- **Database**: PostgreSQL via Drizzle ORM +- **Cache**: Redis (Bun's built-in `RedisClient`) +- **Auth**: NextAuth.js with Discord OAuth provider +- **UI**: ShadCN components + Tailwind CSS +- **Fonts**: Anuphan (Thai), Geist Sans (Latin) +- **Deployment**: Docker Compose + Nginx reverse proxy + +--- + +## Environment Variables (`.env`) + +| Variable | Purpose | +|---|---| +| `DATABASE_URL` | PostgreSQL connection string | +| `NEXTAUTH_SECRET` | Secret for signing NextAuth sessions | +| `NEXTAUTH_URL` | Canonical URL of the site (e.g. `https://erika.sudloh.com`) | +| `REDIS_URL` | Redis connection string, used for caching follower counts and tracking unique visitors | +| `BASE_URL` | Public base URL, used in metadata/SEO | +| `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET` | Discord OAuth app credentials for user login | +| `ADMIN_DISCORD_IDS` | **Comma-separated list of Discord user IDs that have admin access.** These users can access `/admin/form` to create, edit, and delete forms. Any Discord user NOT in this list is treated as a regular user. | +| `DISCORD_BOT_TOKEN` | Bot token used to fetch Discord server member count | +| `DISCORD_GUILD_ID` | Discord server ID to fetch member count from | +| `YOUTUBE_API_KEY` / `YOUTUBE_CHANNEL_ID` | YouTube Data API credentials to fetch subscriber count | +| `ROBLOX_USER_ID` | Roblox user ID to fetch follower count | +| `TIKTOK_CLIENT_KEY` / `TIKTOK_CLIENT_SECRET` | TikTok Official API credentials | +| `TIKTOK_ACCESS_TOKEN` / `TIKTOK_REFRESH_TOKEN` / `TIKTOK_OPEN_ID` | TikTok OAuth tokens (obtained via one-time flow at `/api/auth/tiktok`) | + +--- + +## File & Directory Reference + +### `lib/` — Shared Libraries + +| File | Purpose | +|---|---| +| `lib/config.ts` | **Linktree configuration for the root page (`/`).** Defines the profile (name, description, avatar) and the list of social links (Roblox, Discord, TikTok, YouTube) displayed on the landing page. Also defines `config_p2` which adds the Form link. This does NOT configure the form system. | +| `lib/icons.tsx` | SVG icon components for social platforms (Roblox, Discord, TikTok, YouTube, Form) | +| `lib/redis.ts` | Redis client singleton using Bun's built-in `RedisClient`. Used for caching follower counts (1hr TTL) and tracking unique visitors. | +| `lib/utils.ts` | Utility functions (e.g. `cn()` for className merging) | + +### `lib/followers/` — Follower Count Fetchers + +| File | Purpose | +|---|---| +| `lib/followers/index.ts` | Aggregates all platform follower counts with Redis caching (1hr TTL). Exports `getFollowerCounts()` used by the root page. | +| `lib/followers/discord.ts` | Fetches Discord server member count via bot API | +| `lib/followers/youtube.ts` | Fetches YouTube subscriber count via YouTube Data API | +| `lib/followers/roblox.ts` | Fetches Roblox follower count via public API | +| `lib/followers/tiktok.ts` | Fetches TikTok follower count and profile via TikTok Official API. Auto-refreshes expired tokens. | + +### `db/` — Database Layer + +| File | Purpose | +|---|---| +| `db/index.ts` | Drizzle ORM client initialization, exports `db` | +| `db/schema.ts` | Barrel file re-exporting all schemas | +| `db/schema/account.ts` | NextAuth account tables (`user`, `account`, `session`, `verificationToken`) in the `account` PostgreSQL schema. Users have a `discordId` field. | +| `db/schema/form.ts` | Form system tables in the `form` PostgreSQL schema: `forms` (form metadata, webhook config), `questions` (question definitions with types: text/textarea/radio/checkbox), `submissions` (user submissions with edit history), `answers` (individual answers) | + +### `app/` — Routes + +#### Root & Global + +| File | Purpose | +|---|---| +| `app/layout.tsx` | Root layout. Sets up fonts (Anuphan + Geist), theme provider, tooltip provider, toaster, page progress bar, and SEO metadata. | +| `app/page.tsx` | **Linktree landing page (`/`).** Displays profile avatar (from TikTok API or fallback), social links with live follower counts, unique visitor counter, and a video background. | +| `app/globals.css` | Global CSS styles | + +#### `/form` — Public Form System (user-facing) + +| File | Purpose | +|---|---| +| `app/form/page.tsx` | Lists all **open** forms for users to fill out | +| `app/form/layout.tsx` | Form section layout with header, login state, and theme toggle. Shows a login dialog if the user is not authenticated. | +| `app/form/client.tsx` | Client-side form components (user avatar dropdown, sign out, etc.) | +| `app/form/[id]/page.tsx` | Individual form submission page where users answer questions | +| `app/form/[id]/layout.tsx` | Layout for individual form pages | + +#### `/admin/form` — Admin Form Management (admin-only) + +| File | Purpose | +|---|---| +| `app/admin/form/page.tsx` | **Admin form list.** Shows all forms (open and closed) with create/edit/delete actions. Protected by admin auth check — redirects non-admins to `/`. | +| `app/admin/form/[id]/layout.tsx` | **Admin form editor layout.** Contains the admin auth guard (checks `getServerSession` against `ADMIN_DISCORD_IDS`). Redirects non-admins to `/form`. Includes sidebar navigation. | +| `app/admin/form/[id]/page.tsx` | Admin form editor page | +| `app/admin/form/[id]/client.tsx` | Client-side admin form editor components (question builder, drag-and-drop, settings) | +| `app/admin/form/[id]/result/` | Admin view of form submission results | +| `app/admin/form/[id]/extra/` | Additional admin form settings | + +#### `/discord` — Discord Redirect + +| File | Purpose | +|---|---| +| `app/discord/page.tsx` | Simple redirect to the Discord server invite link | + +#### `/api` — API Routes + +| File | Purpose | +|---|---| +| `app/api/auth/[...nextauth]/route.ts` | NextAuth.js configuration with Discord OAuth provider. Exports `authOptions`. | +| `app/api/auth/tiktok/route.ts` | Initiates TikTok OAuth flow (one-time setup to get tokens) | +| `app/api/auth/tiktok/callback/route.ts` | TikTok OAuth callback handler | +| `app/api/upload/route.ts` | File upload endpoint (admin-only, checks `ADMIN_DISCORD_IDS`) | + +#### `app/actions/` — Server Actions + +| File | Purpose | +|---|---| +| `app/actions/form.ts` | Server actions for form CRUD (create, update settings, delete). All protected by admin auth check. | +| `app/actions/questions.ts` | Server actions for question CRUD within a form. Admin-protected. | +| `app/actions/submissions.ts` | Server actions for form submissions (submit, edit, view results). | + +### `components/` — UI Components + +| File | Purpose | +|---|---| +| `components/LinkCard.tsx` | Social link card component with follower count badges | +| `components/ShaderBackground.tsx` | WebGL shader background effect | +| `components/login-dialog.tsx` | Discord OAuth login dialog | +| `components/rich-text-editor.tsx` | Rich text editor for form descriptions | +| `components/page-progress.tsx` | Page navigation progress bar | +| `components/theme-toggle.tsx` | Light/dark theme toggle | +| `components/theme-provider.tsx` | next-themes provider wrapper | +| `components/ui/` | ShadCN component library (button, card, table, badge, sidebar, etc.) | + +### Config & Infrastructure + +| File | Purpose | +|---|---| +| `drizzle.config.ts` | Drizzle Kit config — points to schema, outputs to `drizzle/`, uses schemas: `public`, `account`, `form` | +| `docker-compose.yml` | Runs the app + Nginx reverse proxy via Docker Compose with `network_mode: host` | +| `proxy.ts` | Replaces `middleware.ts` in modern Next.js. Currently a pass-through for `/form/:path*` routes — auth is handled via dialog in the layout, not here. | +| `nginx.conf` | Nginx config for reverse proxying to the Next.js app | +| `next.config.ts` | Next.js configuration | +| `Dockerfile` | Container build config | + +--- + +## Auth Model + +1. **User login**: Discord OAuth via NextAuth.js. Users log in at `/form` to submit forms. +2. **Admin check**: Server-side only. Compares the logged-in user's `discordId` (from session) against the comma-separated `ADMIN_DISCORD_IDS` env var. This pattern is repeated in: + - `app/admin/form/page.tsx` (form list) + - `app/admin/form/[id]/layout.tsx` (form editor) + - `app/actions/form.ts` (form CRUD actions) + - `app/actions/questions.ts` (question CRUD actions) + - `app/api/upload/route.ts` (file uploads) +3. **No middleware.ts**: Modern Next.js replaces `middleware.ts` with `proxy.ts`. The project has a `proxy.ts` that matches `/form/:path*` but currently just passes through. Auth checks are done per-page/per-action in server components and server actions. +