Skip to content

Enhanced Profile Edit with Robust Validation & Error Handling#36

Open
simplicityf wants to merge 4 commits intoPACTO-LAT:developfrom
simplicityf:feature/profiledetails
Open

Enhanced Profile Edit with Robust Validation & Error Handling#36
simplicityf wants to merge 4 commits intoPACTO-LAT:developfrom
simplicityf:feature/profiledetails

Conversation

@simplicityf
Copy link

@simplicityf simplicityf commented Jan 30, 2026

📋 Summary

Implements a production-ready profile edit feature with comprehensive Zod validation, intelligent error handling, optimistic UI updates, and excellent user experience.

🎫 Related Issue

Closes #32

What's Changed

Core Features

  • Zod Schema Validation - Client-side validation for all profile fields before submission
  • Real-time Field Validation - Instant feedback as users type with visual error indicators
  • Unique Constraint Checking - Pre-validation of username, email, and Stellar address uniqueness
  • Comprehensive Error Handling - User-friendly messages for validation, network, and database errors
  • Optimistic UI Updates - Immediate feedback with automatic rollback on failure
  • Enhanced UX - Loading states, toast notifications, character counters, and accessibility features

Screenshots

pro1 profile2

Files Added

apps/web/lib/schemas/profile-validation.schema.ts    # Zod validation schemas
apps/web/lib/services/enhanced-auth.service.ts       # Enhanced auth service

Files Modified

apps/web/components/profile/ProfileInfo.tsx          # Enhanced with validation UI
apps/web/app/dashboard/profile/page.tsx              # Improved save handler

Summary by CodeRabbit

  • New Features

    • Real-time per-field validation with inline errors and loading indicators
    • Save flow with optimistic UI, loading spinner, success toast, and revert-on-failure
    • Cancel action to discard local edits
  • Improvements

    • More user-friendly error messages and comprehensive validation feedback
    • Richer profile editing experience with extended fields and organized payment-methods structure

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Jan 30, 2026

@simplicityf is attempting to deploy a commit to the Trustless Work Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

Adds Zod-based profile validation, a new EnhancedAuthService with structured profile update/error codes, per-field real-time validation in ProfileInfo, optimistic save/cancel flow on the profile page, and payment_methods type changes to support multiple bank accounts.

Changes

Cohort / File(s) Summary
Profile page (save/cancel + validation)
apps/web/app/dashboard/profile/page.tsx
Renamed default export to EnhancedProfilePage; added validationErrors state, optimistic save flow (validate → optimistic update → persist → revert on failure), EnhancedAuthService usage, handleCancel, loading state, and inline validation error UI.
Profile form & field-level validation
apps/web/components/profile/ProfileInfo.tsx
Added per-field validation state and validating indicators, fieldErrors and validatingFields, handleFieldChange/handleFieldBlur, validateField integration, reusable input renderer, bio counter, and inline error UI with loader.
Validation schemas & field validators
apps/web/lib/schemas/profile-validation.schema.ts
New Zod schemas for profile update and nested settings, field-level validators (email, username, phone, country, stellar_address), validateProfileUpdate, and exported types for profile payloads.
Enhanced auth service (business logic + errors)
apps/web/lib/services/enhanced-auth.service.ts
New service with updateUserProfile (validates input, uniqueness checks, prepares payload, DB update), ProfileUpdateError codes, getErrorMessage, and validateField for real-time checks.
AuthService delegation
apps/web/lib/services/auth.ts
AuthService.updateUserProfile now delegates to EnhancedAuthService.updateUserProfile and updated signature to return Promise<User>.
Types: payment methods change
apps/web/lib/types.ts
User.payment_methods updated: removed single bank fields and added optional bank_accounts?: { bank_iban?: string; bank_name?: string; bank_account_holder?: string }[].
Misc (package file formatting)
apps/web/package.json
Minor whitespace change (removed trailing newline).

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as Browser/UI
    participant PI as ProfileInfo
    participant Auth as EnhancedAuthService
    participant DB as Database
    participant Toast as Toasts

    User->>UI: Edit fields
    UI->>PI: handleFieldChange / blur
    PI->>Auth: validateField(field, value)
    Auth-->>PI: validation result (ok / error)
    PI-->>UI: show field error or ok

    User->>UI: Click "Save"
    UI->>Auth: validateProfileUpdate(payload)
    alt validation fails
        Auth-->>UI: VALIDATION_ERROR (details)
        UI->>Toast: show validation errors
    else validation passes
        UI->>UI: apply optimistic UI update
        UI->>Auth: updateUserProfile(userId, payload)
        Auth->>DB: uniqueness checks + update
        alt unique constraint or validation failure
            DB-->>Auth: error
            Auth-->>UI: ProfileUpdateError (USERNAME/EMAIL/STELLAR_TAKEN or mapped code)
            UI->>UI: revert optimistic update
            UI->>Toast: show error message
        else success
            DB-->>Auth: updated user
            Auth-->>UI: updated user
            UI->>UI: persist UI changes, exit edit mode
            UI->>Toast: show success toast
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • Bran18

