Skip to content

User Profile Edit & Authentication Redirect | Kayleen, Dylan, & Lily #27

@jpuka01

Description

@jpuka01

Objective

  1. Allow users to edit their profile information (display name, bio, school, graduation year, Instagram, profile picture). Currently profile pages only display information but have basic edit functionality that needs improvement.
  2. Direct authenticated users to their profile page after successful login or signup.

Current State

✅ Basic edit profile modal exists in /app/user-page/page.tsx with:

  • Form fields for firstName, lastName, headline, school, year, Instagram
  • Client-side only (doesn't persist to database)
  • Works for demonstration but needs backend integration

⚠️ Missing:

  • Backend API endpoint for profile updates
  • Authentication redirect to user profile
  • Profile picture upload functionality
  • Persistence to database
  • User-specific profile pages (currently static /user-page)

Part 1: Authentication Redirect to User Profile

Tasks

  • Create Dynamic User Profile Route (/app/user-page/[username]/page.tsx):

    export default async function UserProfilePage({ 
      params 
    }: { 
      params: Promise<{ username: string }> 
    }) {
      const { username } = await params;
      const session = await getServerSession(authOptions);
      
      // Fetch user profile by username
      const profile = await prisma.profile.findFirst({
        where: {
          user: { username }
        },
        include: {
          user: {
            select: {
              id: true,
              username: true,
              email: true,
              role: true
            }
          }
        }
      });
    
      if (!profile) {
        notFound();
      }
    
      const isOwnProfile = session?.user?.username === username;
    
      return (
        <UserProfileView 
          profile={profile} 
          isOwnProfile={isOwnProfile}
        />
      );
    }
  • Update Login Success Redirect (/app/login/page.tsx or /app/api/auth/[...nextauth]/route.ts):

    • After successful login, redirect to /user-page/${session.user.username}
    • Configure NextAuth callbacks:
    callbacks: {
      async signIn({ user }) {
        return true; // Allow sign in
      },
      async redirect({ url, baseUrl }) {
        // After sign in, redirect to user's profile
        const session = await getServerSession(authOptions);
        if (session?.user?.username) {
          return `${baseUrl}/user-page/${session.user.username}`;
        }
        return baseUrl;
      }
    }
  • Update Signup Success Redirect (/app/sign-up/page.tsx or /app/api/auth/signup/route.ts):

    • After successful signup and auto-login, redirect to /user-page/${username}
    • Add redirect logic after user creation:
    // After creating user, sign them in and redirect
    const signInResult = await signIn('credentials', {
      username,
      password,
      redirect: false
    });
    
    if (signInResult?.ok) {
      router.push(`/user-page/${username}`);
    }
  • Move Static User Page (/app/user-page/page.tsx):

    • Move current /app/user-page/page.tsx content to /app/user-page/[username]/page.tsx
    • Convert to dynamic route that fetches user-specific data
    • Show "Edit Profile" button only when isOwnProfile === true

Part 2: Profile Edit Backend Integration

Tasks

  • Implement PATCH /api/users/[id] (/app/api/users/[id]/route.ts):

    export async function PATCH(
      req: Request,
      { params }: { params: Promise<{ id: string }> }
    ) {
      try {
        const { id } = await params;
        const session = await getServerSession(authOptions);
        
        // Only allow users to edit their own profile
        if (!session || session.user.id !== id) {
          return NextResponse.json(
            { message: 'Unauthorized' },
            { status: 403 }
          );
        }
    
        const body = await req.json();
        const { firstName, lastName, headline, school, year, instagram, bio } = body;
    
        // Construct display_name from firstName and lastName
        const display_name = `${firstName?.trim() || ''} ${lastName?.trim() || ''}`.trim();
    
        const updatedProfile = await prisma.profile.update({
          where: { user_id: id },
          data: {
            display_name,
            bio: bio || headline, // Use headline as bio if bio not provided
            // Note: school, year, instagram need to be added to Profile model
          }
        });
    
        return NextResponse.json({ profile: updatedProfile }, { status: 200 });
      } catch (error) {
        console.error('Profile update error:', error);
        return NextResponse.json(
          { message: 'Failed to update profile' },
          { status: 500 }
        );
      }
    }
  • Update Prisma Schema (/prisma/schema.prisma):

    • Add missing fields to Profile model:
    model Profile {
      id                 String   @id @default(cuid())
      user_id            String   @unique
      display_name       String?  @db.VarChar(100)
      bio                String?  @db.VarChar(500)
      profile_image_url  String?
      school             String?  @db.VarChar(200)
      graduation_year    String?  @db.VarChar(4)
      instagram_handle   String?  @db.VarChar(100)
      created_at         DateTime @default(now())
      updated_at         DateTime @updatedAt
      
      user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
    }
    • Run npm run db:push to update database
  • Update Edit Profile Form (/app/user-page/[username]/page.tsx):

    • Connect form submission to backend API
    • Replace client-side only state with API calls:
    const handleSave = async (e: React.FormEvent) => {
      e.preventDefault();
      
      try {
        setIsLoading(true);
        
        const response = await fetch(`/api/users/${session.user.id}`, {
          method: 'PATCH',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            firstName: form.firstName,
            lastName: form.lastName,
            headline: form.headline,
            school: form.school,
            year: form.year,
            instagram: form.instagram,
            bio: form.bio
          })
        });
    
        if (!response.ok) {
          const error = await response.json();
          throw new Error(error.message || 'Update failed');
        }
        
        setSuccess('Profile updated successfully!');
        setIsEditing(false);
        router.refresh(); // Refresh server component data
      } catch (err) {
        setError(err.message);
      } finally {
        setIsLoading(false);
      }
    };
  • Add Profile Picture Upload (Optional - Cloudinary integration):

    const handleImageUpload = async (file: File) => {
      const formData = new FormData();
      formData.append('file', file);
      formData.append('upload_preset', process.env.NEXT_PUBLIC_CLOUDINARY_PRESET);
    
      const response = await fetch(
        `https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/image/upload`,
        {
          method: 'POST',
          body: formData
        }
      );
    
      const data = await response.json();
      return data.secure_url;
    };
  • Add Form Validation:

    • Display name: max 100 characters
    • Bio/headline: max 500 characters
    • Instagram handle: max 100 characters, optional @ prefix
    • Profile picture: image files only, max 5MB
    • Show character count indicators
    • Show validation errors inline
  • Add Success/Error States:

    • Success: "Profile updated successfully!" with green alert
    • Error: Display specific error messages with red alert
    • Loading: Disable save button and show spinner during update
  • Update Navbar/Header:

    • Add link to user's profile page using session.user.username
    • Show "My Profile" menu item that links to /user-page/${session.user.username}

Testing Checklist

Authentication Redirect Tests

  • Sign up as a new user → automatically redirected to /user-page/[username]
  • Log in as existing user → redirected to /user-page/[username]
  • Verify username in URL matches logged-in user
  • Test with different usernames (special characters, numbers, etc.)

Profile Edit Tests

  • Navigate to own profile page
  • "Edit Profile" button appears only on own profile
  • "Edit Profile" button does NOT appear on other users' profiles
  • Click "Edit Profile" → modal opens with current data pre-filled
  • Update display name (firstName, lastName) → saves successfully
  • Update bio/headline → saves successfully
  • Update school from dropdown → saves successfully
  • Update graduation year → saves successfully
  • Update Instagram handle → saves successfully
  • Test character limit validation (100 for name, 500 for bio)
  • Test with empty optional fields → saves successfully
  • Upload profile picture → saves to Cloudinary and displays
  • Click Cancel → modal closes without saving
  • After save, profile page refreshes with new data
  • Verify changes persist in database (check with Prisma Studio)
  • Test unauthorized access: try to edit another user's profile via API → gets 403

Security Tests

  • Attempt to access /api/users/[another-user-id] PATCH → gets 403
  • Attempt to edit profile while logged out → gets 401
  • Verify session validation in API endpoint
  • Test SQL injection attempts in form fields
  • Test XSS attempts in bio/display name fields

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions