diff --git a/src/app/chat/chat-feature.cy.ts b/src/app/chat/chat-feature.cy.ts index 61e7d9ce..513b4bad 100644 --- a/src/app/chat/chat-feature.cy.ts +++ b/src/app/chat/chat-feature.cy.ts @@ -6,7 +6,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MountConfig } from 'cypress/angular'; import { CommonModule } from '@angular/common'; import { chats, profilePictures } from '@cypress-support/mock-backend'; -import { ChatPageComponent } from './feature/chat-page/chat-page.component'; +import { ChatComponent } from './chat.component'; // const timerMock = new TimerMockService(); @@ -20,7 +20,7 @@ import { ChatPageComponent } from './feature/chat-page/chat-page.component'; // ], // }; -const defaultMountConfig: MountConfig = { +const defaultMountConfig: MountConfig = { imports: [ CommonModule, BrowserAnimationsModule, @@ -28,11 +28,11 @@ const defaultMountConfig: MountConfig = { RouterTestingModule.withRoutes([ { path: 'chat', - component: ChatPageComponent, + component: ChatComponent, }, { path: 'chat/:participantId', - component: ChatPageComponent, + component: ChatComponent, }, ]), ], @@ -265,7 +265,7 @@ describe('The chat page', () => { ]) ); - cy.mount(ChatPageComponent, defaultMountConfig); + cy.mount(ChatComponent, defaultMountConfig); cy.contains('Adam Ant').click(); cy.contains('Hello, how are you?').should('be.visible'); cy.contains('I am fine, thanks.').should('be.visible'); diff --git a/src/app/chat/feature/chat-page/chat-page.component.scss b/src/app/chat/chat.component.scss similarity index 100% rename from src/app/chat/feature/chat-page/chat-page.component.scss rename to src/app/chat/chat.component.scss diff --git a/src/app/chat/feature/chat-page/chat-page.component.ts b/src/app/chat/chat.component.ts similarity index 68% rename from src/app/chat/feature/chat-page/chat-page.component.ts rename to src/app/chat/chat.component.ts index bcae4847..12a233a8 100644 --- a/src/app/chat/feature/chat-page/chat-page.component.ts +++ b/src/app/chat/chat.component.ts @@ -1,13 +1,18 @@ -import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + inject, + Signal, +} from '@angular/core'; import { RouterLink } from '@angular/router'; import { AlertComponent } from '@shared/ui/alert/alert.component'; -import { ChatMessageComposerComponent } from '../../ui/message-composer/chat-message-composer.component'; -import { ChatMessagesComponent } from '../../ui/chat-messages/chat-messages.component'; -import { ChatConversationListComponent } from '../../ui/conversation-list/chat-conversation-list.component'; +import { ChatMessageComposerComponent } from './ui/message-composer/chat-message-composer.component'; +import { ChatMessagesComponent } from './ui/chat-messages/chat-messages.component'; +import { ChatConversationListComponent } from './ui/conversation-list/chat-conversation-list.component'; import { AsyncPipe, NgFor, NgIf } from '@angular/common'; -import { ChatStateService } from '../../data-access/chat-state.service'; -import { ChatConversationHeaderComponent } from '../../ui/chat-conversation-header.component'; +import { ChatStateService } from './data-access/chat-state.service'; +import { ChatConversationHeaderComponent } from './ui/chat-conversation-header.component'; import { interval, take } from 'rxjs'; @Component({ @@ -22,20 +27,27 @@ import { interval, take } from 'rxjs'; >
- - - - + + + + + +
+

+ Wähle einen Chat aus der Liste aus. +

+
+
@@ -61,9 +73,9 @@ import { interval, take } from 'rxjs'; -

+ Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut. -

