Created
November 11, 2025 07:32
-
-
Save sorrycc/a80674c9a783fd7fdc554176ee407cbc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Inline Name Editing on /me Page | |
| **Date:** 2025-11-11 | |
| **Feature:** Support user to update name inline on the /me page | |
| --- | |
| ## Requirements | |
| - **Interaction Pattern:** Always editable input field with auto-save on blur | |
| - **Validation:** Required field with 1-50 character limit | |
| - **Error Handling:** Show inline error message below input and keep invalid value for correction | |
| --- | |
| ## Architecture Overview | |
| The implementation adds inline editing to the name field using client-side state management with explicit save/revert behavior. | |
| ### Key Components | |
| - **UI Layer:** Replace the read-only `<p>` tag with an `<Input>` component that's always visible | |
| - **State Management:** Three local states in the component: | |
| - `editingName`: current draft value in the input | |
| - `savedName`: last successfully saved value (for revert on error) | |
| - `nameError`: validation/API error message (null when valid) | |
| - **API Layer:** New endpoint `src/app/api/me/name/route.ts` handling PATCH requests | |
| - **Database:** Update via Drizzle ORM to users table | |
| ### User Flow | |
| 1. User sees name in an always-editable input field | |
| 2. User types to modify the name | |
| 3. On blur (clicking away), trigger validation | |
| 4. If valid: call API, update session, clear error | |
| 5. If invalid/failed: show error below input, revert to `savedName` | |
| ### Visual Feedback | |
| - Subtle border styling to indicate it's editable | |
| - Loading state during API call (disabled input + spinner) | |
| - Inline error message in red text below input when validation/save fails | |
| --- | |
| ## Component Implementation Details | |
| ### State & Initialization | |
| ```typescript | |
| const [editingName, setEditingName] = useState(session.user.name || '') | |
| const [savedName, setSavedName] = useState(session.user.name || '') | |
| const [nameError, setNameError] = useState<string | null>(null) | |
| const [isSavingName, setIsSavingName] = useState(false) | |
| ``` | |
| ### Validation Logic | |
| - Check length: 1-50 characters | |
| - Trim whitespace before validation | |
| - Show error: "Name must be between 1 and 50 characters" | |
| ### onBlur Handler Flow | |
| 1. Check if value changed (compare with `savedName`) | |
| 2. If unchanged, do nothing | |
| 3. Trim and validate the input | |
| 4. If invalid: set `nameError`, revert `editingName` to `savedName` | |
| 5. If valid: call API endpoint | |
| 6. On API success: update `savedName`, refresh session via `update()`, clear error | |
| 7. On API failure: set `nameError` with server message, revert `editingName` to `savedName` | |
| ### UI Replacement | |
| Replace the current name display section with an Input component, include: | |
| - Label stays the same | |
| - Input with value bound to `editingName` | |
| - onChange updates `editingName` | |
| - onBlur triggers save handler | |
| - Disabled state when `isSavingName` is true | |
| - Error message div below input (only shown when `nameError` exists) | |
| --- | |
| ## API Endpoint Implementation | |
| ### New File | |
| `src/app/api/me/name/route.ts` | |
| ### Endpoint | |
| `PATCH /api/me/name` | |
| ### Request Body | |
| ```typescript | |
| { name: string } | |
| ``` | |
| ### Implementation Flow | |
| 1. Get session via `getServerSession(authOptions)` | |
| 2. If no session: return 401 Unauthorized | |
| 3. Extract and trim name from request body | |
| 4. Validate: 1-50 characters | |
| 5. If invalid: return 400 with error message | |
| 6. Update database using Drizzle: | |
| ```typescript | |
| await db.update(users) | |
| .set({ name: trimmedName }) | |
| .where(eq(users.email, session.user.email)) | |
| ``` | |
| 7. Return 200 with success message and updated name | |
| ### Error Handling | |
| - 401: "Unauthorized" | |
| - 400: "Name must be between 1 and 50 characters" | |
| - 500: "Failed to update name" (for database errors) | |
| ### Response Format | |
| ```typescript | |
| { success: true, name: string, message?: string } | |
| ``` | |
| --- | |
| ## Error Handling & Edge Cases | |
| ### Client-Side Error Scenarios | |
| 1. **Validation Error (empty/too long):** | |
| - Show inline error below input | |
| - Revert input to `savedName` | |
| - User can try again by clicking into input | |
| 2. **Network/API Error:** | |
| - Show inline error with server message | |
| - Revert input to `savedName` | |
| - Input remains enabled for retry | |
| 3. **Session Refresh After Success:** | |
| - Call `await update()` to sync NextAuth session | |
| - This ensures session.user.name reflects the new value | |
| - Handles case where user navigates away before session updates | |
| ### Edge Cases | |
| - **Rapid blur events:** Disable input during save (`isSavingName`) to prevent concurrent updates | |
| - **Session expires during edit:** API returns 401, error shown inline | |
| - **Name unchanged:** Skip API call if `editingName === savedName` after trim | |
| - **Component unmounts during save:** React handles cleanup, no special action needed | |
| ### Visual States Summary | |
| - Default: Input with subtle border | |
| - Focused: Input with highlighted border (existing Input component styling) | |
| - Saving: Disabled input with loading spinner/text | |
| - Error: Red text below input with error message | |
| - Success: Input returns to default state with new saved value | |
| --- | |
| ## Decision Log | |
| ### Approach Selection: Client-Side State Management | |
| **Chosen:** Approach A - Client-Side State Management | |
| **Alternatives Considered:** | |
| - **Approach B:** Optimistic Update with Rollback - More complex, better UX | |
| - **Approach C:** Extend Existing `/api/me` Endpoint - More RESTful | |
| **Rationale:** Simple, predictable implementation with clear save/error states. Session refresh via `update()` handles state synchronization adequately. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment