//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);
}
}