Profile Page (before rewards)
Wed Aug 07 2024 21:31:35 GMT+0000 (Coordinated Universal Time)
Saved by @iamkatmakhafola
//html <div class="container-fluid" style="margin-top: 60px;"> <div class="row justify-content-start"> <!-- Back Button --> <div class="col-6"> <i class="bi bi-arrow-left-circle header-icon" (click)="goBack()"></i> </div> <!-- My Profile Heading --> <div class="col-6"> <h2 class="text-left">My Profile</h2> <span style="float: right;"> <a class="navbar-brand" href="#" data-bs-toggle="modal" data-bs-target="#helpModal"> <i class="bi bi-info-circle-fill">Help</i> </a> </span> </div> <!-- Left Side Menu --> <div class="col-md-3"> <div routerLink="/orders" *ngIf="userTypeID === 3"> <h5 class="small-heading"> <i class="bi bi-cart4"></i> Orders </h5> </div> <br> <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 style="float:left">{{ reward.rewardTypeName }}</span> <span style="float: right"><button class="btn btn-sm btn-primary" (click)="openRedeemModal(reward)">Redeem</button></span> </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 2 characters long.</div> <div *ngIf="profileForm.controls['name'].errors?.['maxlength']">Name must be at most 20 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 2 characters long.</div> <div *ngIf="profileForm.controls['surname'].errors?.['maxlength']">Surname must be at most 20 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 *ngIf="profileForm.controls['email'].errors?.['email']">Email is invalid.</div> </div> </div> </div> <div class="col-md-6 mb-3"> <div class="form-group"> <label for="phoneNumber" class="form-label">Phone 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']">Phone Number is required.</div> <div *ngIf="profileForm.controls['phoneNumber'].errors?.['minlength']">Phone Number must be a valid 10-digit number.</div> <div *ngIf="profileForm.controls['phoneNumber'].errors?.['maxlength']">Phone 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?.['minlength']">Physical Address must be at least 7 characters long.</div> <div *ngIf="profileForm.controls['physical_Address'].errors?.['maxlength']">Physical Address must be at most 100 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 <strong>{{ selectedReward?.rewardTypeName }}</strong>? <br> <p>The reward criteria met: <strong>{{ selectedReward?.rewardCriteria }}</strong></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"> <strong><h5 class="modal-title" id="successModalTitle">Congratulations</h5></strong> </div> <div class="modal-body"> <strong>Congratulations!</strong> You've successfully redeemed this reward <strong>{{ selectedReward?.rewardTypeName }}</strong>. Please contact the gym and provide this code <strong>{{ verificationCode }}</strong> for further instructions. </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" (click)="dismissSuccessModal()">Close</button> </div> </div> </div> </div> <!-- help-modal.component.html --> <div class="modal fade" id="helpModal" tabindex="-1" aria-labelledby="helpModalLabel" aria-hidden="true"> <div class="modal-dialog modal-dialog-scrollable"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <h5 class="modal-title mx-auto" id="helpModalLabel">Help Guide</h5> <div class="search-bar-container"> <input type="text" class="form-control search-bar" placeholder="Search help" [(ngModel)]="searchTerm" (input)="filterHelpContent()"> </div> </div> <div class="modal-body"> <div *ngFor="let item of filteredContent"> <h5>{{ item.title }}</h5> <p [innerHTML]="item.content"></p> </div> </div> </div> </div> </div> //ts import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormGroup, Validators, FormBuilder, FormsModule } from '@angular/forms'; import { UserService } from '../Services/userprofile.service'; import { Router, RouterLink } 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'; declare var $: any; // Import jQuery @Component({ selector: 'app-profile-page', standalone: true, imports: [CommonModule, ReactiveFormsModule, RouterLink, FormsModule], 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; photoFile: File | null = null; unredeemedRewards: UnredeemedRewardModel[] = []; selectedReward: UnredeemedRewardModel | null = null; verificationCode: string | null = null; userTypeID: number | null = null; helpContent: any[] = []; filteredContent: any[] = []; searchTerm: string = ''; 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(2), Validators.maxLength(20)]], surname: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(20)]], phoneNumber: ['', [Validators.required, Validators.minLength(10), Validators.maxLength(10)]], physical_Address: ['', [Validators.required, Validators.minLength(7), Validators.maxLength(100)]], photo: [''] }); } ngOnInit(): void { const userTypeId = JSON.parse(localStorage.getItem('User') || '{}').userTypeId; this.userTypeID = userTypeId; console.log('User Type ID',userTypeId); const userId = JSON.parse(localStorage.getItem('User') || '{}').userId; console.log('User ID from local storage:', userId); this.loadUserProfile(userId); this.isEditMode = false; // Initialize help content this.helpContent = [ { title: 'Profile Page Context-Sensitive Help', content: ` <p><strong>Overview:</strong> The Profile Page allows users to view and update their personal information. This includes details such as username, email, and profile picture.</p> <p><strong>Elements and Features:</strong></p>` }, { title: '1. Back Button', content: ` <ul> <li><strong>Description:</strong> An arrow icon located in the header that allows you to return to the previous page.</li> <li><strong>Functionality:</strong> Clicking the back button navigates to the previous page you visited.</li> <li><strong>Helpful Tips:</strong> <ul> <li>Use the back button if you want to return to your previous page without losing your current context.</li> </ul> </li> </ul>` }, { title: '2. Header Title', content: ` <ul> <li><strong>Description:</strong> Displays the title "Profile" to indicate the purpose of the screen.</li> <li><strong>Functionality:</strong> Provides a clear indication of the current screen's functionality.</li> </ul>` }, { title: '3. Profile Information', content: ` <ul> <li><strong>Description:</strong> Displays your personal information such as username, email, and profile picture.</li> <li><strong>Functionality:</strong> Allows you to view and edit your personal details.</li> </ul>` }, { title: '4. Change Password Button', content: ` <ul> <li><strong>Description:</strong> A button that navigates you to the Change Password screen.</li> <li><strong>Functionality:</strong> Allows you to update your password securely.</li> </ul>` }, { title: 'Technical Details:', content: ` <ul> <li>Dynamic Data: The profile information is dynamically updated based on the data retrieved from the backend.</li> <li>Navigation: Utilizes Angular's Router for smooth transitions between different sections of the application.</li> </ul>` }, { title: 'Common Questions:', content: ` <p><strong>Q:</strong> How do I update my profile information?</p> <p><strong>A:</strong> Click on the edit button next to the information you want to update, make your changes, and click save.</p> <p><strong>Q:</strong> How do I change my password?</p> <p><strong>A:</strong> Click the "Change Password" button to navigate to the Change Password screen.</p> <p><strong>Q:</strong> What should I do if I encounter an error?</p> <p><strong>A:</strong> Refresh the page or contact support for assistance.</p>` }, { title: 'Troubleshooting:', content: ` <p><strong>Problem:</strong> The profile information is not loading.</p> <p><strong>Solution:</strong> Ensure you are connected to the internet and logged in. If the problem persists, try refreshing the page or contact technical support.</p> <p><strong>Problem:</strong> Unable to update profile information.</p> <p><strong>Solution:</strong> Check your internet connection and ensure all required fields are filled out correctly. If the issue continues, contact support.</p>` } ]; // Initialize filtered content this.filteredContent = [...this.helpContent]; } filterHelpContent(): void { const term = this.searchTerm.toLowerCase(); this.filteredContent = this.helpContent.filter(item => item.title.toLowerCase().includes(term) || item.content.toLowerCase().includes(term) ); } 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; // Patch non-file form values this.profileForm.patchValue({ email: this.user.email, name: this.user.name, surname: this.user.surname, phoneNumber: this.user.phoneNumber, physical_Address: this.user.physical_Address, }); // Log photo to debug console.log('Photo:', this.user.photo); // Set user profile image this.userProfileImage = `data:image/jpeg;base64,${this.user.photo}`; console.log('User:', this.user); const userTypeId = JSON.parse(localStorage.getItem('User') || '{}').userTypeId; this.userTypeID = userTypeId; if (this.userTypeID === 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) { console.log('Member data received:', this.member); // Detailed debug statement console.log('Member ID:', this.member.member_ID); // Debug statement 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; const formValue = this.profileForm.value; // If no new photo is selected, use the existing photo let photoFile: File | null = this.photoFile; if (!photoFile && this.user?.photo) { // Ensure the Base64 string is valid const base64Prefix = 'data:image/jpeg;base64,'; let base64String = this.user.photo; if (base64String.startsWith(base64Prefix)) { base64String = base64String.replace(base64Prefix, ''); } try { const byteString = atob(base64String); const arrayBuffer = new ArrayBuffer(byteString.length); const intArray = new Uint8Array(arrayBuffer); for (let i = 0; i < byteString.length; i++) { intArray[i] = byteString.charCodeAt(i); } const blob = new Blob([intArray], { type: 'image/jpeg' }); photoFile = new File([blob], 'photo.jpg', { type: 'image/jpeg' }); } catch (e) { console.error('Invalid Base64 string:', e); } } // Create the user object const user: updateUser = { name: formValue.name, surname: formValue.surname, email: formValue.email, physical_Address: formValue.physical_Address, phoneNumber: formValue.phoneNumber, photo: this.user?.photo ?? '', // Ensure photo is included even if it's not updated user_Type_ID: this.user?.user_Type_ID ?? 0 }; this.userService.updateUser(userId, user, photoFile).subscribe({ next: (result) => { console.log('User updated successfully:', result); this.router.navigateByUrl(`/ProfilePage/${userId}`); alert('Successfully updated profile'); }, error: (error) => { console.error('Error updating user profile:', error); alert('Error updating profile.'); } }); } else { console.warn('Form is invalid:', this.profileForm.errors); } } onPhotoChange(event: Event): void { if (!this.isEditMode) return; const input = event.target as HTMLInputElement; if (input.files && input.files[0]) { const file = input.files[0]; // Ensure file is an image if (!file.type.startsWith('image/')) { console.error('Selected file is not an image.'); alert('Please select a valid image file.'); return; } const reader = new FileReader(); reader.onload = (e: any) => { if (e.target && e.target.result) { this.userProfileImage = e.target.result; // Update the image source for preview this.photoFile = file; // Set the file for form submission console.log('Base64 string of the image:', this.userProfileImage); } }; reader.onerror = (e) => { console.error('Error reading file:', e); alert('There was an error reading the file. Please try again.'); }; reader.onabort = (e) => { console.warn('File read aborted:', e); alert('File read was aborted. Please try again.'); }; reader.onloadend = () => { console.log('File read completed.'); }; try { reader.readAsDataURL(file); this.photoFile = file; } catch (error) { console.error('An error occurred while reading the file:', error); alert('An error occurred while reading the file. Please try again.'); } } else { console.error('No file selected or input element is invalid.'); } } 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 { console.log('Loading unredeemed rewards for member ID:', memberId); this.rewardService.getUnredeemedRewardsForMember(memberId).subscribe({ next: rewards => { this.unredeemedRewards = rewards; console.log("reward", rewards) }, error: error => { console.error('Error fetching unredeemed rewards:', error); }, complete: () => { console.log('Fetching unredeemed rewards completed.'); } }); } // 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 this.verificationCode = this.generateSixDigitCode(); // Generate code when opening modal console.log('Generated verification code:', this.verificationCode); $('#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'); } generateSixDigitCode(): string { const code = Math.floor(100000 + Math.random() * 900000).toString(); return `${code.slice(0, 3)}-${code.slice(3)}`; } }
Comments