Poem

🐰 I hopped through fields and checked each line,

usernames and phones now validate fine,
I danced with Zod and policed every key,
Saved with a toast, then nibbled on tea,
Hop, save, cancel — my profile's carefree!

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarizes the main change: enhanced profile editing with robust validation and error handling, which aligns with the core objectives.
Linked Issues check ✅ Passed All objectives from issue #32 are met: validation layer via Zod schemas, database persistence with error handling, UI optimistic updates with rollback, JSONB field handling, and improved UX with loading states and toast notifications.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #32 objectives. The profile validation schema, enhanced auth service, profile info UI enhancements, and page-level save flow align with the stated goal of connecting the Edit Profile button to persist changes reliably.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/app/dashboard/profile/page.tsx (1)

114-131: ⚠️ Potential issue | 🟡 Minor

Avoid persisting empty bank account placeholders.
Because payment_methods is now part of the payload, any UI placeholder bank_accounts with empty strings will be persisted. Filtering empty entries before validation/update prevents inconsistent JSONB data.

🧹 Sanitize payment_methods before save
-      const payload = {
+      const sanitizedPaymentMethods = hydratedUserData.payment_methods
+        ? {
+            ...hydratedUserData.payment_methods,
+            bank_accounts:
+              hydratedUserData.payment_methods.bank_accounts?.filter(
+                (account) =>
+                  account.bank_iban ||
+                  account.bank_name ||
+                  account.bank_account_holder
+              ) ?? [],
+          }
+        : undefined;
+
+      const payload = {
         // Only persist email if user provided a non-empty value
         ...(hydratedUserData.email &&
           !hydratedUserData.email.endsWith('@wallet.local')
           ? { email: hydratedUserData.email }
           : {}),
         full_name: hydratedUserData.full_name,
         username: hydratedUserData.username,
         bio: hydratedUserData.bio,
         avatar_url: hydratedUserData.avatar_url,
         phone: hydratedUserData.phone,
         country: hydratedUserData.country,
         kyc_status: hydratedUserData.kyc_status,
         notifications: hydratedUserData.notifications,
         security: hydratedUserData.security,
-        payment_methods: hydratedUserData.payment_methods,
+        payment_methods: sanitizedPaymentMethods,
         stellar_address: hydratedUserData.stellar_address,
       } as const;
🤖 Fix all issues with AI agents
In `@apps/web/app/dashboard/profile/page.tsx`:
- Around line 281-285: Replace the unstable index key used in the
validationErrors rendering with a stable identifier: in the
validationErrors.map(...) callback (the mapping that returns the <li> elements
in page.tsx) change key={index} to use the error string (key={error}); if error
strings might not be unique, use a deterministic fallback like
key={`${error}-${index}`} to ensure stability without relying solely on the
array index.

In `@apps/web/lib/schemas/profile-validation.schema.ts`:
- Around line 161-167: The field-level email validator (email: (value: string)
=> { ... }) is missing the .max(255) constraint present in the schema; update
the validator to validate length as well by using
z.string().email().max(255).safeParse(value) so real-time validation matches the
schema and UX stays consistent with other fields like username.

In `@apps/web/lib/services/enhanced-auth.service.ts`:
- Around line 3-4: The validateField parameter is too broad (keyof User) and
allows read-only fields to be considered editable; change the parameter type in
the validateField function to keyof PartialProfileUpdateInput (instead of keyof
User) and update any imports so PartialProfileUpdateInput is imported alongside
validateProfileUpdate from the profile-validation schema; ensure calls/sites
that pass field values use only editable keys and that the validation call
(validateProfileUpdate) continues to run against the narrowed key type so
non-editable fields like reputation_score, total_trades, created_at, and
updated_at are excluded.
🧹 Nitpick comments (2)
apps/web/lib/schemas/profile-validation.schema.ts (1)

153-155: Prefer const arrow with explicit return type for validateProfileUpdate.
This keeps the helper consistent with the TS style guidelines used elsewhere in the repo.

♻️ Suggested refactor
-export function validateProfileUpdate(data: unknown) {
-    return partialProfileUpdateSchema.safeParse(data);
-}
+export const validateProfileUpdate = (
+    data: unknown
+): z.SafeParseReturnType<unknown, PartialProfileUpdateInput> =>
+    partialProfileUpdateSchema.safeParse(data);

As per coding guidelines, Prefer const arrow functions with explicit type annotations over function declarations.

apps/web/components/profile/ProfileInfo.tsx (1)

3-4: Use clsx for conditional class composition.
This keeps class logic clearer and aligns with the styling guideline.

🎨 Refactor conditional classes with clsx
-import { AlertCircle, Camera, CheckCircle, Loader2, User } from 'lucide-react';
-import { useState, useCallback } from 'react';
+import { AlertCircle, Camera, CheckCircle, Loader2, User } from 'lucide-react';
+import clsx from 'clsx';
+import { useState, useCallback } from 'react';
@@
-            className={`glass-effect-light ${hasError
-              ? 'border-red-500 focus:border-red-500 focus:ring-red-500'
-              : ''
-              }`}
+            className={clsx(
+              'glass-effect-light',
+              hasError && 'border-red-500 focus:border-red-500 focus:ring-red-500'
+            )}
@@
-            className={`glass-effect-light ${fieldErrors.bio
-              ? 'border-red-500 focus:border-red-500 focus:ring-red-500'
-              : ''
-              }`}
+            className={clsx(
+              'glass-effect-light',
+              fieldErrors.bio && 'border-red-500 focus:border-red-500 focus:ring-red-500'
+            )}

As per coding guidelines, For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX.

Also applies to: 168-180, 269-272

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/web/app/dashboard/profile/page.tsx`:
- Around line 149-167: previousUserData is being set from hydratedUserData
(which already includes local edits) so rollback never restores the persisted
server state; before applying the optimistic update, capture the last persisted
state (e.g., snapshot the current server-backed state or call the read/fetch
that returns the persisted profile) into previousUserData (not
hydratedUserData), then perform the optimistic setUserData(...edited...), call
updateProfile(...), and on catch use setUserData(previousUserData) to restore;
reference updateProfile, setUserData, hydratedUserData and previousUserData when
making this change.

In `@apps/web/components/profile/ProfileInfo.tsx`:
- Around line 52-68: The switch handling field validation in ProfileInfo.tsx
misses cases for 'full_name' and 'bio', so blur validation never sets
fieldErrors for renderInputField('full_name', ...) and the bio input; add cases
for 'full_name' and 'bio' in the switch (e.g., case 'full_name': result =
fieldValidators.fullName(value); break; case 'bio': result =
fieldValidators.bio(value); break;) and implement corresponding validators on
the fieldValidators object (or alternatively remove/disable the blur error UI
for those two inputs) so the validation behavior matches the UI.
🧹 Nitpick comments (3)
apps/web/components/profile/ProfileInfo.tsx (2)

32-36: Prefer const arrow component with explicit typing.

This file uses a function declaration for the component. Consider switching to a const arrow with an explicit return type to align with TS conventions here.
As per coding guidelines: Prefer const arrow functions with explicit type annotations over function declarations.

♻️ Suggested refactor
-export function ProfileInfo({
-  userData,
-  isEditing,
-  onUserDataChange,
-}: ProfileInfoProps) {
+export const ProfileInfo = ({
+  userData,
+  isEditing,
+  onUserDataChange,
+}: ProfileInfoProps): JSX.Element => {
   // ...
-}
+};

176-179: Prefer clsx (or existing cn) for conditional classes.

Avoid ternary-string concatenation in JSX for conditional classes; it’s harder to read and scale.
As per coding guidelines: For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX.

♻️ Suggested refactor
+import clsx from 'clsx';
...
-            className={`glass-effect-light ${hasError
-              ? 'border-red-500 focus:border-red-500 focus:ring-red-500'
-              : ''
-              }`}
+            className={clsx('glass-effect-light', {
+              'border-red-500 focus:border-red-500 focus:ring-red-500': hasError,
+            })}
...
-            className={`glass-effect-light ${fieldErrors.bio
-              ? 'border-red-500 focus:border-red-500 focus:ring-red-500'
-              : ''
-              }`}
+            className={clsx('glass-effect-light', {
+              'border-red-500 focus:border-red-500 focus:ring-red-500':
+                !!fieldErrors.bio,
+            })}

Also applies to: 269-272

apps/web/app/dashboard/profile/page.tsx (1)

23-27: Prefer const arrow component with explicit typing.

This file declares the page component as a function; switch to a const arrow with an explicit return type for TS consistency.
As per coding guidelines: Prefer const arrow functions with explicit type annotations over function declarations.

♻️ Suggested refactor
-export default function EnhancedProfilePage() {
+const EnhancedProfilePage = (): JSX.Element => {
   // ...
-}
+};
+
+export default EnhancedProfilePage;

@simplicityf
Copy link
Author

@aguilar1x

Kindly review the PR

@simplicityf
Copy link
Author

@aguilar1x

PR is ready to be merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Connect “Edit Profile” Button on /profile to Update Personal Information in the Database

1 participant

Comments