+
@@ -89,7 +101,7 @@ import { interval, take } from 'rxjs'; `, - styleUrls: ['./chat-page.component.scss'], + styleUrls: ['./chat.component.scss'], providers: [ChatStateService], changeDetection: ChangeDetectionStrategy.Default, standalone: true, @@ -105,7 +117,8 @@ import { interval, take } from 'rxjs'; ChatConversationHeaderComponent, ], }) -export class ChatPageComponent { +export class ChatComponent { chatState = inject(ChatStateService); delayLoading$ = interval(100).pipe(take(1)); + hasActiveChat: Signal = this.chatState.hasActiveChat; } diff --git a/src/app/chat/chat.routes.ts b/src/app/chat/chat.routes.ts index 746946b5..a17194d4 100644 --- a/src/app/chat/chat.routes.ts +++ b/src/app/chat/chat.routes.ts @@ -1,13 +1,13 @@ import { Routes } from '@angular/router'; -import { ChatPageComponent } from './feature/chat-page/chat-page.component'; +import { ChatComponent } from './chat.component'; export const CHAT_ROUTES: Routes = [ { path: ':participantId', - component: ChatPageComponent, + component: ChatComponent, }, { path: '', - component: ChatPageComponent, + component: ChatComponent, }, ]; diff --git a/src/app/chat/data-access/chat-http.service.ts b/src/app/chat/data-access/chat-http.service.ts index d7e8c1c7..a35a0eec 100644 --- a/src/app/chat/data-access/chat-http.service.ts +++ b/src/app/chat/data-access/chat-http.service.ts @@ -11,7 +11,6 @@ import { ChatDto, ChatList, ChatsAndDancers, - CreateChatResponse, CreateMessageRequest, DancerId, DancerMapDto, @@ -169,16 +168,15 @@ export class ChatHttpService { return Array.from(dancerIds.keys()); } - createChat$(participantId: string): Observable { + /** returns the chat id */ + createChat$(participantId: string): Observable { const body = { participantIds: [this.profileService.getProfile()?.id, participantId], }; - return this.http.post( - `${this.chatApiUrl}`, - body, - this.defaultOptions - ); + return this.http + .post<{ id: string }>(`${this.chatApiUrl}`, body) + .pipe(map((res) => res.id)); } sendMessage$(chatId: string, message: string): Observable { diff --git a/src/app/chat/data-access/chat-state.adapter.ts b/src/app/chat/data-access/chat-state.adapter.ts index 16f9896a..08c0f8ff 100644 --- a/src/app/chat/data-access/chat-state.adapter.ts +++ b/src/app/chat/data-access/chat-state.adapter.ts @@ -1,16 +1,12 @@ import { ChatAdaptState } from './chat-state.service'; import { createAdapter } from '@state-adapt/core'; -import { - ChatDto, - CreateChatResponse, - DancerMapDto, - MessagesWithChatId, -} from './chat.types'; +import { ChatDto, DancerMapDto, MessagesWithChatId } from './chat.types'; import { HttpErrorResponse } from '@angular/common/http'; export const chatStateAdapter = createAdapter()({ chatsFetched: (state, chatsDto: ChatDto[]) => { const newChats = chatsDto + .reverse() // latest created chat first .filter( (chatDto) => !state.chats.find((stateChat) => stateChat.id === chatDto.chatId) @@ -87,10 +83,10 @@ export const chatStateAdapter = createAdapter()({ openChatWithParticipantId: null, }), - chatCreated: (state, chat: CreateChatResponse) => ({ + chatCreated: (state, chatId: string) => ({ // select new chat ...state, - activeChatId: chat.chatId, + activeChatId: chatId, chatCreated: true, }), @@ -120,6 +116,7 @@ export const chatStateAdapter = createAdapter()({ .flat() .filter((participant) => participant.dancerName === undefined), activeChatId: (state) => state.activeChatId, + hasActiveChat: (state) => state.activeChatId !== null, messagesForActiveChat: (state) => state.chats.find((chat) => chat.id === state.activeChatId)?.messages ?? [], diff --git a/src/app/chat/data-access/chat-state.service.ts b/src/app/chat/data-access/chat-state.service.ts index 91d4ced4..587b9ca7 100644 --- a/src/app/chat/data-access/chat-state.service.ts +++ b/src/app/chat/data-access/chat-state.service.ts @@ -187,6 +187,9 @@ export class ChatStateService { activeChatParticipants = toSignal(this.chatStore.activeChatParticipants$, { requireSync: true, }); + hasActiveChat = toSignal(this.chatStore.hasActiveChat$, { + requireSync: true, + }); constructor() {} } diff --git a/src/app/chat/data-access/chat.types.ts b/src/app/chat/data-access/chat.types.ts index a5856e74..eb6f57df 100644 --- a/src/app/chat/data-access/chat.types.ts +++ b/src/app/chat/data-access/chat.types.ts @@ -1,10 +1,3 @@ -import { Profile } from '../../profile/data-access/types/profile.types'; - -export type Conversation = { - chatId: string; - participants: ChatParticipant[]; -}; - export type ChatDto = { chatId: string; participantIds: DancerId[]; @@ -21,8 +14,6 @@ export type ChatMessage = { createdAt: string; }; -export type ChatType = 'GROUP' | 'DIRECT'; - export type ChatList = { chats: ChatDto[]; }; @@ -64,12 +55,6 @@ export type CreateMessageRequest = { text: string; }; -export type ChatData = { - chats: ChatDto[]; - dancers: DancerMapDto; - profile: Profile; -}; - export type CreateChatResponse = { chatId: string; dancerIds: string[]; diff --git a/src/app/chat/feature/chat-page/chat-page-demo.component.ts b/src/app/chat/feature/chat-page/chat-page-demo.component.ts deleted file mode 100644 index 239ce286..00000000 --- a/src/app/chat/feature/chat-page/chat-page-demo.component.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - NgZone, -} from '@angular/core'; -import { ChatDto } from '../../data-access/chat.types'; -import { ChatServiceDemoService } from './chat-service-demo.service'; - -@Component({ - selector: 'app-chat-page-demo', - standalone: true, - imports: [], - providers: [], - template: ` -

