Preview:
//profilepage component
//html
<div class="container-fluid" style="margin-top: 60px;">
  <div class="row justify-content-start">
    <!-- Back Button -->
    <div class="col-6">
      <button class="btn btn-link" (click)="goBack()">
        <i class="bi bi-arrow-left-circle"></i> Back
      </button>
    </div>

    <!-- My Profile Heading -->
    <div class="col-6">
      <h2 class="text-left">My Profile</h2>
    </div>

    <!-- Left Side Menu -->
    <div class="col-md-3">
      <h5 class="small-heading">
        <i class="bi bi-bell-fill"></i>
        Notifications
      </h5>
      <!-- Notification placeholder -->
      <div class="card mt-3">
        <div class="card-body">
          <div *ngIf="unredeemedRewards.length === 0" class="notification-item">
            <span>No Notifications</span>
          </div>
          <div *ngFor="let reward of unredeemedRewards" class="notification-item">
            <span>{{ reward.reward_Type_Name }}</span>
            <button class="btn btn-sm btn-primary" (click)="openRedeemModal(reward)">Redeem</button>
          </div>
        </div>
      </div>
    </div>

    

    <!-- Vertical Line Separator -->
    <div class="col-md-1">
      <div class="vertical-line"></div>
    </div>

    <!-- Right Side Form -->
    <div class="col-md-6">
      <form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
        <h5 class="small-heading">
          <i class="bi bi-house-gear-fill"></i>
          Personal Details
        </h5>
        <div class="text-center mb-3">
          <div class="profile-photo-wrapper">
            <img [src]="userProfileImage" alt="Profile Photo" class="img-fluid rounded-circle profile-photo" *ngIf="userProfileImage">
            <div class="edit-photo-wrapper">
              <a href="#" (click)="enableEditMode($event)" class="edit-link">Edit</a>
              <label [class.disabled-icon]="!isEditMode" for="photoUpload" class="photo-upload-icon">
                <i class="bi bi-camera"></i>
              </label>
            </div>
            <input type="file" id="photoUpload" formControlName="photo" class="d-none" (change)="onPhotoChange($event)" [readonly]="!isEditMode" >
          </div>
        </div>
        

        <br>

      
        <div class="row">
          <div class="col-md-6 mb-3">
            <div class="form-group">
              <label for="name" class="form-label">Name</label>
              <input type="text" class="form-control" id="name" formControlName="name" [readonly]="!isEditMode" [ngClass]="{'disabled-input': !isEditMode}">
              <div *ngIf="profileForm.controls['name'].invalid && (profileForm.controls['name'].dirty || profileForm.controls['name'].touched)" class="alert">
                <div *ngIf="profileForm.controls['name'].errors?.['required']">Name is required.</div>
                <div *ngIf="profileForm.controls['name'].errors?.['minlength']">Name must be at least 3 characters long.</div>
                <div *ngIf="profileForm.controls['name'].errors?.['maxlength']">Name must be at most 50 characters long.</div>
              </div>
            </div>
          </div>

          <div class="col-md-6 mb-3">
            <div class="form-group">            
              <label for="surname" class="form-label">Surname</label>
              <input type="text" class="form-control" id="surname" formControlName="surname" [readonly]="!isEditMode" [ngClass]="{'disabled-input': !isEditMode}">
              <div *ngIf="profileForm.controls['surname'].invalid && (profileForm.controls['surname'].dirty || profileForm.controls['surname'].touched)" class="alert">
                <div *ngIf="profileForm.controls['surname'].errors?.['required']">Surname is required.</div>
                <div *ngIf="profileForm.controls['surname'].errors?.['minlength']">Surname must be at least 3 characters long.</div>
                <div *ngIf="profileForm.controls['surname'].errors?.['maxlength']">Surname must be at most 50 characters long.</div>
              </div>
            </div>
          </div>

          <div class="col-md-6 mb-3">
            <div class="form-group">
              <label for="email" class="form-label">Email Address</label>
              <input type="email" class="form-control" id="email" formControlName="email" [readonly]="!isEditMode" [ngClass]="{'disabled-input': !isEditMode}">
              <div *ngIf="profileForm.controls['email'].invalid && (profileForm.controls['email'].dirty || profileForm.controls['email'].touched)" class="alert">
                <div *ngIf="profileForm.controls['email'].errors?.['required']">Email is required.</div>
              </div>
            </div>
          </div>

          <div class="col-md-6 mb-3">
            <div class="form-group">
              <label for="phoneNumber" class="form-label">Contact Number</label>
              <input type="text" class="form-control" id="phoneNumber" formControlName="phoneNumber" [readonly]="!isEditMode" [ngClass]="{'disabled-input': !isEditMode}">
              <div *ngIf="profileForm.controls['phoneNumber'].invalid && (profileForm.controls['phoneNumber'].dirty || profileForm.controls['phoneNumber'].touched)" class="alert">
                <div *ngIf="profileForm.controls['phoneNumber'].errors?.['required']">Contact number is required.</div>
                <div *ngIf="profileForm.controls['phoneNumber'].errors?.['maxlength']">Contact number must be a valid 10-digit number.</div>
              </div>
            </div>
          </div>

          <div class="col-md-6 mb-3">
            <div class="form-group">
              <label for="physical_Address" class="form-label">Physical Address</label>
              <input type="text" class="form-control" id="physical_Address" formControlName="physical_Address" [readonly]="!isEditMode" [ngClass]="{'disabled-input': !isEditMode}">
              <div *ngIf="profileForm.controls['physical_Address'].invalid && (profileForm.controls['physical_Address'].dirty || profileForm.controls['physical_Address'].touched)" class="alert">
                <div *ngIf="profileForm.controls['physical_Address'].errors?.['required']">Physical address is required.</div>
                <div *ngIf="profileForm.controls['physical_Address'].errors?.['maxlength']">Physical address must be at most 255 characters long.</div>
              </div>
            </div>
          </div>
        </div>

        <div class="d-flex justify-content-end">
          <button type="button" class="btn btn-primary me-2" (click)="openSaveModal()" [disabled]="!isEditMode">Save</button>
          <button type="button" class="btn btn-info" (click)="changePassword()" [disabled]="!isEditMode">Change Password</button>
        </div>
      </form>
    </div>
  </div>
