diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8a6b280d..bf6604e3 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,8 +1,8 @@ -import { NgModule, CUSTOM_ELEMENTS_SCHEMA, APP_INITIALIZER } from '@angular/core'; +import { NgModule, CUSTOM_ELEMENTS_SCHEMA, inject, provideAppInitializer } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { RouteReuseStrategy } from '@angular/router'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; -import { HttpClient, HttpClientModule } from '@angular/common/http'; +import { provideHttpClient } from '@angular/common/http'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { CommonModule } from '@angular/common'; @@ -11,7 +11,7 @@ import { TranslateModule, TranslateService, } from '@ngx-translate/core'; -import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { TranslateHttpLoader, TRANSLATE_HTTP_LOADER_CONFIG } from '@ngx-translate/http-loader'; import { CoreModule } from './core/core.module'; import { Drivers, Storage } from '@ionic/storage'; import { IonicStorageModule } from '@ionic/storage-angular'; @@ -27,9 +27,6 @@ import { FrontendChatLibraryModule } from 'sl-chat-library'; import { MatToolbarModule } from '@angular/material/toolbar'; import { translateFactory } from './shared/components/translationFactory'; import { LocalStorageService } from './core/services'; -export const translateHttpLoaderFactory = (httpClient: HttpClient) => - new TranslateHttpLoader(httpClient, './assets/i18n/', '.json'); - @NgModule({ declarations: [AppComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], @@ -38,7 +35,6 @@ export const translateHttpLoaderFactory = (httpClient: HttpClient) => BrowserModule, IonicModule.forRoot(), AppRoutingModule, - HttpClientModule, MatTableModule, MatPaginatorModule, IonicStorageModule.forRoot({ @@ -49,8 +45,7 @@ export const translateHttpLoaderFactory = (httpClient: HttpClient) => TranslateModule.forRoot({ loader: { provide: TranslateLoader, - useFactory: translateHttpLoaderFactory, - deps: [HttpClient], + useClass: TranslateHttpLoader, }, }), ReactiveFormsModule, @@ -68,14 +63,19 @@ export const translateHttpLoaderFactory = (httpClient: HttpClient) => exports: [], providers: [ { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, + { + provide: TRANSLATE_HTTP_LOADER_CONFIG, + useValue: { prefix: './assets/i18n/', suffix: '.json' } + }, TitleCasePipe, SwUpdate, - { - provide: APP_INITIALIZER, - useFactory: translateFactory, - deps: [TranslateService, LocalStorageService], - multi: true - } + provideHttpClient(), + provideAppInitializer(() => { + const translateService = inject(TranslateService); + const localStorageService = inject(LocalStorageService); + + return translateFactory(translateService, localStorageService)(); + }), ], bootstrap: [AppComponent], }) diff --git a/src/app/modules/private/private.module.ts b/src/app/modules/private/private.module.ts index c6fd2c3a..a4848ac0 100644 --- a/src/app/modules/private/private.module.ts +++ b/src/app/modules/private/private.module.ts @@ -4,13 +4,10 @@ import { FormsModule } from '@angular/forms'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { PrivatePage } from './private.page'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { TranslateHttpLoader } from '@ngx-translate/http-loader'; -import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader, TRANSLATE_HTTP_LOADER_CONFIG } from '@ngx-translate/http-loader'; import { RouteReuseStrategy } from '@angular/router'; import { PrivatePageRoutingModule } from './private-routing.module'; import { SharedModule } from 'src/app/shared/shared.module'; -export const translateHttpLoaderFactory = (httpClient: HttpClient) => - new TranslateHttpLoader(httpClient, './assets/i18n/', '.json'); @NgModule({ declarations: [PrivatePage], imports: [ @@ -21,12 +18,17 @@ export const translateHttpLoaderFactory = (httpClient: HttpClient) => TranslateModule.forChild({ loader: { provide: TranslateLoader, - useFactory: translateHttpLoaderFactory, - deps: [HttpClient], + useClass: TranslateHttpLoader, }, }), SharedModule, ], - providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }], + providers: [ + { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, + { + provide: TRANSLATE_HTTP_LOADER_CONFIG, + useValue: { prefix: './assets/i18n/', suffix: '.json' } + }, + ], }) export class PrivatePageModule {} diff --git a/src/app/pages/chat-request/chat-request.page.html b/src/app/pages/chat-request/chat-request.page.html index c6dd0b3e..ae3fc351 100644 --- a/src/app/pages/chat-request/chat-request.page.html +++ b/src/app/pages/chat-request/chat-request.page.html @@ -4,92 +4,91 @@ + @let details = userDetails();
Profile Picture
- {{info?.user_details?.name}} - {{info?.user_details?.user_roles}} + {{details?.name}} + {{details?.user_roles}}
+ -
-
-
- Profile Picture -
- {{info?.meta?.message}} - {{info.created_at | date : 'hh:mm a'}} -
- -
-
-
-
-
- - - {{ messages[info?.status]?.title | translate: { name: - info?.user_details?.name } }} - -

- {{ messages[info?.status]?.subText | translate: { name: - info?.user_details?.name } }} -

-
+ @let currentStatus = status(); + @if (currentStatus !== 'ACCEPTED') { +
+
+ @if (currentStatus === 'REQUESTED') { +
+ Profile Picture +
+ {{info()?.meta?.message}} + {{info().created_at | date : 'hh:mm a'}} +
+
+ }
- - - {{'ACCEPT' | translate}} - - - {{'REJECT' | - translate}} - + @if (statusMessage(); as msg) { +
+
+ + + {{ msg.title | translate: { name: userDetails()?.name } }} + +

+ {{ msg.subText | translate: { name: userDetails()?.name } }} +

+
+
+ } + @if (showCurrentStatusActions()) { +
+ + + {{'ACCEPT' | translate}} + + + + {{'REJECT' | translate}} + +
+ }
-
+ } -
-
-
{{message?.length}} / {{messageLimit}} -
-
- +@if (showMessageInput()) { +
+
+
{{message()?.length}} / {{messageLimit}} +
+
+ - - + size="small" + (click)="sendRequest()" + style="width: 50px; height: 45px" + > + + +
-
\ No newline at end of file +} diff --git a/src/app/pages/chat-request/chat-request.page.spec.ts b/src/app/pages/chat-request/chat-request.page.spec.ts index d687f38e..11bce0db 100644 --- a/src/app/pages/chat-request/chat-request.page.spec.ts +++ b/src/app/pages/chat-request/chat-request.page.spec.ts @@ -72,7 +72,7 @@ describe('ChatRequestPage', () => { }); it('should initialize with default values', () => { - expect(component.message).toBe('Hi, I would like to connect with you.'); + expect(component.message()).toBe('Hi, I would like to connect with you.'); expect(component.headerConfig).toEqual({ menu: false, headerColor: 'primary' @@ -81,7 +81,7 @@ describe('ChatRequestPage', () => { }); it('should extract id from route params', () => { - expect(component.id).toBe('123'); + expect(component.id()).toBe('123'); }); describe('ngOnInit', () => { @@ -108,7 +108,7 @@ describe('ChatRequestPage', () => { fixture.whenStable().then(() => { expect(httpService.post).toHaveBeenCalled(); - expect(component.info.status).toBe('PENDING'); + expect(component.info().status).toBe('PENDING'); }); })); @@ -126,7 +126,7 @@ describe('ChatRequestPage', () => { component.getConnectionInfo(); fixture.whenStable().then(() => { - expect(component.message).toBe(''); + expect(component.message()).toBe(''); }); })); @@ -165,7 +165,7 @@ describe('ChatRequestPage', () => { component.getConnectionInfo(); fixture.whenStable().then(() => { - expect(component.messages).toEqual(CHAT_MESSAGES.INITIATOR); + expect(component.messages()).toEqual(CHAT_MESSAGES.INITIATOR); }); })); @@ -182,25 +182,25 @@ describe('ChatRequestPage', () => { component.getConnectionInfo(); fixture.whenStable().then(() => { - expect(component.messages).toEqual(CHAT_MESSAGES.RECEIVER); + expect(component.messages()).toEqual(CHAT_MESSAGES.RECEIVER); }); })); }); describe('sendRequest', () => { beforeEach(() => { - component.id = '123'; - component.message = 'Test message'; + component.id.set('123'); + component.message.set('Test message'); }); it('should not send request if message is empty or whitespace', () => { - component.message = ' '; + component.message.set(' '); component.sendRequest(); expect(httpService.post).not.toHaveBeenCalled(); }); it('should show error toast if message exceeds limit', () => { - component.message = 'a'.repeat(component.messageLimit + 1); + component.message.set('a'.repeat(component.messageLimit + 1)); component.sendRequest(); expect(toastService.showToast).toHaveBeenCalledWith('MESSAGE_TEXT_LIMIT', 'danger'); expect(httpService.post).not.toHaveBeenCalled(); @@ -215,7 +215,7 @@ describe('ChatRequestPage', () => { fixture.whenStable().then(() => { expect(httpService.post).toHaveBeenCalled(); - expect(component.info.status).toBe('REQUESTED'); + expect(component.info().status).toBe('REQUESTED'); expect(component.getConnectionInfo).toHaveBeenCalled(); }); })); @@ -236,10 +236,10 @@ describe('ChatRequestPage', () => { describe('acceptRequest', () => { beforeEach(() => { - component.id = '123'; - component.info = { + component.id.set('123'); + component.info.set({ user_details: { name: 'Jane Doe' } - }; + }); }); it('should accept request and navigate to chat', waitForAsync(() => { @@ -260,7 +260,7 @@ describe('ChatRequestPage', () => { 'Accepted message request from Jane Doe', 'success' ); - expect(component.info.status).toBe('ACCEPTED'); + expect(component.info().status).toBe('ACCEPTED'); expect(router.navigate).toHaveBeenCalledWith( [CommonRoutes.CHAT, 'room789'], { replaceUrl: true, queryParams: { id: 'conn123' } } @@ -269,7 +269,7 @@ describe('ChatRequestPage', () => { })); it('should use default name if user details name is not available', waitForAsync(() => { - component.info = { user_details: {} }; + component.info.set({ user_details: {} }); const mockResponse = { result: { id: 'conn123', @@ -330,7 +330,7 @@ describe('ChatRequestPage', () => { describe('rejectRequest', () => { beforeEach(() => { - component.id = '123'; + component.id.set('123'); }); it('should reject request and show toast', waitForAsync(() => { @@ -341,8 +341,8 @@ describe('ChatRequestPage', () => { fixture.whenStable().then(() => { expect(httpService.post).toHaveBeenCalled(); - expect(component.info.status).toBe('REJECTED'); - expect(component.messages).toEqual(CHAT_MESSAGES.RECEIVER); + expect(component.info().status).toBe('REJECTED'); + expect(component.messages()).toEqual(CHAT_MESSAGES.RECEIVER); expect(toastService.showToast).toHaveBeenCalledWith('REJECTED_MESSAGE_REQ', 'danger'); }); })); @@ -362,7 +362,7 @@ describe('ChatRequestPage', () => { describe('goToProfile', () => { it('should navigate to mentor details page', () => { - component.id = '123'; + component.id.set('123'); component.goToProfile(); expect(router.navigate).toHaveBeenCalledWith([CommonRoutes.MENTOR_DETAILS, '123']); }); @@ -375,12 +375,12 @@ describe('ChatRequestPage', () => { component.getConnectionInfo(); fixture.whenStable().then(() => { - expect(component.info).toBeNull(); + expect(component.info()).toBeNull(); }); })); it('should handle message with exact character limit', () => { - component.message = 'a'.repeat(component.messageLimit); + component.message.set('a'.repeat(component.messageLimit)); const mockResponse = { result: {} }; httpService.post.and.returnValue(Promise.resolve(mockResponse)); spyOn(component, 'getConnectionInfo'); @@ -391,13 +391,13 @@ describe('ChatRequestPage', () => { }); it('should trim whitespace from message before validation', () => { - component.message = ' '; + component.message.set(' '); component.sendRequest(); expect(httpService.post).not.toHaveBeenCalled(); }); it('should handle response without meta in acceptRequest', waitForAsync(() => { - component.info = { user_details: { name: 'Test User' } }; + component.info.set({ user_details: { name: 'Test User' } }); const mockResponse = { result: { id: 'conn123', @@ -410,7 +410,7 @@ describe('ChatRequestPage', () => { component.acceptRequest(); fixture.whenStable().then(() => { - expect(component.info.status).toBe('ACCEPTED'); + expect(component.info().status).toBe('ACCEPTED'); }); })); @@ -430,11 +430,11 @@ describe('ChatRequestPage', () => { describe('Message validation', () => { beforeEach(() => { - component.id = '123'; + component.id.set('123'); }); it('should trim leading whitespace before checking if empty', () => { - component.message = ' hello'; + component.message.set(' hello'); const mockResponse = { result: {} }; httpService.post.and.returnValue(Promise.resolve(mockResponse)); @@ -444,7 +444,7 @@ describe('ChatRequestPage', () => { }); it('should handle message at exactly the limit boundary', () => { - component.message = 'a'.repeat(component.messageLimit); + component.message.set('a'.repeat(component.messageLimit)); const mockResponse = { result: {} }; httpService.post.and.returnValue(Promise.resolve(mockResponse)); @@ -455,7 +455,7 @@ describe('ChatRequestPage', () => { }); it('should reject message one character over limit', () => { - component.message = 'a'.repeat(component.messageLimit + 1); + component.message.set('a'.repeat(component.messageLimit + 1)); component.sendRequest(); @@ -466,7 +466,7 @@ describe('ChatRequestPage', () => { describe('Constructor', () => { it('should subscribe to route params', () => { - expect(component.id).toBe('123'); + expect(component.id()).toBe('123'); }); }); @@ -484,7 +484,7 @@ describe('ChatRequestPage', () => { component.getConnectionInfo(); fixture.whenStable().then(() => { - expect(component.info.status).toBe('PENDING'); + expect(component.info().status).toBe('PENDING'); }); })); @@ -501,8 +501,63 @@ describe('ChatRequestPage', () => { component.getConnectionInfo(); fixture.whenStable().then(() => { - expect(component.info.status).toBe('REJECTED'); + expect(component.info().status).toBe('REJECTED'); }); })); }); -}); \ No newline at end of file + + describe('Computed signals', () => { + it('should compute status from info', () => { + component.info.set({ status: 'PENDING' }); + expect(component.status()).toBe('PENDING'); + }); + + it('should compute userDetails from info', () => { + component.info.set({ user_details: { name: 'Test' } }); + expect(component.userDetails()).toEqual({ name: 'Test' }); + }); + + it('should compute profileImage with fallback', () => { + component.info.set({ user_details: {} }); + expect(component.profileImage()).toBe('assets/prof-img/user.png'); + + component.info.set({ user_details: { image: 'custom.png' } }); + expect(component.profileImage()).toBe('custom.png'); + }); + + it('should compute isInitiator correctly', () => { + component.info.set({ created_by: '123', user_id: '123' }); + expect(component.isInitiator()).toBeTrue(); + + component.info.set({ created_by: '456', user_id: '123' }); + expect(component.isInitiator()).toBeFalse(); + }); + + it('should wait for participant role resolution before showing current status actions', () => { + component.info.set({ status: 'REQUESTED' }); + expect(component.hasResolvedParticipantRole()).toBeFalse(); + expect(component.showCurrentStatusActions()).toBeFalse(); + + component.info.set({ created_by: '456', user_id: '123', status: 'REQUESTED' }); + expect(component.hasResolvedParticipantRole()).toBeTrue(); + expect(component.showCurrentStatusActions()).toBeTrue(); + + component.info.set({ created_by: '123', user_id: '123', status: 'REQUESTED' }); + expect(component.showCurrentStatusActions()).toBeFalse(); + }); + + it('should compute showMessageInput correctly', () => { + component.info.set({ created_by: '123', user_id: '123', status: 'PENDING' }); + expect(component.showMessageInput()).toBeTrue(); + + component.info.set({ created_by: '123', user_id: '123', status: 'ACCEPTED' }); + expect(component.showMessageInput()).toBeFalse(); + }); + + it('should compute statusMessage from messages and status', () => { + component.info.set({ status: 'PENDING' }); + component.messages.set(CHAT_MESSAGES.INITIATOR); + expect(component.statusMessage()).toEqual(CHAT_MESSAGES.INITIATOR['PENDING']); + }); + }); +}); diff --git a/src/app/pages/chat-request/chat-request.page.ts b/src/app/pages/chat-request/chat-request.page.ts index d19beff2..ddbe2730 100644 --- a/src/app/pages/chat-request/chat-request.page.ts +++ b/src/app/pages/chat-request/chat-request.page.ts @@ -1,12 +1,12 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, signal, computed } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { AlertController } from '@ionic/angular'; import { TranslateService } from '@ngx-translate/core'; -import { replace } from 'lodash'; import { CHAT_MESSAGES } from 'src/app/core/constants/chatConstants'; import { urlConstants } from 'src/app/core/constants/urlConstants'; import { HttpService, ToastService, UtilService } from 'src/app/core/services'; import { CommonRoutes } from 'src/global.routes'; + @Component({ selector: 'app-chat-request', templateUrl: './chat-request.page.html', @@ -14,16 +14,41 @@ import { CommonRoutes } from 'src/global.routes'; standalone: false }) export class ChatRequestPage implements OnInit { - public headerConfig: any = { + readonly headerConfig = { menu: false, headerColor: 'primary', }; - id; - messageLimit = CHAT_MESSAGES.MESSAGE_TEXT_LIMIT; - message: string = 'Hi, I would like to connect with you.'; - info: any = {}; - messages = {}; + readonly messageLimit = CHAT_MESSAGES.MESSAGE_TEXT_LIMIT; + + id = signal(undefined); + message = signal('Hi, I would like to connect with you.'); + info = signal({}); + messages = signal({}); + + // Computed signals for derived state + readonly status = computed(() => this.info()?.status); + readonly userDetails = computed(() => this.info()?.user_details); + readonly profileImage = computed(() => this.userDetails()?.image || 'assets/prof-img/user.png'); + readonly statusMessage = computed(() => this.messages()?.[this.status()]); + readonly hasResolvedParticipantRole = computed(() => { + const i = this.info(); + return !!(i?.created_by && i?.user_id); + }); + readonly isInitiator = computed(() => { + const i = this.info(); + return i?.created_by && i?.user_id && i.created_by === i.user_id; + }); + readonly showCurrentStatusActions = computed(() => + this.hasResolvedParticipantRole() && this.status() !== 'REJECTED' && !this.isInitiator() + ); + readonly showMessageInput = computed(() => { + return this.status() === 'PENDING' || (this.isInitiator() && this.status() !== 'ACCEPTED'); + }); + readonly messageInfoBottom = computed(() => { + const i = this.info(); + return i?.hasOwnProperty?.('created_by') && i.created_by !== i.user_id ? '20px' : '100px'; + }); constructor( private httpService: HttpService, @@ -35,7 +60,7 @@ export class ChatRequestPage implements OnInit { private utilService: UtilService ) { routerParams.params.subscribe((parameters) => { - this.id = parameters?.id; + this.id.set(parameters?.id); }); } @@ -43,95 +68,102 @@ export class ChatRequestPage implements OnInit { this.getConnectionInfo(); } -getConnectionInfo() { - const payload = { - url: urlConstants.API_URLS.GET_CHAT_INFO, - payload: { - user_id: this.id, - }, - }; - this.httpService.post(payload) - .then((resp) => { - const result = resp?.result; - if (!result) { - this.info = null; - return; - } - this.info = result; - this.info.status = result.status ?? 'PENDING'; - if (this.info.status === 'REQUESTED') { - this.message = ''; - } else if (this.info.status === 'ACCEPTED') { - const roomId = result.meta?.room_id; - if (roomId) { - this.router.navigate( - [CommonRoutes.CHAT, roomId], - { queryParams: { id: result.id }, replaceUrl: true } - ); + getConnectionInfo() { + const payload = { + url: urlConstants.API_URLS.GET_CHAT_INFO, + payload: { + user_id: this.id(), + }, + }; + this.httpService.post(payload) + .then((resp) => { + const result = resp?.result; + if (!result) { + this.info.update(() => null); + return; } - } - if (this.info.created_by && this.info.user_id) { - this.messages = - this.info.created_by === this.info.user_id - ? CHAT_MESSAGES.INITIATOR - : CHAT_MESSAGES.RECEIVER; - } else { - this.messages = CHAT_MESSAGES.RECEIVER; - } - }) - .catch((err) => { - console.error('getConnectionInfo error', err); - }); -} + const infoData = { ...result }; + infoData.status = result.status ?? 'PENDING'; + + if (infoData.status === 'REQUESTED') { + this.message.set(''); + } else if (infoData.status === 'ACCEPTED') { + const roomId = result.meta?.room_id; + if (roomId) { + this.router.navigate( + [CommonRoutes.CHAT, roomId], + { queryParams: { id: result.id }, replaceUrl: true } + ); + } + } + + if (infoData.created_by && infoData.user_id) { + this.messages.update(() => + infoData.created_by === infoData.user_id + ? CHAT_MESSAGES.INITIATOR + : CHAT_MESSAGES.RECEIVER + ); + } else { + this.messages.update(() => CHAT_MESSAGES.RECEIVER); + } + + this.info.update(() => infoData); + }) + .catch((err) => { + console.error('getConnectionInfo error', err); + }); + } + sendRequest() { - if(this.message.trim() === ''){ + if (this.message().trim() === '') { return; } - if(this.message.length >this.messageLimit){ + if (this.message().length > this.messageLimit) { this.toast.showToast('MESSAGE_TEXT_LIMIT', 'danger'); return; } const payload = { url: urlConstants.API_URLS.SEND_REQUEST, payload: { - user_id: this.id, - message: this.message, + user_id: this.id(), + message: this.message(), }, }; this.httpService.post(payload).then((resp) => { - this.info.status = 'REQUESTED'; + this.info.update((prev) => ({ ...prev, status: 'REQUESTED' })); this.getConnectionInfo(); }); } - acceptRequest() { - const payload = { - url: urlConstants.API_URLS.ACCEPT_MSG_REQ, - payload: { - user_id: this.id, - }, - }; - this.httpService.post(payload) - .then((resp) => { - this.info = this.info ?? {}; - const name = this.info.user_details?.name ?? 'the user'; - const message = this.translate.instant('ACCEPTED_MESSAGE_REQ', { name }); - this.toast.showToast(message, 'success'); - this.info.status = 'ACCEPTED'; - const roomId = resp?.result?.meta?.room_id; - const connId = resp?.result?.id ?? null; - if (roomId) { - this.router.navigate([CommonRoutes.CHAT, roomId], { - replaceUrl: true, - queryParams: { id: connId }, - }); - } - }) - .catch((err) => { - console.error('acceptRequest error', err); - }); -} - async rejectConfirmation() { + acceptRequest() { + const payload = { + url: urlConstants.API_URLS.ACCEPT_MSG_REQ, + payload: { + user_id: this.id(), + }, + }; + this.httpService.post(payload) + .then((resp) => { + const currentInfo = this.info() ?? {}; + const name = currentInfo.user_details?.name ?? 'the user'; + const message = this.translate.instant('ACCEPTED_MESSAGE_REQ', { name }); + this.toast.showToast(message, 'success'); + this.info.update((prev) => ({ ...prev, status: 'ACCEPTED' })); + const roomId = resp?.result?.meta?.room_id; + const connId = resp?.result?.id ?? null; + if (roomId) { + this.router.navigate([CommonRoutes.CHAT, roomId], { + replaceUrl: true, + queryParams: { id: connId }, + }); + } + }) + .catch((err) => { + console.error('acceptRequest error', err); + }); + } + + async rejectConfirmation() { let texts: any; this.translate .get(['MESSAGE_REQ_REJECT', 'REJECT', 'CANCEL']) @@ -144,27 +176,29 @@ getConnectionInfo() { cancel: 'CANCEL', submit: 'Reject', }; - const response:any = await this.utilService.alertPopup(msg); + const response: any = await this.utilService.alertPopup(msg); if (response) { - this.rejectRequest(); + this.rejectRequest(); } else { console.log('User canceled the rejection'); } } + rejectRequest() { const payload = { url: urlConstants.API_URLS.REJECT_MSG_REQ, payload: { - user_id: this.id, + user_id: this.id(), }, }; this.httpService.post(payload).then((resp) => { - this.info.status = 'REJECTED'; - this.messages = CHAT_MESSAGES.RECEIVER; + this.info.update((prev) => ({ ...prev, status: 'REJECTED' })); + this.messages.update(() => CHAT_MESSAGES.RECEIVER); this.toast.showToast('REJECTED_MESSAGE_REQ', 'danger'); }); } - goToProfile(){ - this.router.navigate([CommonRoutes.MENTOR_DETAILS, this.id]); + + goToProfile() { + this.router.navigate([CommonRoutes.MENTOR_DETAILS, this.id()]); } } diff --git a/src/app/pages/chat-window/chat-window.page.html b/src/app/pages/chat-window/chat-window.page.html index 05e68a0a..187b62dd 100644 --- a/src/app/pages/chat-window/chat-window.page.html +++ b/src/app/pages/chat-window/chat-window.page.html @@ -1,4 +1,12 @@ - - + @if (showChat()) { + + + } diff --git a/src/app/pages/chat-window/chat-window.page.spec.ts b/src/app/pages/chat-window/chat-window.page.spec.ts index 33cee025..4258912c 100644 --- a/src/app/pages/chat-window/chat-window.page.spec.ts +++ b/src/app/pages/chat-window/chat-window.page.spec.ts @@ -77,32 +77,33 @@ describe('ChatWindowPage', () => { it('should create', () => { expect(component).toBeTruthy(); - expect(component.showChat).toBeTruthy(); + expect(component.showChat()).toBeFalsy(); expect(component.headerConfig).toBeDefined(); }); - it('should react to params emission: set rid and call ngOnInit', fakeAsync(() => { - const initSpy = spyOn(component, 'ngOnInit').and.callThrough(); + it('should react to params emission: set rid and initialize chat', fakeAsync(() => { + const initSpy = spyOn(component, 'initializeChat').and.callThrough(); paramsSubject.next({ id: 'room-1' }); tick(); - expect(component.rid).toBe('room-1'); + expect(component.rid()).toBe('room-1'); expect(initSpy).toHaveBeenCalled(); })); it('should set id from queryParams', fakeAsync(() => { queryParamsSubject.next({ id: 'external-1' }); tick(); - expect(component.id).toBe('external-1'); + expect(component.id()).toBe('external-1'); })); it('ngOnInit should call getChatToken, set showChat true and load translations', async () => { const fakeTranslations = { 'KEY1': 'one', 'KEY2': 'two' }; mockTranslate.get.and.returnValue(of(fakeTranslations)); mockProfileService.getChatToken.and.returnValue(Promise.resolve(true)); + component.rid.set('room-1'); await component.ngOnInit(); expect(mockProfileService.getChatToken).toHaveBeenCalled(); - expect(component.showChat).toBeTrue(); - expect(component.translations).toEqual(fakeTranslations); + expect(component.showChat()).toBeTrue(); + expect(component.translations()).toEqual(fakeTranslations); }); it('onBack should call location.back', () => { @@ -139,21 +140,21 @@ describe('ChatWindowPage', () => { }); it('ionViewWillLeave should set rocket.isWebSocketInitialized false when rid exists', () => { - component.rid = 'abc'; + component.rid.set('abc'); (mockRocket as any).isWebSocketInitialized = true; component.ionViewWillLeave(); expect((mockRocket as any).isWebSocketInitialized).toBeFalse(); }); it('ngOnDestroy should set rocket.isWebSocketInitialized false when rid exists', () => { - component.rid = 'room-2'; + component.rid.set('room-2'); (mockRocket as any).isWebSocketInitialized = true; component.ngOnDestroy(); expect((mockRocket as any).isWebSocketInitialized).toBeFalse(); }); it('ngOnDestroy should not throw when no rid present (defensive)', () => { - component.rid = null; + component.rid.set(null); (mockRocket as any).isWebSocketInitialized = true; expect(() => component.ngOnDestroy()).not.toThrow(); expect((mockRocket as any).isWebSocketInitialized).toBeTrue(); @@ -162,16 +163,17 @@ describe('ChatWindowPage', () => { it('constructor queryParams subscription updates id when later emitted', fakeAsync(() => { queryParamsSubject.next({ id: 'first' }); tick(); - expect(component.id).toBe('first'); + expect(component.id()).toBe('first'); queryParamsSubject.next({ id: 'second' }); tick(); - expect(component.id).toBe('second'); + expect(component.id()).toBe('second'); })); it('translate.get should be called with keys from CHAT_LIB_META_KEYS', async () => { const keys = Object.values(CHAT_LIB_META_KEYS); mockTranslate.get.and.returnValue(of({})); + component.rid.set('room-1'); await component.ngOnInit(); expect(mockTranslate.get).toHaveBeenCalledWith(keys); }); diff --git a/src/app/pages/chat-window/chat-window.page.ts b/src/app/pages/chat-window/chat-window.page.ts index 8251433b..d4bf3df6 100644 --- a/src/app/pages/chat-window/chat-window.page.ts +++ b/src/app/pages/chat-window/chat-window.page.ts @@ -1,5 +1,5 @@ import { Location } from '@angular/common'; -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { CHAT_LIB_META_KEYS } from 'src/app/core/constants/formConstant'; import { urlConstants } from 'src/app/core/constants/urlConstants'; @@ -15,64 +15,84 @@ import { RocketChatApiService } from 'sl-chat-library'; styleUrls: ['./chat-window.page.scss'], standalone: false }) -export class ChatWindowPage implements OnInit { - showChat: boolean = false; +export class ChatWindowPage implements OnInit, OnDestroy { + private readonly routerParams = inject(ActivatedRoute); + private readonly location = inject(Location); + private readonly profileService = inject(ProfileService); + private readonly router = inject(Router); + private readonly apiServer = inject(HttpService); + private readonly toastService = inject(ToastService); + private readonly translate = inject(TranslateService); + private readonly rocket = inject(RocketChatApiService); + + readonly showChat = signal(false); public headerConfig: any = { menu: false, headerColor: 'primary', }; - rid: any; - id : any; - translations: any; - constructor( - private routerParams: ActivatedRoute, - private location: Location, - private profileService: ProfileService, - private router: Router, - private apiServer : HttpService, - private toastService: ToastService, - private translate: TranslateService, - private rocket: RocketChatApiService - ) { - routerParams.params.subscribe((parameters) => { - this.rid = parameters?.id; - this.ngOnInit(); + readonly rid = signal(null); + readonly id = signal(null); + readonly translations = signal>({}); + + constructor() { + this.routerParams.params.subscribe((parameters) => { + this.rid.set(parameters?.id ?? null); + void this.initializeChat(); }); - routerParams.queryParams.subscribe((parameters) => { - this.id = parameters?.id; - }) + + this.routerParams.queryParams.subscribe((parameters) => { + this.id.set(parameters?.id ?? null); + }); + } + + async ngOnInit(): Promise { + if (!this.rid()) { + return; + } + + await this.initializeChat(); } - async ngOnInit() { + private async initializeChat(): Promise { + if (!this.rid()) { + return; + } + await this.profileService.getChatToken(); - this.showChat = true; + this.showChat.set(true); const keys = Object.values(CHAT_LIB_META_KEYS); this.translate.get(keys).subscribe(res => { - this.translations = res; + this.translations.set(res); }); } - onBack() { + + onBack(): void { this.location.back(); } - onClickProfile(externalId){ - this.apiServer.post({url:urlConstants.API_URLS.GETUSERIDBYRID, payload:{"external_user_id":externalId}}).then((resp) =>{ + async onClickProfile(externalId: string): Promise { + const resp = await this.apiServer.post({ + url: urlConstants.API_URLS.GETUSERIDBYRID, + payload: { external_user_id: externalId } + }); + if (resp?.result?.user_id) { this.router.navigate([CommonRoutes.MENTOR_DETAILS, resp?.result?.user_id]); - }) + } } - limitExceeded(event){ + limitExceeded(_event: unknown): void { this.toastService.showToast('MESSAGE_TEXT_LIMIT','danger'); } ngOnDestroy(): void { - if(this.rid) - this.rocket.isWebSocketInitialized =false; - + if (this.rid()) { + this.rocket.isWebSocketInitialized = false; + } } - ionViewWillLeave() { - if(this.rid) - this.rocket.isWebSocketInitialized =false; + ionViewWillLeave(): void { + if (this.rid()) { + this.rocket.isWebSocketInitialized = false; + } } } diff --git a/src/app/pages/login-activity/login-activity.page.ts b/src/app/pages/login-activity/login-activity.page.ts index afd683c3..822ddc98 100644 --- a/src/app/pages/login-activity/login-activity.page.ts +++ b/src/app/pages/login-activity/login-activity.page.ts @@ -8,7 +8,7 @@ import { import { MenuController } from '@ionic/angular'; import { SessionService } from 'src/app/core/services/session/session.service'; import { localKeys } from 'src/app/core/constants/localStorage.keys'; -import jwt_decode from 'jwt-decode'; +import { jwtDecode } from 'jwt-decode'; import { paginatorConstants } from 'src/app/core/constants/paginatorConstants'; import { MatPaginator } from '@angular/material/paginator'; @@ -49,7 +49,7 @@ export class LoginActivityPage implements OnInit { async ngOnInit() { this.sessionActivities(); let token = await this.localStorage.getLocalData(localKeys.TOKEN); - this.sessionData = jwt_decode(token.access_token); + this.sessionData = jwtDecode(token.access_token); this.sessionId = this.sessionData?.data?.session_id; } diff --git a/src/app/pages/mentor-search-directory/mentor-search-directory.page.ts b/src/app/pages/mentor-search-directory/mentor-search-directory.page.ts index d31163bb..3b97d2c4 100644 --- a/src/app/pages/mentor-search-directory/mentor-search-directory.page.ts +++ b/src/app/pages/mentor-search-directory/mentor-search-directory.page.ts @@ -140,10 +140,14 @@ export class MentorSearchDirectoryPage implements OnInit { } async onClearSearch($event: string) { - const current = this.searchAndCriterias(); - current.headerData.searchText = ''; - current.headerData.criterias = undefined; - this.searchAndCriterias.set({ ...current }); + this.searchAndCriterias.update(current => ({ + ...current, + headerData: { + ...current.headerData, + searchText: '', + criterias: undefined + } + })); this.router.navigate([], { relativeTo: this.route, @@ -223,9 +227,13 @@ export class MentorSearchDirectoryPage implements OnInit { eventHandler(event: any) { this.valueFromChipAndFilter.set(event); - const current = this.searchAndCriterias(); - current.headerData.criterias = { name: undefined, label: undefined }; - this.searchAndCriterias.set({ ...current }); + this.searchAndCriterias.update(current => ({ + ...current, + headerData: { + ...current.headerData, + criterias: { name: undefined, label: undefined } + } + })); } onPageChange(event) { @@ -235,14 +243,12 @@ export class MentorSearchDirectoryPage implements OnInit { } removeFilteredData(chip) { - const updatedFilterData = this.filterData().map((filter) => { - filter.options.map((option) => { - if (option.value === chip) { - option.selected = false; - } - }); - return filter; - }); + const updatedFilterData = this.filterData().map(filter => ({ + ...filter, + options: filter.options.map(option => ( + option.value === chip ? { ...option, selected: false } : option + )) + })); this.filterData.set(updatedFilterData); const currentFiltered = { ...this.filteredDatas() }; @@ -323,4 +329,4 @@ export class MentorSearchDirectoryPage implements OnInit { this.chips.set([]); this.urlQueryData.set(null); } -} \ No newline at end of file +} diff --git a/src/app/pages/session-request-details/session-request-details.page.html b/src/app/pages/session-request-details/session-request-details.page.html index 632baf83..550c0759 100644 --- a/src/app/pages/session-request-details/session-request-details.page.html +++ b/src/app/pages/session-request-details/session-request-details.page.html @@ -1,223 +1,238 @@ -
- - Profile Picture - + @if (apiResponse(); as response) { + @let details = sessionDetails(); + @let schedule = scheduledSessionDetals(); +
+ + Profile Picture + -

{{apiResponse?.user_details?.name | titlecase}}

-

- - - {{ item.label | titlecase }}, - - -

- {{"VIEW_PROFILE" | translate}} - -

{{"AGENDA" | translate }}

-

- {{ showFullText ? (apiResponse.agenda | titlecase) : (apiResponse.agenda | titlecase | slice:0:150 ) }}{{ apiResponse.agenda.length > 150 && !showFullText ? '...' : '' }} +

{{ response?.user_details?.name | titlecase }}

+

+ @if (response?.user_details?.designation?.length) { + @for (item of response.user_details.designation; track item.label; let last = $last) { + {{ item.label | titlecase }} + @if (!last) { + , + } + } + }

-
- - - {{ showFullText ? 'View Less' : 'View More' }} - + {{ 'VIEW_PROFILE' | translate }} + +

{{ 'AGENDA' | translate }}

+

+ {{ showFullText() ? (response.agenda | titlecase) : (response.agenda | titlecase | slice:0:150) }}{{ response.agenda.length > 150 && !showFullText() ? '...' : '' }} +

+
-

- {{ - apiResponse?.created_by == apiResponse?.user_details?.user_id - ? (apiResponse?.user_details?.name | titlecase) + ' ' + ("IS_REQUESTING" | translate) - : ("YOU_HAVE" | translate) - }} - {{"A_SLOT" | translate}} - {{ - formatUnixTime(apiResponse.start_date) - }} - - {{ - formatUnixTime(apiResponse.end_date) - }} - {{"ON" | translate}} - {{ apiResponse.start_date * 1000 | date : "EEEE (dd MMM yyyy)" }} - -

+ @if (response.agenda.length > 150) { + + {{ showFullText() ? 'View Less' : 'View More' }} + + } -

- - {{"SLOT_BOOKED" | translate}} {{ - apiResponse?.created_by == apiResponse?.user_details?.user_id - ? (apiResponse?.user_details?.name | titlecase) - : ("YOU_HAVE" | translate) - }} - at - {{ - formatUnixTime(apiResponse.start_date) - }} - - {{ - formatUnixTime(apiResponse.end_date) - }} - {{"ON" | translate}} - {{ apiResponse.start_date * 1000 | date : "EEEE (dd MMM yyyy)" }} - -

+ @if (response.status !== 'ACCEPTED') { +

+ {{ + response?.created_by == response?.user_details?.user_id + ? (response?.user_details?.name | titlecase) + ' ' + ('IS_REQUESTING' | translate) + : ('YOU_HAVE' | translate) + }} + {{ 'A_SLOT' | translate }} + {{ formatUnixTime(response.start_date) }} - + {{ formatUnixTime(response.end_date) }} + {{ 'ON' | translate }} + {{ response.start_date * 1000 | date : 'EEEE (dd MMM yyyy)' }} + +

+ } @else { +

+ {{ 'SLOT_BOOKED' | translate }} + {{ + response?.created_by == response?.user_details?.user_id + ? (response?.user_details?.name | titlecase) + : ('YOU_HAVE' | translate) + }} + at + {{ formatUnixTime(response.start_date) }} - + {{ formatUnixTime(response.end_date) }} + {{ 'ON' | translate }} + {{ response.start_date * 1000 | date : 'EEEE (dd MMM yyyy)' }} + +

+ } - -
-

{{"MEETING_LINK" | translate}}

- @if (sessionDetails && sessionDetails?.meeting_info?.value === 'Zoom') { + @if (response && response.requestor_id === response.user_details.user_id && response.status === 'ACCEPTED') { +
+

{{ 'MEETING_LINK' | translate }}

+ @if (details && details?.meeting_info?.value === 'Zoom') {
- {{"MEETING_TAKE_PLACE" | translate}} {{sessionDetails?.meeting_info?.platform}}
- {{"MEETING_ID" | translate}} : {{sessionDetails?.meeting_info?.meta?.meetingId}}
- {{"PASSCODE" | translate}} : {{sessionDetails?.meeting_info?.meta?.password}} + {{ 'MEETING_TAKE_PLACE' | translate }} {{ details?.meeting_info?.platform }}
+ {{ 'MEETING_ID' | translate }} : {{ details?.meeting_info?.meta?.meetingId }}
+ {{ 'PASSCODE' | translate }} : {{ details?.meeting_info?.meta?.password }}
- } @else if (sessionDetails?.meeting_info?.value === 'Gmeet') { + } @else if (details?.meeting_info?.value === 'Gmeet') {
- {{sessionDetails?.meeting_info?.platform}} : {{sessionDetails?.meeting_info?.link}} + {{ details?.meeting_info?.platform }} : {{ details?.meeting_info?.link }}
- } @else if(sessionDetails?.meeting_info?.value === 'Whatsapp') { + } @else if (details?.meeting_info?.value === 'Whatsapp') {
- {{sessionDetails?.meeting_info?.platform}} : {{sessionDetails?.meeting_info?.link}} + {{ details?.meeting_info?.platform }} : {{ details?.meeting_info?.link }}
- } @else if(sessionDetails?.meeting_info?.value === 'BBB' || sessionDetails?.meeting_info?.value === 'Default') { + } @else if (details?.meeting_info?.value === 'BBB' || details?.meeting_info?.value === 'Default') {
- {{sessionDetails?.meeting_info?.platform}} : Default + {{ details?.meeting_info?.platform }} : Default
- } @else if(sessionDetails?.meeting_info?.value ==='OFF') { + } @else if (details?.meeting_info?.value === 'OFF') { } @else {
- {{sessionDetails?.meeting_info?.platform}} : {{sessionDetails?.meeting_info?.link}} + {{ details?.meeting_info?.platform }} : {{ details?.meeting_info?.link }}
} - {{"ADD_LINK" | translate}} - {{"EDIT_LINK" | translate}} -
+ @if (!isMeetingLinkAdded()) { + {{ 'ADD_LINK' | translate }} + } @else { + {{ 'EDIT_LINK' | translate }} + } +
+ } - @if (isRejected) { - {{"SLOT_REJECT" | translate}} - } @else if (isAccepted) { - {{"START_BTN_ENABLE" | translate}} - } @else if (apiResponse && apiResponse.user_details.user_id === apiResponse.requestor_id) { - {{"SLOT_AVAILABLE" | translate}} + @if (isRejected()) { + {{ 'SLOT_REJECT' | translate }} + } @else if (isAccepted()) { + {{ 'START_BTN_ENABLE' | translate }} + } @else if (response && response.user_details.user_id === response.requestor_id) { + {{ 'SLOT_AVAILABLE' | translate }} } @else { - {{ "PENDING" | translate}} + {{ 'PENDING' | translate }} } - - - - - - - - {{"SELECT_MEETING_PLATFORM" | translate}} - - - - - - {{ option.name }} - - - - - {{selectedHint}} - - -
- + + @if (details && schedule.length > 0 && response && response.user_details.user_id === response.requestor_id) { + + + + + + + {{ 'SELECT_MEETING_PLATFORM' | translate }} + + + + + @for (option of meetingPlatforms; track option.value) { + + {{ option.name }} + + } + + + + {{ selectedHint }} + + + @for (option of meetingPlatforms; track option.value) { + @if (selectedLink == option) { + + } + } + + + + + + +
+ {{ 'ADD_NOW' | translate }} + {{ 'ADD_LATER' | translate }}
- - - - - - -
- {{"ADD_NOW" | translate}} - {{"ADD_LATER" | translate}} -
-
-
-
-
- - - - - - - - {{"SCHEDULE" | translate}} - - -
-
- {{event.date}} -
- -
-
-
-
-
-
-
+ + + + + } - - -

{{ formatUnixTime(event?.startTime) }}


-

{{ formatUnixTime(event?.endTime) }}

-
-

{{event?.title | titlecase}}

-
-
-
- - -
- - - -
- -
-
- -
-
- - - -
- - {{"ACCEPT" | translate}} - - - {{"REJECT" | translate}} - -
-
- - {{"START_SESSIONS" | translate}} - - -
-
+ @if (schedule.length > 0 && response && response.user_details.user_id === response.requestor_id) { + + + + + + + + {{ 'SCHEDULE' | translate }} + + + @for (dateEvent of schedule; track dateEvent.date) { +
+
+ {{ dateEvent.date }} +
+
+
+
+
+
+
+ @for (slotEvent of dateEvent.bookedSlots; track slotEvent.startTime) { +
+ + +

{{ formatUnixTime(slotEvent?.startTime) }}


+

{{ formatUnixTime(slotEvent?.endTime) }}

+
+ +

{{ slotEvent?.title | titlecase }}

+
+
+
+ } +
+
+
+ } +
+
+ } +
+ } + +@if (apiResponse(); as response) { + @let details = sessionDetails(); + @if (response.user_details.user_id === response.requestor_id && response.status !== 'REJECTED') { + + + @if (!isAccepted() && response.status === 'REQUESTED') { +
+ + {{ 'ACCEPT' | translate }} + + + {{ 'REJECT' | translate }} + +
+ } @else { + @if (details?.status?.value == 'PUBLISHED' || details?.status?.value == 'LIVE') { + {{ 'START_SESSIONS' | translate }} + + } + } +
+
+ } +} diff --git a/src/app/pages/session-request-details/session-request-details.page.spec.ts b/src/app/pages/session-request-details/session-request-details.page.spec.ts index f0f7cec6..6c376879 100644 --- a/src/app/pages/session-request-details/session-request-details.page.spec.ts +++ b/src/app/pages/session-request-details/session-request-details.page.spec.ts @@ -62,7 +62,7 @@ describe('SessionRequestDetailsPage', () => { fixture = TestBed.createComponent(SessionRequestDetailsPage); component = fixture.componentInstance; component.modal = mockModal as any; - component.params = { id: '123' }; + component.params.set({ id: '123' }); // Default mocks/spies mockFormService.getForm.and.returnValue(Promise.resolve({ data: { fields: { forms: [{ name: 'zoom', hint: 'zoom_hint', value: 'zoom_val' }] } } })); @@ -77,7 +77,7 @@ describe('SessionRequestDetailsPage', () => { mockSessionService.requestSessionUserAvailability.and.returnValue(Promise.resolve({ result: [] })); // Initialize component data to prevent template errors - component.apiResponse = { + component.apiResponse.set({ id: 'req_1', session_id: 'sess_1', start_date: 100, @@ -92,9 +92,9 @@ describe('SessionRequestDetailsPage', () => { image: 'img.png', designation: [{ label: 'dev' }] } - }; - component.userId = 'user_123'; - component.scheduledSessionDetals = []; // Initialize to prevent template error + }); + component.userId.set('user_123'); + component.scheduledSessionDetals.set([]); // Initialize to prevent template error fixture.detectChanges(); })); @@ -112,10 +112,10 @@ describe('SessionRequestDetailsPage', () => { component.ionViewWillEnter(); tick(); - expect(component.userId).toBeDefined(); - expect(component.params).toEqual({ id: '123' }); + expect(component.userId()).toBeDefined(); + expect(component.params()).toEqual({ id: '123' }); expect(mockSessionService.getReqSessionDetails).toHaveBeenCalledWith('123'); - expect(component.apiResponse).toBeDefined(); + expect(component.apiResponse()).toBeDefined(); })); it('should fetch session details if ACCEPTED', fakeAsync(() => { @@ -127,18 +127,18 @@ describe('SessionRequestDetailsPage', () => { tick(); expect(mockSessionService.getSessionDetailsAPI).toHaveBeenCalledWith('sess_1'); - expect(component.isMeetingLinkAdded).toBeTrue(); - expect(component.isEnabled).toBeFalse(); // far future + expect(component.isMeetingLinkAdded()).toBeTrue(); + expect(component.isEnabled()).toBeFalse(); // far future })); }); describe('toggleText', () => { it('should toggle showFullText', () => { - component.showFullText = false; + component.showFullText.set(false); component.toggleText(); - expect(component.showFullText).toBeTrue(); + expect(component.showFullText()).toBeTrue(); component.toggleText(); - expect(component.showFullText).toBeFalse(); + expect(component.showFullText()).toBeFalse(); }); }); @@ -153,7 +153,7 @@ describe('SessionRequestDetailsPage', () => { expect(mockSessionService.requestSessionAccept).toHaveBeenCalledWith('req_1'); expect(mockToastService.showToast).toHaveBeenCalledWith('Accepted', 'success'); expect(mockSessionService.requestSessionUserAvailability).toHaveBeenCalled(); // via getAllUpdatedSession - expect(component.isAccepted).toBeTrue(); + expect(component.isAccepted()).toBeTrue(); })); }); @@ -169,7 +169,7 @@ describe('SessionRequestDetailsPage', () => { expect(mockUtilService.alertPopup).toHaveBeenCalled(); expect(mockSessionService.requestSessionReject).toHaveBeenCalledWith('req_1', 'Busy'); expect(mockToastService.showToast).toHaveBeenCalledWith('Rejected', 'danger'); - expect(component.isRejected).toBeTrue(); + expect(component.isRejected()).toBeTrue(); })); it('should not reject if cancelled', fakeAsync(() => { @@ -186,8 +186,8 @@ describe('SessionRequestDetailsPage', () => { describe('addLink', () => { it('should open modal and set session id', () => { component.addLink(true, 'sess_1'); - expect(component.isModalOpen).toBeTrue(); - expect(component.sessionId).toBe('sess_1'); + expect(component.isModalOpen()).toBeTrue(); + expect(component.sessionId()).toBe('sess_1'); }); }); @@ -201,8 +201,8 @@ describe('SessionRequestDetailsPage', () => { value: { link: 'http://zoom.us', password: 'pass', meetingId: '123' } } } as any; - component.sessionId = 'sess_1'; - component.params = { id: 'req_1' }; + component.sessionId.set('sess_1'); + component.params.set({ id: 'req_1' }); mockSessionService.createSession.and.returnValue(Promise.resolve({})); mockSessionService.getReqSessionDetails.and.returnValue(Promise.resolve({ @@ -235,8 +235,8 @@ describe('SessionRequestDetailsPage', () => { } }); expect(mockSessionService.createSession).toHaveBeenCalledWith(component.meetingInfo, 'sess_1'); - expect(component.isMeetingLinkAdded).toBeTrue(); - expect(component.editSessionBtn).toBeTrue(); + expect(component.isMeetingLinkAdded()).toBeTrue(); + expect(component.editSessionBtn()).toBeTrue(); })); }); @@ -256,17 +256,17 @@ describe('SessionRequestDetailsPage', () => { } } ]; - component.sessionDetails = { + component.sessionDetails.set({ meeting_info: { platform: 'Google Meet', link: 'http://meet.google.com', meta: { meetingId: 'm1', password: 'p1' } } - }; + }); component.editLink(true, 'sess_1'); - expect(component.isModalOpen).toBeTrue(); + expect(component.isModalOpen()).toBeTrue(); expect(component.selectedLink.name).toBe('Google Meet'); // Verify controls were updated (by reference) const controls = component.meetingPlatforms[0].form.controls; @@ -280,7 +280,7 @@ describe('SessionRequestDetailsPage', () => { component.modal = mockModal; // Ensure modal is set component.addLater(); expect(mockModal.dismiss).toHaveBeenCalled(); - expect(component.isModalOpen).toBeFalse(); + expect(component.isModalOpen()).toBeFalse(); }); }); diff --git a/src/app/pages/session-request-details/session-request-details.page.ts b/src/app/pages/session-request-details/session-request-details.page.ts index 390f9c54..dd2918fe 100644 --- a/src/app/pages/session-request-details/session-request-details.page.ts +++ b/src/app/pages/session-request-details/session-request-details.page.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit, ViewChild, inject, signal } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { IonModal } from '@ionic/angular'; import * as moment from 'moment'; @@ -17,34 +17,42 @@ import { CommonRoutes } from 'src/global.routes'; standalone: false }) export class SessionRequestDetailsPage implements OnInit { + private readonly form = inject(FormService); + private readonly sessionService = inject(SessionService); + private readonly toast = inject(ToastService); + private readonly utilService = inject(UtilService); + private readonly activateRoute = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly http = inject(HttpService); + @ViewChild('platformForm') platformForm: DynamicFormComponent; @ViewChild(IonModal) modal!: IonModal; - showFullText: boolean; - isAccepted: boolean; - meetingPlatforms: any; + readonly showFullText = signal(false); + readonly isAccepted = signal(false); + meetingPlatforms: any[] = []; selectedLink: any; - selectedHint: any; - editSessionBtn: boolean = false; - isMeetingLinkAdded: boolean =false; - isModalOpen: boolean = false; - isRejected: boolean = false; - params: any; - apiResponse: any; - scheduledSessionDetals: any; - meetingInfo: { meeting_info: { platform: any; link: any; value: any; meta: { password: any; meetingId: any; }; }; }; - sessionId: any; - sessionDetails: any; - isEnabled: boolean; - userId : any; - constructor( - private form: FormService, - private sessionService: SessionService, - private toast: ToastService, - private utilService: UtilService, - private activateRoute: ActivatedRoute, - private router: Router, - private http: HttpService - ) { } + selectedHint = ''; + readonly editSessionBtn = signal(false); + readonly isMeetingLinkAdded = signal(false); + readonly isModalOpen = signal(false); + readonly isRejected = signal(false); + readonly params = signal({}); + readonly apiResponse = signal(null); + readonly scheduledSessionDetals = signal([]); + meetingInfo: { + meeting_info: { + platform: any; + link: any; + value: any; + meta: { password: any; meetingId: any }; + }; + }; + readonly sessionId = signal(null); + readonly sessionDetails = signal(null); + readonly isEnabled = signal(false); + readonly userId = signal(null); + + constructor() {} public headerConfig: any = { backButton: true, headerColor: 'primary' @@ -54,56 +62,61 @@ export class SessionRequestDetailsPage implements OnInit { ionViewWillEnter() { this.getPlatformFormDetails(); - this.userId = localStorage.getItem('userId'); - this.activateRoute.queryParams.subscribe((params) => {this.params = params}); - this.sessionService.getReqSessionDetails(this.params.id).then((res) => { - this.apiResponse = res.result; - if (this.apiResponse?.status === 'ACCEPTED') { - this.sessionService.getSessionDetailsAPI(this.apiResponse.session_id).then((res) => { - - this.sessionDetails = res.result; - this.isMeetingLinkAdded = true; - let currentTimeInSeconds=Math.floor(Date.now()/1000); - this.isEnabled = ((this.sessionDetails.start_date - currentTimeInSeconds) < 600 || this.sessionDetails?.status?.value=='LIVE') ? true : false; - }) - } + this.userId.set(localStorage.getItem('userId')); + this.activateRoute.queryParams.subscribe((params) => { + this.params.set(params); + this.loadRequestDetails(params.id); }); this.getAllUpdatedSession(); } + private async loadRequestDetails(id: any): Promise { + const res = await this.sessionService.getReqSessionDetails(id); + this.apiResponse.set(res.result); + if (this.apiResponse()?.status === 'ACCEPTED') { + const sessionRes = await this.sessionService.getSessionDetailsAPI(this.apiResponse()?.session_id); + this.sessionDetails.set(sessionRes.result); + this.isMeetingLinkAdded.set(true); + const currentTimeInSeconds = Math.floor(Date.now() / 1000); + const details = this.sessionDetails(); + this.isEnabled.set( + ((details.start_date - currentTimeInSeconds) < 600 || details?.status?.value === 'LIVE') + ); + } + } + getAllUpdatedSession() { - const currentEpoch = Math.floor(Date.now() / 1000); + const currentEpoch = Math.floor(Date.now() / 1000); const thirtyDaysLaterEpoch = Math.floor((Date.now() + 30 * 24 * 60 * 60 * 1000) / 1000); this.sessionService.requestSessionUserAvailability(currentEpoch, thirtyDaysLaterEpoch).then((res) => { - this.scheduledSessionDetals = res.result; + this.scheduledSessionDetals.set(res.result); }); } toggleText() { - this.showFullText = !this.showFullText; + this.showFullText.update((value) => !value); } - accept(id:any){ - + accept(id: any) { this.sessionService.requestSessionAccept(id).then((res) => { if (res) { - this.isAccepted = true; + this.isAccepted.set(true); this.toast.showToast(res.message, 'success'); - this.sessionService.getReqSessionDetails(this.params.id).then((res) => { - this.apiResponse = res.result; - if(res){ + this.sessionService.getReqSessionDetails(this.params().id).then((requestRes) => { + this.apiResponse.set(requestRes.result); + if (requestRes) { this.getAllUpdatedSession(); - this.sessionService.getSessionDetailsAPI(this.apiResponse.session_id).then((res) => { - this.sessionDetails = res.result; - }) + this.sessionService.getSessionDetailsAPI(this.apiResponse().session_id).then((sessionRes) => { + this.sessionDetails.set(sessionRes.result); + }); } }); } - }) + }); } - async reject(id:any, name: string) { - let msg = { + async reject(id: any, name: string) { + const msg = { header: 'Reject ?', message: 'Are you sure you want to reject this session request?', cancel: 'CANCEL', @@ -123,100 +136,107 @@ export class SessionRequestDetailsPage implements OnInit { if (response) { this.sessionService.requestSessionReject(id, response?.reason).then((res) => { if (res) { - this.isRejected = true; - this.sessionService.getReqSessionDetails(this.params.id).then((res) => { - this.apiResponse = res.result;}); + this.isRejected.set(true); + this.sessionService.getReqSessionDetails(this.params().id).then((requestRes) => { + this.apiResponse.set(requestRes.result); + }); this.toast.showToast(res.message, 'danger'); } - }) + }); } else { console.log('User canceled the rejection'); } } - addLink(isOpen: boolean, id:any) { - this.isModalOpen = isOpen; - this.sessionId = id; + addLink(isOpen: boolean, id: any) { + this.isModalOpen.set(isOpen); + this.sessionId.set(id); } async getPlatformFormDetails() { - let form = await this.form.getForm(PLATFORMS); + const form = await this.form.getForm(PLATFORMS); this.meetingPlatforms = form.data.fields.forms; this.selectedLink = this.meetingPlatforms[0]; this.selectedHint = this.meetingPlatforms[0].hint; } - clickOptions(event:any){ + clickOptions(event: any) { this.selectedHint = event.detail.value.hint; } - compareWithFn(o1, o2) { + compareWithFn(o1: any, o2: any) { return o1 === o2; - }; + } - addNow(){ + addNow() { this.modal.dismiss(); - this.isModalOpen =false; - if (this.platformForm?.myForm?.valid){ + this.isModalOpen.set(false); + if (this.platformForm?.myForm?.valid) { this.meetingInfo = { - 'meeting_info':{ - 'platform': this.selectedLink.name, - 'link': this.platformForm.myForm.value?.link, - 'value': this.selectedLink.value, - "meta": { - "password": this.platformForm.myForm.value?.password, - "meetingId":this.platformForm.myForm.value?.meetingId + meeting_info: { + platform: this.selectedLink.name, + link: this.platformForm.myForm.value?.link, + value: this.selectedLink.value, + meta: { + password: this.platformForm.myForm.value?.password, + meetingId: this.platformForm.myForm.value?.meetingId + } } + }; + } + this.sessionService.createSession(this.meetingInfo, this.sessionId()).then((res) => { + if (res) { + this.sessionService.getReqSessionDetails(this.params().id).then((requestRes) => { + this.apiResponse.set(requestRes.result); + if (this.apiResponse()?.status === 'ACCEPTED') { + this.sessionService.getSessionDetailsAPI(this.apiResponse().session_id).then((sessionRes) => { + this.sessionDetails.set(sessionRes.result); + this.isMeetingLinkAdded.set(true); + }); + } + }); + } + }); + this.editSessionBtn.set(true); + } - }} + editLink(isOpen: boolean, id: any) { + if (!this.sessionDetails()?.meeting_info) { + return; } - this.sessionService.createSession(this.meetingInfo,this.sessionId).then((res) => { - if (res) { - this.sessionService.getReqSessionDetails(this.params.id).then((res) => { - this.apiResponse = res.result; - }); - if (this.apiResponse?.status === 'ACCEPTED') { - this.sessionService.getSessionDetailsAPI(this.apiResponse.session_id).then((res) => { - this.sessionDetails = res.result; - this.isMeetingLinkAdded = true; - }) - } - }}); - this.editSessionBtn = true; - } - - editLink(isOpen: boolean, id:any) { - this.isModalOpen = isOpen; - this.sessionId = id; - for(let j=0;j link?.name == 'link') - let meetingId = this?.meetingPlatforms[j]?.form?.controls.find( (meetingId:any) => meetingId?.name == 'meetingId') - let password = this?.meetingPlatforms[j]?.form?.controls.find( (password:any) => password?.name == 'password') - if(obj && this.sessionDetails?.meeting_info?.link){ - obj.value = this.sessionDetails?.meeting_info?.link; + this.isModalOpen.set(isOpen); + this.sessionId.set(id); + for (let j = 0; j < this?.meetingPlatforms?.length; j++) { + if (this.sessionDetails().meeting_info.platform === this?.meetingPlatforms[j].name) { + this.selectedLink = this?.meetingPlatforms[j]; + this.selectedHint = this.meetingPlatforms[j].hint; + const obj = this?.meetingPlatforms[j]?.form?.controls.find((link: any) => link?.name === 'link'); + const meetingId = this?.meetingPlatforms[j]?.form?.controls.find((meet: any) => meet?.name === 'meetingId'); + const password = this?.meetingPlatforms[j]?.form?.controls.find((pass: any) => pass?.name === 'password'); + if (obj && this.sessionDetails()?.meeting_info?.link) { + obj.value = this.sessionDetails()?.meeting_info?.link; } - if(this.sessionDetails?.meeting_info?.meta?.meetingId){ - meetingId.value = this.sessionDetails?.meeting_info?.meta?.meetingId; - password.value = this.sessionDetails?.meeting_info?.meta?.password; + if (this.sessionDetails()?.meeting_info?.meta?.meetingId) { + meetingId.value = this.sessionDetails()?.meeting_info?.meta?.meetingId; + password.value = this.sessionDetails()?.meeting_info?.meta?.password; } } } } - addLater(){ + addLater() { this.modal.dismiss(); - this.isModalOpen = false; + this.isModalOpen.set(false); } - viewProfile(id: any){ + viewProfile(id: any) { this.router.navigate([CommonRoutes.MENTOR_DETAILS, id]); } async onStart(data) { - let result = await this.sessionService.startSession(data); - result?this.router.navigate([`/${CommonRoutes.TABS}/${CommonRoutes.HOME}`]):null; + const result = await this.sessionService.startSession(data); + if (result) { + this.router.navigate([`/${CommonRoutes.TABS}/${CommonRoutes.HOME}`]); + } } formatUnixTime(unixTimestamp: number): string { diff --git a/src/app/pages/tabs/requests/requests.page.html b/src/app/pages/tabs/requests/requests.page.html index e63612d5..77a3227e 100644 --- a/src/app/pages/tabs/requests/requests.page.html +++ b/src/app/pages/tabs/requests/requests.page.html @@ -1,9 +1,15 @@ + @let currentSegment = segmentType(); + @let cardConfig = mentorForm(); + @let messageRequests = data(); + @let sessionRequests = slotRequests(); + @let hasNoResults = currentSegment === 'slot-requests' ? !sessionRequests.length : !messageRequests.length; +
- + {{ "SLOT_REQUESTS" | translate }} @@ -13,49 +19,56 @@
-
-
- - - - - - + + @switch (currentSegment) { + @case ('slot-requests') { + + + @for (value of sessionRequests; track value.id) { + + + + + } - -
- -
- - - - - - + } + @case ('message-requests') { + + + @for (value of messageRequests; track value.id) { + + + + + } -
-
- - - - -
- -
-
\ No newline at end of file + } + } + + + + + + + @if (hasNoResults && isDataAvailable()) { + + } + diff --git a/src/app/pages/tabs/requests/requests.page.spec.ts b/src/app/pages/tabs/requests/requests.page.spec.ts index d66b7a84..de9e3388 100644 --- a/src/app/pages/tabs/requests/requests.page.spec.ts +++ b/src/app/pages/tabs/requests/requests.page.spec.ts @@ -72,11 +72,11 @@ describe('RequestsPage', () => { await component.ionViewWillEnter(); expect(formServiceSpy.getForm).toHaveBeenCalled(); - expect(component.mentorForm).toEqual(_.get(fakeFormResult, 'data.fields.controls')); - expect(component.buttonConfig).toBeDefined(); - expect(component.slotBtnConfig).toBeDefined(); + expect(component.mentorForm()).toEqual(_.get(fakeFormResult, 'data.fields.controls')); + expect(component.buttonConfig()).toBeDefined(); + expect(component.slotBtnConfig()).toBeDefined(); expect(sessionServiceSpy.requestSessionList).toHaveBeenCalledWith(1); - expect(component.isLoading).toBeFalse(); + expect(component.isLoading()).toBeFalse(); })); it('segmentChanged should switch to message-requests and call pendingRequest', waitForAsync(async () => { @@ -85,7 +85,7 @@ describe('RequestsPage', () => { await component.segmentChanged(event); - expect(component.segmentType).toBe('message-requests'); + expect(component.segmentType()).toBe('message-requests'); expect(component.page).toBe(1); expect(component.pendingRequest).toHaveBeenCalled(); })); @@ -97,10 +97,10 @@ describe('RequestsPage', () => { const result = await component.pendingRequest(); expect(httpServiceSpy.get).toHaveBeenCalled(); - expect(component.data.length).toBe(0); - expect(component.noResult).toBe(component.routeData?.noDataFound?.noMessage); - // when response count is 0, component.data.length >= totalCount -> true - expect(component.isInfiniteScrollDisabled).toBeTrue(); + expect(component.data().length).toBe(0); + expect(component.noResult()).toBe(component.routeData()?.noDataFound?.noMessage); + // when response count is 0, component.data().length >= totalCount -> true + expect(component.isInfiniteScrollDisabled()).toBeTrue(); expect(result).toBe(resp); })); @@ -117,10 +117,10 @@ describe('RequestsPage', () => { await component.slotRequestData(); expect(sessionServiceSpy.requestSessionList).toHaveBeenCalledWith(component.page); - expect(component.slotRequests.length).toBe(2); + expect(component.slotRequests().length).toBe(2); - const expired = component.slotRequests.find((s: any) => s.id === 'expired'); - const active = component.slotRequests.find((s: any) => s.id === 'active'); + const expired = component.slotRequests().find((s: any) => s.id === 'expired'); + const active = component.slotRequests().find((s: any) => s.id === 'active'); expect(expired.showTag).toEqual(component.expiryTag); expect(expired.disableButton).toBeTrue(); @@ -145,7 +145,7 @@ describe('RequestsPage', () => { spyOn(component, 'slotRequestData').and.returnValue(Promise.resolve()); const event = { target: { complete: jasmine.createSpy('complete') } } as any; - component.segmentType = 'slot-requests'; + component.segmentType.set('slot-requests'); component.page = 1; await component.loadMore(event); @@ -160,7 +160,7 @@ describe('RequestsPage', () => { const resp = await component.pendingRequest(); - expect(component.isInfiniteScrollDisabled).toBeTrue(); + expect(component.isInfiniteScrollDisabled()).toBeTrue(); expect(resp).toBe('err'); })); diff --git a/src/app/pages/tabs/requests/requests.page.ts b/src/app/pages/tabs/requests/requests.page.ts index ae50dfee..a8d4c01d 100644 --- a/src/app/pages/tabs/requests/requests.page.ts +++ b/src/app/pages/tabs/requests/requests.page.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, signal } from '@angular/core'; import { urlConstants } from 'src/app/core/constants/urlConstants'; import { ActivatedRoute } from '@angular/router'; import { Router } from '@angular/router'; @@ -23,23 +23,25 @@ export class RequestsPage implements OnInit { headerColor: 'primary', notification: false, }; - segmentType = 'slot-requests'; - buttonConfig: any; - data: any[] = []; - noResult: any; - routeData: any; - slotBtnConfig: any; - slotRequests: any[] = []; - mentorForm:any; + + segmentType = signal<'slot-requests' | 'message-requests'>('slot-requests'); + buttonConfig = signal(null); + data = signal([]); + noResult = signal(''); + routeData = signal(null); + slotBtnConfig = signal(null); + slotRequests = signal([]); + mentorForm = signal(null); + isInfiniteScrollDisabled = signal(false); + isLoading = signal(false); + isDataAvailable = signal(false); + expiryTag = { label: 'EXPIRED', cssClass: 'expired-tag' }; page = 1; - isInfiniteScrollDisabled = false; - isLoading: boolean = false; - isDataAvailable: boolean; - + constructor( private httpService: HttpService, private route: ActivatedRoute, @@ -48,44 +50,46 @@ export class RequestsPage implements OnInit { private form: FormService, ) {} - async ionViewWillEnter(){ - if(this.isLoading) - return; - this.isDataAvailable = false; - this.isLoading = true; + async ionViewWillEnter() { + if (this.isLoading()) return; + + this.isDataAvailable.set(false); + this.isLoading.set(true); + const result = await this.form.getForm(MENTOR_REQ_CARD_FORM); - this.mentorForm = _.get(result, 'data.fields.controls'); + this.mentorForm.set(_.get(result, 'data.fields.controls')); + this.route.data.subscribe((data) => { - this.routeData = data; - this.buttonConfig = this.routeData?.button_config; - this.slotBtnConfig = this.routeData.slotButtonConfig; + this.routeData.set(data); + this.buttonConfig.set(data?.button_config); + this.slotBtnConfig.set(data?.slotButtonConfig); }); - + this.page = 1; - this.slotRequests = []; - this.data = []; - this.isInfiniteScrollDisabled = false; - - if (this.segmentType === 'slot-requests') { + this.slotRequests.set([]); + this.data.set([]); + this.isInfiniteScrollDisabled.set(false); + + if (this.segmentType() === 'slot-requests') { await this.slotRequestData(); } else { await this.pendingRequest(); } - this.isLoading = false; - } - - ngOnInit() { + this.isLoading.set(false); } + ngOnInit() {} + async segmentChanged(event: any) { - this.segmentType = event.target.value; + this.segmentType.set(event.target.value); this.page = 1; - this.isInfiniteScrollDisabled = false; - this.noResult = ''; - this.slotRequests = []; - this.data = []; - this.isDataAvailable = false; - if (this.segmentType === 'slot-requests') { + this.isInfiniteScrollDisabled.set(false); + this.noResult.set(''); + this.slotRequests.set([]); + this.data.set([]); + this.isDataAvailable.set(false); + + if (this.segmentType() === 'slot-requests') { await this.slotRequestData(); } else { await this.pendingRequest(); @@ -95,34 +99,34 @@ export class RequestsPage implements OnInit { async pendingRequest(isLoadMore: boolean = false) { const config = { url: urlConstants.API_URLS.CONNECTION_REQUEST + - '?pageNo=' + this.page + - '&pageSize=100', + '?pageNo=' + this.page + + '&pageSize=100', }; - + try { let response: any = await this.httpService.get(config); - this.isDataAvailable = true; - let newData = response?.result?.data || []; - + this.isDataAvailable.set(true); + const newData = response?.result?.data || []; + if (isLoadMore) { - this.data = [...this.data, ...newData]; + this.data.update(prev => [...prev, ...newData]); } else { - this.data = newData; + this.data.set(newData); } - + const totalCount = response?.result?.count || 0; - this.isInfiniteScrollDisabled = this.data.length >= totalCount; - - if (this.data.length === 0 && this.page === 1) { - this.noResult = this.routeData?.noDataFound?.noMessage; + this.isInfiniteScrollDisabled.set(this.data().length >= totalCount); + + if (this.data().length === 0 && this.page === 1) { + this.noResult.set(this.routeData()?.noDataFound?.noMessage); } else { - this.noResult = ''; + this.noResult.set(''); } - + return response; } catch (error) { console.error('Error fetching pending requests:', error); - this.isInfiniteScrollDisabled = true; + this.isInfiniteScrollDisabled.set(true); return error; } } @@ -130,20 +134,20 @@ export class RequestsPage implements OnInit { async slotRequestData(isLoadMore: boolean = false) { try { const res = await this.sessionService.requestSessionList(this.page); - this.isDataAvailable = true; - let data = []; - + this.isDataAvailable.set(true); + + let data: any[]; if (isLoadMore) { - data = [...this.slotRequests, ...(res?.result?.data || [])]; + data = [...this.slotRequests(), ...(res?.result?.data || [])]; } else { data = res?.result?.data || []; } - + const totalCount = res?.result?.count || 0; - this.isInfiniteScrollDisabled = data.length >= totalCount; - + this.isInfiniteScrollDisabled.set(data.length >= totalCount); + if (data.length === 0 && this.page === 1) { - this.noResult = this.routeData?.noDataFound?.noSession; + this.noResult.set(this.routeData()?.noDataFound?.noSession); return; } @@ -154,11 +158,11 @@ export class RequestsPage implements OnInit { disableButton: this.isSessionExpired(value) })); - this.slotRequests = formattedData; + this.slotRequests.set(formattedData); } catch (error) { console.error('Error fetching session list:', error); - this.isInfiniteScrollDisabled = true; + this.isInfiniteScrollDisabled.set(true); } } @@ -171,32 +175,40 @@ export class RequestsPage implements OnInit { }; } - isSessionExpired(meta): boolean { + getMessageRequestMeta(value: any) { + return { + isSent: value?.created_by === value?.user_id, + message: value?.meta?.message, + timeStamp: '' + }; + } + + isSessionExpired(meta: any): boolean { const endDate = meta?.end_date; - if (!endDate) return false; + if (!endDate) return false; return Date.now() > endDate * 1000; } - - onCardClick(event, data?) { + + onCardClick(event: any, data?: any) { switch (event.type) { case 'viewMessage': this.router.navigate([CommonRoutes.CHAT_REQ, event.data]); break; case 'viewDetails': - this.router.navigate([CommonRoutes.SESSION_REQUEST_DETAILS], {queryParams: {id: data}}); + this.router.navigate([CommonRoutes.SESSION_REQUEST_DETAILS], { queryParams: { id: data } }); break; } } async loadMore($event: any) { this.page = this.page + 1; - - if (this.segmentType === 'slot-requests') { + + if (this.segmentType() === 'slot-requests') { await this.slotRequestData(true); } else { await this.pendingRequest(true); } - + $event.target.complete(); } -} \ No newline at end of file +} diff --git a/src/app/shared/components/generic-profile-header/generic-profile-header.component.ts b/src/app/shared/components/generic-profile-header/generic-profile-header.component.ts index 6dbe08fb..4fc2caa8 100644 --- a/src/app/shared/components/generic-profile-header/generic-profile-header.component.ts +++ b/src/app/shared/components/generic-profile-header/generic-profile-header.component.ts @@ -61,13 +61,15 @@ export class GenericProfileHeaderComponent implements OnInit { } async action(event) { + const header = this.headerData(); + const meta = this.buttonConfig()?.meta; switch (event) { case 'edit': this.router.navigate([`/${CommonRoutes.EDIT_PROFILE}`], {replaceUrl:true}); break; case 'role': - if (this.headerData()?.about != null || environment['isAuthBypassed']) { + if (header?.about != null || environment['isAuthBypassed']) { this.router.navigate([`/${CommonRoutes.MENTOR_QUESTIONNAIRE}`]); } else { this.profileService.upDateProfilePopup(); @@ -75,11 +77,11 @@ export class GenericProfileHeaderComponent implements OnInit { break; case 'share': - if (this.isMobile && navigator.share && this.buttonConfig()?.meta) { + if (this.isMobile && navigator.share && meta) { this.translateText(); - let url = `/mentoring/${CommonRoutes.MENTOR_DETAILS}/${this.buttonConfig().meta.id}`; + let url = `/mentoring/${CommonRoutes.MENTOR_DETAILS}/${meta.id}`; let link = await this.utilService.getDeepLink(url); - const name = (this.headerData()?.name || '').trim(); + const name = (header?.name || '').trim(); let params = { link: link, subject: name, @@ -92,17 +94,17 @@ export class GenericProfileHeaderComponent implements OnInit { } break; case 'requestSession': - this.router.navigate([`/${CommonRoutes.SESSION_REQUEST}`], {queryParams: {data: this.headerData().id}}); + this.router.navigate([`/${CommonRoutes.SESSION_REQUEST}`], {queryParams: {data: header?.id}}); break; case 'chat': - this.headerData().is_connected + header?.is_connected ? this.router.navigate([ `/${CommonRoutes.CHAT}`, - this.headerData().connection_details?.room_id, - ],{queryParams: {id: this.headerData().id}}) + header?.connection_details?.room_id, + ],{queryParams: {id: header?.id}}) : this.router.navigate([ `/${CommonRoutes.CHAT_REQ}`, - this.headerData().id, + header?.id, ]); } } @@ -130,7 +132,8 @@ export class GenericProfileHeaderComponent implements OnInit { }; async viewRoles(){ - const titlesArray = this.headerData().organizations[0].roles.map(item => item.title); + const roles = this.headerData()?.organizations?.[0]?.roles || []; + const titlesArray = roles.map(item => item.title); this.profileService.viewRolesModal(titlesArray); } } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index e8ea3df7..50d5c71f 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -412,6 +412,8 @@ "CLUSTER":"Cluster", "DISTRICT":"District", "BLOCK":"Block", + "YOU_HAVE_BLOCKED":"You have blocked {{name}}", + "DO_YOU_WISH_TO_UNBLOCK":"Do you wish to unblock?", "SCHOOL":"School", "LOADING": "Loading more data...", "YOU_HAVE" :"You have requested", @@ -430,5 +432,6 @@ "DESELECT_ALL" : "Deselect all", "SELECTED" : "Selected", "SELECT_PAGE" : "Select page", - "DESELECT_PAGE" : "Deselect page" + "DESELECT_PAGE" : "Deselect page", + "TYPE_MESSAGE_HERE": "Type a message ..." } diff --git a/src/assets/i18n/hi.json b/src/assets/i18n/hi.json index 23467711..d30aeb7e 100644 --- a/src/assets/i18n/hi.json +++ b/src/assets/i18n/hi.json @@ -388,4 +388,8 @@ "SELECTED": "चयनित", "SELECT_PAGE": "पेज चुनें", "DESELECT_PAGE": "पेज चयन हटाएँ" + , + "YOU_HAVE_BLOCKED": "आपने {{name}} को ब्लॉक किया है", + "DO_YOU_WISH_TO_UNBLOCK": "क्या आप अनब्लॉक करना चाहते हैं?", + "TYPE_MESSAGE_HERE": "यहाँ संदेश टाइप करें..." }