chat-page-demo works!

-

1

- - - - -

Value from Demo: {{ demoService2.chats().length }}

- - `, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ChatPageDemoComponent { - chats: ChatDto[] = []; - // demoService = inject(ChatHttpService); - demoService2 = inject(ChatServiceDemoService); - // service = inject(ChatStateSignalsService); - - // demoValue = this.demoService.getChats$().subscribe((value) => { - // console.log('chats', value); - // this.chats = value; - // }); - - // actualService = inject(ChatStateSignalsService); - - constructor(private zone: NgZone) { - // @ts-ignore - // window['demoService'] = this.demoService2; - // @ts-ignore - } - - handleClick(_e: Event): void { - this.demoService2.valueSubject.next(2); - } -} diff --git a/src/app/chat/feature/chat-page/chat-service-demo.service.ts b/src/app/chat/feature/chat-page/chat-service-demo.service.ts deleted file mode 100644 index 8855a4b4..00000000 --- a/src/app/chat/feature/chat-page/chat-service-demo.service.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { computed, inject, Injectable, signal } from '@angular/core'; -import { ChatHttpService } from '../../data-access/chat-http.service'; -import { catchError, NEVER, shareReplay, Subject, switchMap } from 'rxjs'; -import { HttpErrorResponse } from '@angular/common/http'; -import { ChatDto } from '../../data-access/chat.types'; -import { TimerService } from '@shared/util/time/timer.service'; - -export type DemoChatState = { - chats: ChatDto[]; -}; - -@Injectable({ - providedIn: 'root', -}) -export class ChatServiceDemoService { - private apiService = inject(ChatHttpService); - // @ts-ignore - chatFetchTimer$ = inject(TimerService).interval('fetch-chats', 1000); - - valueSubject = new Subject(); - // chatFetchTimer$ = this.valueSubject.asObservable(); - - // chatFetchTimer$ = of(0); - - demoValue = 'Demo Welt'; - - // chats: ChatDto[] = []; - - chatState = signal({ - chats: [], - }); - - // selectors - chats = computed(() => this.chatState().chats); - - fetchedConversations$ = this.chatFetchTimer$.pipe( - // startWith(-1), - switchMap(() => this.apiService.getChats$()), - catchError((_err: HttpErrorResponse) => { - return NEVER; - }), - shareReplay(1) - ); - - constructor() { - this.fetchedConversations$.subscribe((value) => { - // this.chats = value; - this.chatState.update(() => ({ - chats: value, - })); - }); - } -} diff --git a/src/app/chat/ui/chat-conversation-header.component.ts b/src/app/chat/ui/chat-conversation-header.component.ts index 15289d83..d5d8942e 100644 --- a/src/app/chat/ui/chat-conversation-header.component.ts +++ b/src/app/chat/ui/chat-conversation-header.component.ts @@ -7,34 +7,61 @@ import { } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ChatStateService } from '../data-access/chat-state.service'; -import { ChatParticipant } from '../data-access/chat.types'; +import { ChatParticipant, DancerId } from '../data-access/chat.types'; import { toSignal } from '@angular/core/rxjs-interop'; import { OwnProfileService } from '@shared/data-access/profile/own-profile.service'; import { startWith } from 'rxjs/operators'; import { Profile } from '../../profile/data-access/types/profile.types'; import { map } from 'rxjs'; +import { ImageService } from '@shared/data-access/image.service'; +import { Router } from '@angular/router'; @Component({ selector: 'app-chat-conversation-header', standalone: true, imports: [CommonModule], template: ` -
+
-

- Chat mit {{ participant()?.dancerName }} -

+
+
+
+ +
+
+
{{ participant()?.dancerName }}
+
Zum Profil wechseln
+
+
+
`, changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChatConversationHeaderComponent { private chatState = inject(ChatStateService); + public imageService = inject(ImageService); + private router = inject(Router); ownProfileId: Signal = toSignal( inject(OwnProfileService).profile$.pipe( @@ -57,4 +84,13 @@ export class ChatConversationHeaderComponent { returnToConversationList(): void { this.chatState.selectChat$.next(null); } + + handleMissingImage($event: ErrorEvent): void { + ($event.target as HTMLImageElement).src = + this.imageService.getDefaultDancerImage(); + } + + navigateToProfile(id: DancerId): void { + this.router.navigate(['profile', 'view', id]); + } } diff --git a/src/app/chat/ui/chat-messages/chat-messages.component.ts b/src/app/chat/ui/chat-messages/chat-messages.component.ts index aa4190d2..43e20494 100644 --- a/src/app/chat/ui/chat-messages/chat-messages.component.ts +++ b/src/app/chat/ui/chat-messages/chat-messages.component.ts @@ -17,11 +17,11 @@ import { map } from 'rxjs'; @Component({ selector: 'app-chat-messages', template: ` +
-

Noch gibt es hier nichts zu sehen

Schreibe jetzt deine erste Nachricht

+ `, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, @@ -67,18 +68,4 @@ export class ChatMessagesComponent { activeChatMessages: Signal = this.chatState.messagesForActiveChat; - - // TODO: logic to differentiate between own messages and partner messages - // ownUserId = this.chatStore.ownProfileId$; - // - // messages = this.chatStore.selectedConversationMessages$; - // - // messagesIterative: ChatMessage[] = []; - // - // constructor(public chatStore: ChatStore) { - // this.messages.subscribe((messages) => { - // console.log('new messages arrived', messages); - // this.messagesIterative = messages; - // }); - // } } diff --git a/src/app/chat/ui/conversation-list/chat-conversation-list-entry.component.ts b/src/app/chat/ui/conversation-list/chat-conversation-list-entry.component.ts index d07a9626..4e38803b 100644 --- a/src/app/chat/ui/conversation-list/chat-conversation-list-entry.component.ts +++ b/src/app/chat/ui/conversation-list/chat-conversation-list-entry.component.ts @@ -34,7 +34,9 @@ import { Profile } from '../../../profile/data-access/types/profile.types'; }" (click)="chatState.selectChat$.next(conversation()!.id)" > -
+
-
-
{{ participant()!.dancerName }}
-
+
+
+ {{ participant()!.dancerName }} +
+
{{ participant()!.city }}
@@ -99,54 +103,4 @@ export class ChatConversationListEntryComponent { ($event.target as HTMLImageElement).src = this.imageService.getDefaultDancerImage(); } - - // constructor() { - // effect(() => { - // console.log(this.participant()); - // }); - // } - - // isSelected$?: Observable; - // - // @Input() - // conversation?: Conversation; - // - // participant?: ChatParticipant; - // - // ownProfileId?: string; - // - // constructor( - // public imageService: ImageService, - // public chatStore: ChatStore, - // public profileService: OwnProfileService, - // public router: Router - // ) { - // this.profileService.profile$.subscribe((profile) => { - // this.ownProfileId = profile.id; - // }); - // } - // - // ngOnInit(): void { - // if (!this.conversation) { - // return; - // } - // this.participant = this.conversation.participants.find( - // (participant) => participant.id !== this.ownProfileId - // ); - // this.isSelected$ = this.chatStore.selectedConversationId$.pipe( - // map((conversationId) => { - // return conversationId === this.conversation?.chatId; - // }) - // ); - // } - // - // selectConversation(): void { - // if (!this.conversation) { - // return; - // } - // this.chatStore.selectConversation(this.conversation.chatId); - // this.router.navigate(['/chat', this.participant?.id], { - // replaceUrl: true, - // }); - // } } diff --git a/src/app/profile/feature/public-profile.component.ts b/src/app/profile/feature/public-profile.component.ts index 1561a174..3f155051 100644 --- a/src/app/profile/feature/public-profile.component.ts +++ b/src/app/profile/feature/public-profile.component.ts @@ -134,10 +134,10 @@ import { AlertComponent } from '@shared/ui/alert/alert.component'; -

+ Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut. -

+
diff --git a/src/app/recommendation/recommendations.component.ts b/src/app/recommendation/recommendations.component.ts index 35b632f8..901d7a10 100644 --- a/src/app/recommendation/recommendations.component.ts +++ b/src/app/recommendation/recommendations.component.ts @@ -10,28 +10,61 @@ import { BehaviorSubject, filter, map, switchMap } from 'rxjs'; selector: 'app-recommendations', template: `
-

Diese Tänzer könnten für Sie interessant sein

- - + +

+ Diese Tänzer könnten für Sie interessant sein +

+ -
- -
+
+ +
+
+ +
+

Keine Empfehlungen verfügbar

+
+ +
+

+ Leider haben wir noch keine Tanzpartner, die wir dir hier empfehlen + können. +

+

+ Sobald wir neue Empfehlungen für dich haben, wirst du von uns + per Mail benachrichtigt. +

+

+ Neue Empfehlungen werden regelmäßig aktualisiert, z.B. wenn sich + neue Tanzpartner aus deiner Umgebung bei uns registrieren. +

+
+
+ +

+ Diese Tänzer könnten für Sie interessant sein +