</div>

<!-- Save Confirmation Modal -->
<div class="modal fade" id="saveConfirmationModal" tabindex="-1" role="dialog" aria-labelledby="saveConfirmationModalTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="saveConfirmationModalTitle">Save Changes</h5>
      </div>
      <div class="modal-body">
        Are you sure you want to update your profile details?
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" (click)="dismissModal()">Cancel</button>
        <button type="button" class="btn btn-primary" (click)="confirmSave()">Confirm</button>
      </div>
    </div>
  </div>
</div>

<!-- Error Modal -->
<div class="modal fade" id="errorModal" tabindex="-1" role="dialog" aria-labelledby="errorModalTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="errorModalTitle">Error</h5>
      </div>
      <div class="modal-body">
        Please enter a valid input for the following fields:
        <ul id="errorList"></ul>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-primary" (click)="dismissErrorModal()">OK</button>
      </div>
    </div>
  </div>
</div>

<!-- Redeem Reward Modal -->
<div class="modal fade" id="redeemRewardModal" tabindex="-1" role="dialog" aria-labelledby="redeemRewardModalTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="redeemRewardModalTitle">Redeem Reward</h5>
      </div>
      <div class="modal-body">
        Are you sure you want to redeem the reward {{ selectedReward?.reward_Type_Name }}?
        <p>{{ selectedReward?.reward_Criteria }}</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" (click)="dismissRedeemModal()">No</button>
        <button type="button" class="btn btn-primary" (click)="confirmRedeem()">Yes</button>
      </div>
    </div>
  </div>
</div>

<!-- Success Modal -->
<div class="modal fade" id="successModal" tabindex="-1" role="dialog" aria-labelledby="successModalTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="successModalTitle">Success</h5>
        <button type="button" class="close" (click)="dismissSuccessModal()">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        Congratulations! You've successfully redeemed this reward {{ selectedReward?.reward_Type_Name }}. Please contact the gym for further instructions.
      </div>
    </div>
  </div>
