ProfilePageComponent(with working frontend image)
Fri Jul 12 2024 15:13:48 GMT+0000 (Coordinated Universal Time)
Saved by @iamkatmakhafola
//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">×</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); } }
Comments