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