</div>

//ts
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { UserService } from '../Services/userprofile.service';
import { Router } from '@angular/router';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { Member, updateUser } from '../shared/update-user';
import { Subscription, catchError } from 'rxjs';
import { RewardRedeemViewModel, UnredeemedRewardModel } from '../shared/reward';
import { RewardService } from '../Services/reward.service';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';

declare var $: any; // Import jQuery

@Component({
  selector: 'app-profile-page',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule],
  templateUrl: './profile-page.component.html',
  styleUrls: ['./profile-page.component.css']
})
export class ProfilePageComponent implements OnInit, OnDestroy {
  profileForm: FormGroup;
  user: updateUser | undefined;
  member: Member | undefined;
  isEditMode = false;
  errorMessage = '';  
  userProfileImage: string | null = null;
  unredeemedRewards: UnredeemedRewardModel[] = [];
  selectedReward: UnredeemedRewardModel | null = null;

  private userSubscription: Subscription | undefined;
  private memberSubscription: Subscription | undefined;
  private redeemSubscription: Subscription | undefined;
  
  constructor(
    private userService: UserService,
    private rewardService: RewardService,
    private router: Router,
    private fb: FormBuilder
  ) {
    this.profileForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      name: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(20)]],
      surname: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(20)]],
      phoneNumber: ['', [Validators.required, Validators.maxLength(10)]],
      physical_Address: ['', [Validators.required, Validators.maxLength(255)]],
      photo: ['']
    });
  }

  ngOnInit(): void {
    const userId = JSON.parse(localStorage.getItem('User') || '{}').userId;
    console.log('User ID from local storage:', userId);
    this.loadUserProfile(userId);
    this.isEditMode = false;
  }

  ngOnDestroy(): void {
    // Clean up subscriptions
    if (this.userSubscription) {
      this.userSubscription.unsubscribe();
    }
    if (this.memberSubscription) {
      this.memberSubscription.unsubscribe();
    }
    if (this.redeemSubscription) {
      this.redeemSubscription.unsubscribe();
    }
  }

  loadUserProfile(userId: number): void {
    this.userSubscription = this.userService.getUserById(userId).pipe(
      catchError(error => {
        console.error('Error fetching user profile:', error);
        return [];
      })
    ).subscribe({
      next: (result) => {
        console.log('User data received:', result);
        this.user = result; 
        // Log photo to debug
        console.log('Photo:', this.user.photo);        
        // Set user profile image
        this.userProfileImage = `data:image/jpeg;base64,${this.user.photo}`;
        this.profileForm.patchValue(this.user);
        console.log('User:', this.user);

        if (this.user.user_Type_ID === 3) {
          this.loadMemberProfile(userId);
        }
      },
      complete: () => {
        console.log('getUserById subscription completed');
      }
    });
  }

  loadMemberProfile(userId: number): void {
    this.memberSubscription = this.userService.getMemberByUserId(userId).pipe(
      catchError(error => {
        console.error('Error fetching member profile:', error);
        return [];
      })
    ).subscribe({
      next: (result) => {
        this.member = result;
        if (this.member) {
          this.loadUnredeemedRewards(this.member.Member_ID);
        }
      },
      complete: () => {
        console.log('getMemberByUserId subscription completed');
      }
    });
  }

  clearForm() {
    this.profileForm.reset();
  }

  enableEditMode(event: Event) {
    event.preventDefault();
    this.isEditMode = true;
    this.profileForm.enable();
  }

  openSaveModal() {
    if (this.profileForm.invalid) {
      this.showValidationErrors();
      $('#errorModal').modal('show');
      return;
    }
    $('#saveConfirmationModal').modal('show');
  }

  showValidationErrors() {
    const invalidFields: string[] = [];
    Object.keys(this.profileForm.controls).forEach(key => {
      const controlErrors = this.profileForm.get(key)?.errors;
      if (controlErrors) {
        Object.keys(controlErrors).forEach(errorKey => {
          invalidFields.push(`${key}: ${errorKey}`);
        });
      }
    });
    this.errorMessage = `Please enter a valid input: ${invalidFields.join(', ')}`;
  }

  dismissModal() {
    $('#saveConfirmationModal').modal('hide');
  }

  dismissErrorModal() {
    $('#errorModal').modal('hide');
  }

  confirmSave() {
    this.dismissModal();
    this.onSubmit();
    this.isEditMode = false; // Disable edit mode after confirmation
  }

  onSubmit() {
    if (this.profileForm.valid) {
      const userId = JSON.parse(localStorage.getItem('User')!).userId;
      this.userService.updateUser(userId, this.profileForm.value).subscribe({
        next: (result) => {
          console.log('User to be updated:', result);
          this.router.navigateByUrl(`/ProfilePage/${userId}`);
          alert('Successfully updated profile');
        },
        error: () => {
          console.error('Error updating user profile:');
          alert('Error updating profile');
        }
      });
    }
  }

  onPhotoChange(event: Event): void {
    if (!this.isEditMode) return;

    const input = event.target as HTMLInputElement;
    if (input.files && input.files[0]) {
      const reader = new FileReader();
      reader.onload = (e: any) => {
        this.userProfileImage = e.target.result; // Update the image source
        this.profileForm.patchValue({ photo: e.target.result }); // Update the form control
      };
      reader.readAsDataURL(input.files[0]);
    }
  }

  goBack() {
    const userTypeId = JSON.parse(localStorage.getItem('User')!).userTypeId;
    const userId = JSON.parse(localStorage.getItem('User')!).userId;
    if (userTypeId === 1) {  // Ensure userTypeID is compared as string
      this.router.navigateByUrl(`/OwnerHome/${userId}`);
    } else if (userTypeId === 2) {
      this.router.navigateByUrl(`/EmployeeHome/${userId}`);
    } else if (userTypeId === 3) {
      this.router.navigateByUrl(`/Home/${userId}`);
    }
  }

  changePassword() {
    this.router.navigateByUrl('/ChangePasswordPage');
  }

  // Method to load rewards for the current user
  loadUnredeemedRewards(memberId: number): void {
    this.rewardService.getUnredeemedRewardsForMember(memberId).subscribe(
      rewards => {
        this.unredeemedRewards = rewards;
      },
      error => {
        console.error('Error fetching unredeemed rewards:', error);
      }
    );
  }

  // Method to open redeem modal for a reward
  openRedeemModal(reward: UnredeemedRewardModel): void {
    this.selectedReward = reward;
    $('#redeemRewardModal').modal('show');
  }

  // Method to dismiss redeem modal
  dismissRedeemModal(): void {
    $('#redeemRewardModal').modal('hide');
  }

  // Method to confirm redeeming a reward
  confirmRedeem(): void {
    if (!this.selectedReward) {
      return;
    }
    const redeemRequest = new RewardRedeemViewModel();
    redeemRequest.MemberId = this.member?.Member_ID ?? 0;
    redeemRequest.RewardId = this.selectedReward.reward_ID;

    // Call backend service to redeem the reward
    this.redeemSubscription = this.rewardService.redeemReward(redeemRequest).subscribe({
      next: () => {
        // Show success modal on successful redemption
        $('#successModal').modal('show');
        // Remove redeemed reward from the list
        this.unredeemedRewards = this.unredeemedRewards.filter(r => r.reward_ID !== this.selectedReward?.reward_ID);
      },
      error: (error) => {
        console.error('Error redeeming reward:', error);
        // Handle error
      }
    });
    this.dismissRedeemModal();
  }

  // Method to dismiss success modal
  dismissSuccessModal(): void {
    $('#successModal').modal('hide');
  }
}


//models
export class updateUser
{
   name!: string;
   surname!: string;
   email!: string;
   physical_Address!: string;
   phoneNumber!: string;
   photo!: string;
   user_Type_ID!: number;
}
export class UserProfile
{   
   User_ID !: number
   Id!: number
   Name!: string
   Surname!: string
   ID_Number!: string
   Email!: string
   Physical_Address!: string
   PhoneNumber!: string
   Photo!: string
   UserName!: string
   PasswordHash!: string
   Date_of_Birth!: Date
   User_Status_ID !: number
   User_Type_ID!: number
}

//service
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, catchError, map, throwError } from 'rxjs';
import { UserProfile } from '../Models/UserProfile';
import { LoginUser } from '../shared/login-user';
import { User } from '../shared/user';
import { updateUser } from '../shared/update-user';
import { UserTypeViewModel } from '../shared/user-type-vm';
import { UserViewModel } from '../shared/search-user';

@Injectable({
  providedIn: 'root'
})
export class UserService {

  httpOptions = {
    headers: new HttpHeaders({
      'Content-Type': 'application/json'
    })
  };

  constructor(private http: HttpClient) {}
  endPoint: string = "https://localhost:7185/api/";

  //User EndPoints
  // RegisterUser(registerUser: RegisterUser) {
  //   return this.http.post(`${this.endPoint}User/Register`, registerUser, this.httpOptions);
  // }

  RegisterUser(formData: FormData): Observable<any> {
    return this.http.post(`${this.endPoint}User/Register`, formData);
  }

  
  LoginUser(loginUser: LoginUser): Observable<User> {
    return this.http.post<User>(`${this.endPoint}User/Login`, loginUser, this.httpOptions);
  }

  getAllUsers(): Observable<UserProfile[]> {
    return this.http.get<UserProfile[]>(this.endPoint + "User/getAllUsers");
  }

  getUserById(userId: number): Observable<updateUser> {
    return this.http.get<updateUser>(`${this.endPoint}User/getUserById/${userId}`)
    .pipe(map(result => result))
  }

  getMemberByUserId(userId: number): Observable<any> {
    return this.http.get<any>(`${this.endPoint}User/GetMemberByUserId/${userId}`, this.httpOptions);
  }

  updateUser(userId: string, user: updateUser): Observable<any> {
    return this.http.put(`${this.endPoint}User/editUser/${userId}`, user, { responseType: 'text' })
    .pipe(
      map((response: string) => {
        try {
          // Attempt to parse as JSON if the response is in JSON format
          return JSON.parse(response);
        } catch {
          // Return as plain text if not JSON
          return response;
        }
      }),
      catchError(this.handleError)
    );
  }

  private handleError(error: HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
      console.error('An error occurred:', error.error.message);
    } else {
      console.error(
        `Backend returned code ${error.status}, ` +
        `body was: ${error.error}`);
    }
    return throwError(() => new Error('Something bad happened; please try again later.'))    
  }
  
  searchUsers(criteria: string): Observable<UserViewModel[]> {
    return this.http.get<UserViewModel[]>(`${this.endPoint}User/SearchUsers?criteria=${criteria}`);
  }

  deleteUser(userId: number): Observable<string> {
    return this.http.delete<string>(`${this.endPoint}User/deleteUser/${userId}`);
  }

  //UserType EndPoints
  addUserType(userType: UserTypeViewModel): Observable<UserTypeViewModel> {
    return this.http.post<UserTypeViewModel>(`${this.endPoint}UserType/addUserType`, userType, this.httpOptions);
  }

  getAllUserTypes(): Observable<UserTypeViewModel[]> {
    return this.http.get<UserTypeViewModel[]>(`${this.endPoint}UserType/getAllUserTypes`, this.httpOptions);
  }

  getUserTypeById(id: number): Observable<UserTypeViewModel> {
    return this.http.get<UserTypeViewModel>(`${this.endPoint}UserType/getUserTypeById/${id}`, this.httpOptions);
  }

  updateUserType(userTypeId: number, userTypeName: string): Observable<void> {
    const body = { user_Type_Name: userTypeName }; // Correctly format the body as JSON
    return this.http.put<void>(`${this.endPoint}UserType/updateUserType/${userTypeId}`, body, this.httpOptions);
  }

  deleteUserType(id: number): Observable<void> {
    return this.http.delete<void>(`${this.endPoint}UserType/deleteUserType/${id}`, this.httpOptions);
  }
}
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter