diff --git a/src/app/chat/data-access/chat-http.service.ts b/src/app/chat/data-access/chat-http.service.ts index 2d193be3..9370bb35 100644 --- a/src/app/chat/data-access/chat-http.service.ts +++ b/src/app/chat/data-access/chat-http.service.ts @@ -3,9 +3,9 @@ import { inject, Injectable } from '@angular/core'; import { catchError, map, Observable, of, switchMap } from 'rxjs'; import { EnvironmentService } from '@shared/data-access/environment.service'; import { - APIResponse, asError, asSuccess, + OldAPIResponse, } from '@shared/util/http/response.types'; import { ChatDto, @@ -43,7 +43,7 @@ export class ChatHttpService { createMessage$( chatId: string, message: CreateMessageRequest - ): Observable> { + ): Observable> { return this.http .post( `${this.chatApiUrl}/${chatId}/messages`, @@ -107,7 +107,7 @@ export class ChatHttpService { ); } - getChatsAndDancers$(): Observable> { + getChatsAndDancers$(): Observable> { return this.http.get(this.chatApiUrl, this.defaultOptions).pipe( map((chatList) => { const dancerIds = this.getAllDancerIds(chatList.chats); diff --git a/src/app/home/data-access/contact.service.ts b/src/app/home/data-access/contact.service.ts index d0f5270c..0a8b0bb3 100644 --- a/src/app/home/data-access/contact.service.ts +++ b/src/app/home/data-access/contact.service.ts @@ -8,9 +8,9 @@ import { } from '@angular/common/http'; import { EnvironmentService } from '@shared/data-access/environment.service'; import { - APIResponse, asError, asSuccess, + OldAPIResponse, } from '@shared/util/http/response.types'; import { EventLogService } from '@shared/data-access/log/event-log.service'; @@ -28,7 +28,10 @@ export class ContactService { private eventLogService: EventLogService ) {} - sendMessage(message: string, sender: string): Observable> { + sendMessage( + message: string, + sender: string + ): Observable> { const apiUrlContact = `${this.environment.getApiUrl()}/contacts`; const payload = { sender, diff --git a/src/app/home/feature/contact/contact.component.ts b/src/app/home/feature/contact/contact.component.ts index 629410ec..2bc25e93 100644 --- a/src/app/home/feature/contact/contact.component.ts +++ b/src/app/home/feature/contact/contact.component.ts @@ -10,7 +10,7 @@ import { AuthenticationService } from '@shared/data-access/auth/authentication.s import { EventLogService } from '@shared/data-access/log/event-log.service'; import { ContactService } from '../../data-access/contact.service'; import { tap } from 'rxjs'; -import { APIResponse } from '@shared/util/http/response.types'; +import { OldAPIResponse } from '@shared/util/http/response.types'; import { ErrorMessagePipe } from '@shared/util/http/error-message.pipe'; import { MatButtonModule } from '@angular/material/button'; import { RecaptchaModule } from 'ng-recaptcha'; @@ -40,7 +40,7 @@ import { DataTestDirective } from '@shared/util/data-test.directive'; }) export class ContactComponent implements OnInit { contactForm!: FormGroup; - contactResponse?: APIResponse; + contactResponse?: OldAPIResponse; // TODO: refactor, we should solve this in a reactive way (e.g. subscribe in the template) isCaptchaSolved = false; diff --git a/src/app/profile/data-access/image-upload.service.ts b/src/app/profile/data-access/image-upload.service.ts index 12e61ea8..e0d6a537 100644 --- a/src/app/profile/data-access/image-upload.service.ts +++ b/src/app/profile/data-access/image-upload.service.ts @@ -3,9 +3,9 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { catchError, map, Observable, of } from 'rxjs'; import { EnvironmentService } from '@shared/data-access/environment.service'; import { - APIResponse, asError, asSuccess, + OldAPIResponse, } from '@shared/util/http/response.types'; import { UploadedImageDao } from './types/profile.types'; @@ -36,7 +36,7 @@ export class ImageUploadService { uploadImage$( croppedImage: string - ): Observable> { + ): Observable> { const blobFromDataUrl = this.dataURItoBlob(croppedImage); const formData: FormData = new FormData(); formData.append('file', blobFromDataUrl); diff --git a/src/app/profile/data-access/profile.service.ts b/src/app/profile/data-access/profile.service.ts index a37bd987..0c8d8e80 100644 --- a/src/app/profile/data-access/profile.service.ts +++ b/src/app/profile/data-access/profile.service.ts @@ -1,12 +1,42 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; +import { EnvironmentService } from '@shared/data-access/environment.service'; +import { HttpClient } from '@angular/common/http'; +import { PublicProfile } from './types/public-profile.types'; +import { Observable } from 'rxjs'; +import { toApiResponse } from '@shared/util/http/response.utils'; +import { ApiResponse } from '@shared/util/http/response.types'; +import { ImageService } from '@shared/data-access/image.service'; @Injectable({ providedIn: 'root', }) export class ProfileService { - constructor() {} + private readonly http = inject(HttpClient); + private readonly imageService = inject(ImageService); + private readonly profileApiUrl = `${inject( + EnvironmentService + ).getApiUrl()}/profile`; // public getOwnProfile(): void {} // - // public getProfile(dancerId: string): void {} + + public getPublicProfile( + dancerId: string + ): Observable> { + return toApiResponse( + this.http.get(`${this.profileApiUrl}/${dancerId}`) + ); + } + + getProfileImageSrc(imgHash: string | undefined, width = 150): string { + if (imgHash) { + return this.imageService.getDancerImageSrcOrDefault(imgHash, width); + } else { + return this.imageService.getDancerImageSrcOrDefault(null, width); + } + } + + getDefaultProfileImage(): string { + return this.imageService.getDefaultDancerImage(); + } } diff --git a/src/app/profile/data-access/types/public-profile.types.ts b/src/app/profile/data-access/types/public-profile.types.ts new file mode 100644 index 00000000..19d2f53c --- /dev/null +++ b/src/app/profile/data-access/types/public-profile.types.ts @@ -0,0 +1,15 @@ +import { Dance, Gender } from './profile.types'; + +export type PublicProfile = { + id: string; + size: number; + gender: Gender; + dancerName: string; + age: number; + ableTo: Dance[]; + wantsTo: Dance[]; + city: string; + country: string; + profileImageHash: string; + aboutMe: string; +}; diff --git a/src/app/profile/feature/edit-profile/edit-profile.component.ts b/src/app/profile/feature/edit-profile/edit-profile.component.ts index b014e646..8bfa6e90 100644 --- a/src/app/profile/feature/edit-profile/edit-profile.component.ts +++ b/src/app/profile/feature/edit-profile/edit-profile.component.ts @@ -4,7 +4,7 @@ import { NonNullableFormBuilder, ReactiveFormsModule, } from '@angular/forms'; -import { APIError, APIResponse } from '@shared/util/http/response.types'; +import { APIError, OldAPIResponse } from '@shared/util/http/response.types'; import { ProfileOldService } from '@shared/data-access/profile/profile-old.service'; import { Profile, @@ -56,7 +56,7 @@ export class EditProfileComponent { }); croppedImage?: string | null; imageChangedEvent: any = ''; - uploadResponse?: APIResponse; + uploadResponse?: OldAPIResponse; error?: APIError; constructor( diff --git a/src/app/profile/feature/initial-setup/init-profile-image/init-profile-image.component.ts b/src/app/profile/feature/initial-setup/init-profile-image/init-profile-image.component.ts index 2c72d6ec..77609e51 100644 --- a/src/app/profile/feature/initial-setup/init-profile-image/init-profile-image.component.ts +++ b/src/app/profile/feature/initial-setup/init-profile-image/init-profile-image.component.ts @@ -2,7 +2,7 @@ import { Component } from '@angular/core'; import { ImageUploadService } from '../../../data-access/image-upload.service'; import { ImageCroppedEvent, ImageCropperModule } from 'ngx-image-cropper'; import { ProfileOldService } from '@shared/data-access/profile/profile-old.service'; -import { APIResponse } from '@shared/util/http/response.types'; +import { OldAPIResponse } from '@shared/util/http/response.types'; import { UploadedImageDao } from '../../../data-access/types/profile.types'; import { Router } from '@angular/router'; import { NgIf } from '@angular/common'; @@ -18,7 +18,7 @@ import { MatButtonModule } from '@angular/material/button'; export class InitProfileImageComponent { croppedImage?: string | null | undefined; imageChangedEvent: any = ''; - uploadResponse?: APIResponse; + uploadResponse?: OldAPIResponse; constructor( private imageUploadService: ImageUploadService, diff --git a/src/app/profile/feature/initial-setup/init-user-name/init-user-name.component.ts b/src/app/profile/feature/initial-setup/init-user-name/init-user-name.component.ts index ad46b465..23cee9ac 100644 --- a/src/app/profile/feature/initial-setup/init-user-name/init-user-name.component.ts +++ b/src/app/profile/feature/initial-setup/init-user-name/init-user-name.component.ts @@ -9,8 +9,8 @@ import { ProfileHttpService } from '@shared/data-access/profile/profile-http.ser import { ProfileOldService } from '@shared/data-access/profile/profile-old.service'; import { APIError, - APIResponse, asError, + OldAPIResponse, ResponseError, } from '@shared/util/http/response.types'; import { of, switchMap } from 'rxjs'; @@ -60,7 +60,7 @@ export class InitUserNameComponent { this.profileHttpService .checkNameAvailability$(username) .pipe( - switchMap((response: APIResponse) => { + switchMap((response: OldAPIResponse) => { if (!response.isSuccess) { return of(response as ResponseError); } @@ -70,7 +70,7 @@ export class InitUserNameComponent { return this.profileService.setDancerName(username); }) ) - .subscribe((response: APIResponse) => { + .subscribe((response: OldAPIResponse) => { if (response.isSuccess) { this.router.navigate(['profile/initial-setup/personal-info']); } else { diff --git a/src/app/profile/feature/profile-new.component.ts b/src/app/profile/feature/own-profile.component.ts similarity index 98% rename from src/app/profile/feature/profile-new.component.ts rename to src/app/profile/feature/own-profile.component.ts index c8862f93..c7500d9b 100644 --- a/src/app/profile/feature/profile-new.component.ts +++ b/src/app/profile/feature/own-profile.component.ts @@ -10,7 +10,7 @@ import { ProfileDataEntryComponent } from '../ui/profile-data-entry.component'; import { ImageService } from '@shared/data-access/image.service'; @Component({ - selector: 'app-profile-new', + selector: 'app-own-profile', standalone: true, imports: [ CommonModule, @@ -106,7 +106,7 @@ import { ImageService } from '@shared/data-access/image.service'; `, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ProfileNewComponent { +export class OwnProfileComponent { imageService = inject(ImageService); profileService = inject(ProfileOldService); router = inject(Router); diff --git a/src/app/profile/feature/public-profile.component.ts b/src/app/profile/feature/public-profile.component.ts new file mode 100644 index 00000000..04f1c9c2 --- /dev/null +++ b/src/app/profile/feature/public-profile.component.ts @@ -0,0 +1,135 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ProfileService } from '../data-access/profile.service'; +import { AgePipe } from '@shared/util/age.pipe'; +import { DisplayDanceLevelPipe } from '../util/pipes/display-dance-level.pipe'; +import { DisplayDanceRolePipe } from '../util/pipes/display-dance-role.pipe'; +import { DisplayGenderPipe } from '../util/pipes/display-gender.pipe'; +import { ProfileDataEntryComponent } from '../ui/profile-data-entry.component'; + +@Component({ + selector: 'app-public-profile', + standalone: true, + imports: [ + CommonModule, + AgePipe, + DisplayDanceLevelPipe, + DisplayDanceRolePipe, + DisplayGenderPipe, + ProfileDataEntryComponent, + ], + template: ` + + +
+
+ + Profile Image +
+
+

+ Profil von + {{ profile.dancerName }} +

+ + + + + + + + + + + + + + +
+
+
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PublicProfileComponent { + private readonly activeRoute = inject(ActivatedRoute); + public readonly profileService = inject(ProfileService); + private readonly router = inject(Router); + + public readonly profileResponse$ = this.profileService.getPublicProfile( + this.activeRoute.snapshot.params['participantId'] + ); + + handleMissingImage($event: ErrorEvent): void { + ($event.target as HTMLImageElement).src = + this.profileService.getDefaultProfileImage(); + } + + openChat(dancerId: string): void { + this.router.navigate(['chat'], { + queryParams: { participantId: dancerId }, + }); + } +} diff --git a/src/app/profile/profile.routes.ts b/src/app/profile/profile.routes.ts index baa75212..ce623255 100644 --- a/src/app/profile/profile.routes.ts +++ b/src/app/profile/profile.routes.ts @@ -13,18 +13,20 @@ import { NarrowPageComponent } from '@shared/ui/layout/narrow-page/narrow-page.c import { EditProfileComponent } from './feature/edit-profile/edit-profile.component'; import { DancerProfileNotCreatedGuard } from './util/dancer-profile-not-created.guard'; import { loggedInGuard } from '@shared/util/auth/logged-in.guard'; -import { ProfileNewComponent } from './feature/profile-new.component'; +import { OwnProfileComponent } from './feature/own-profile.component'; +import { PublicProfileComponent } from './feature/public-profile.component'; export const PROFILE_ROUTES: Routes = [ { path: 'view/:participantId', + // add guard: only open if not the own profile... pathMatch: 'full', - component: ProfileNewComponent, + component: PublicProfileComponent, }, { path: '', pathMatch: 'full', - component: ProfileNewComponent, + component: OwnProfileComponent, canActivate: [ loggedInGuard, ...mapToCanActivate([DancerProfileSufficientGuard]), diff --git a/src/app/recommendation/data-access/recommendation-http.service.ts b/src/app/recommendation/data-access/recommendation-http.service.ts index 28dd01de..afa0809b 100644 --- a/src/app/recommendation/data-access/recommendation-http.service.ts +++ b/src/app/recommendation/data-access/recommendation-http.service.ts @@ -3,9 +3,9 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { EnvironmentService } from '@shared/data-access/environment.service'; import { RecommendationsDto } from './types/recommendations.dto'; import { - APIResponse, asError, asSuccess, + OldAPIResponse, } from '@shared/util/http/response.types'; import { catchError, map, Observable, of } from 'rxjs'; @@ -28,7 +28,7 @@ export class RecommendationHttpService { this.recommendationsApiUrl = `${this.environment.getApiUrl()}/recommendations`; } - getRecommendations$(): Observable> { + getRecommendations$(): Observable> { return this.http .get(this.recommendationsApiUrl, this.defaultOptions) .pipe( diff --git a/src/app/recommendation/data-access/recommendation.service.ts b/src/app/recommendation/data-access/recommendation.service.ts index 6b3664a7..35c1f947 100644 --- a/src/app/recommendation/data-access/recommendation.service.ts +++ b/src/app/recommendation/data-access/recommendation.service.ts @@ -3,7 +3,7 @@ import { RecommendationHttpService } from './recommendation-http.service'; import { RecommendedDancer } from './types/recommended-dancers.types'; import { map, Observable, shareReplay } from 'rxjs'; import { RecommendationsDto } from './types/recommendations.dto'; -import { APIResponse } from '@shared/util/http/response.types'; +import { OldAPIResponse } from '@shared/util/http/response.types'; @Injectable({ providedIn: 'root', @@ -11,7 +11,7 @@ import { APIResponse } from '@shared/util/http/response.types'; export class RecommendationService { constructor(private httpService: RecommendationHttpService) {} - getRecommendations$(): Observable> { + getRecommendations$(): Observable> { return this.httpService.getRecommendations$().pipe( map((response) => { if (response.isSuccess) { diff --git a/src/app/recommendation/recommendations.component.ts b/src/app/recommendation/recommendations.component.ts index f25d90f7..407c6617 100644 --- a/src/app/recommendation/recommendations.component.ts +++ b/src/app/recommendation/recommendations.component.ts @@ -1,8 +1,9 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { RecommendationService } from './data-access/recommendation.service'; import { AlertComponent } from '@shared/ui/alert/alert.component'; import { RecommendedDancerComponent } from './ui/recommended-dancer.component'; import { AsyncPipe, NgFor, NgIf } from '@angular/common'; +import { Router } from '@angular/router'; @Component({ selector: 'app-recommendations', @@ -18,6 +19,7 @@ import { AsyncPipe, NgFor, NgIf } from '@angular/common'; @@ -61,7 +63,13 @@ import { AsyncPipe, NgFor, NgIf } from '@angular/common'; imports: [NgIf, NgFor, RecommendedDancerComponent, AlertComponent, AsyncPipe], }) export class RecommendationsComponent { + recommendationsService = inject(RecommendationService); + router = inject(Router); recommendationsResponse$ = this.recommendationsService.getRecommendations$(); - constructor(public recommendationsService: RecommendationService) {} + constructor() {} + + openPublicProfile(dancerId: string): void { + this.router.navigate(['profile', 'view', dancerId]); + } } diff --git a/src/app/registration/ui/send-verification-link-form/send-verification-link-form.component.ts b/src/app/registration/ui/send-verification-link-form/send-verification-link-form.component.ts index 483e1a16..173727d5 100644 --- a/src/app/registration/ui/send-verification-link-form/send-verification-link-form.component.ts +++ b/src/app/registration/ui/send-verification-link-form/send-verification-link-form.component.ts @@ -6,7 +6,7 @@ import { } from '@angular/forms'; import { AuthenticationService } from '@shared/data-access/auth/authentication.service'; import { ActivatedRoute, Router } from '@angular/router'; -import { APIError, APIResponse } from '@shared/util/http/response.types'; +import { APIError, OldAPIResponse } from '@shared/util/http/response.types'; import { LinkType } from '../../util/registration.types'; import { Observable } from 'rxjs'; import { AuthStorageService } from '@shared/data-access/auth/auth-storage.service'; @@ -40,7 +40,7 @@ export class SendVerificationLinkFormComponent implements OnInit { submitButtonText?: string; redirectUrl?: string; onSubmit!: - | ((email: string) => Observable>) + | ((email: string) => Observable>) | ((email: string) => never); error?: APIError; verificationForm = this.fb.group({ diff --git a/src/app/shared/data-access/auth/authentication.service.ts b/src/app/shared/data-access/auth/authentication.service.ts index a1e71c06..58d7a923 100644 --- a/src/app/shared/data-access/auth/authentication.service.ts +++ b/src/app/shared/data-access/auth/authentication.service.ts @@ -15,9 +15,9 @@ import { catchError, map, Observable, of, shareReplay, tap } from 'rxjs'; import { AuthStorageService } from './auth-storage.service'; import { EnvironmentService } from '../environment.service'; import { - APIResponse, asError, asSuccess, + OldAPIResponse, } from '../../util/http/response.types'; @Injectable({ @@ -38,7 +38,9 @@ export class AuthenticationService { this.baseUrl = `${this.environment.getApiUrl()}/authentication`; } - register(userRegistration: UserRegistration): Observable> { + register( + userRegistration: UserRegistration + ): Observable> { return this.http .post( `${this.baseUrl}/registrations`, @@ -59,7 +61,7 @@ export class AuthenticationService { ); } - login(loginRequest: LoginRequest): Observable> { + login(loginRequest: LoginRequest): Observable> { return this.http .post( `${this.baseUrl}/login`, @@ -88,7 +90,7 @@ export class AuthenticationService { ); } - loginAsHuman(captchaToken: string): Observable> { + loginAsHuman(captchaToken: string): Observable> { const httpOptions = { headers: new HttpHeaders({ 'X-Captcha-Token': captchaToken, @@ -115,7 +117,7 @@ export class AuthenticationService { requestPasswordChange( passwordChangeRequest: PasswordChangeRequest - ): Observable> { + ): Observable> { return this.http .post( `${this.baseUrl}/password-changes`, @@ -137,7 +139,7 @@ export class AuthenticationService { requestEmailValidationCode( emailValidationCodeRequest: EmailValidationCodeRequest - ): Observable> { + ): Observable> { return this.http .post( `${this.baseUrl}/email-validations`, @@ -160,7 +162,7 @@ export class AuthenticationService { changePassword( validationCode: string, password: string - ): Observable> { + ): Observable> { return this.http .put( `${this.baseUrl}/password-changes/${validationCode}`, @@ -180,7 +182,7 @@ export class AuthenticationService { ); } - verifyAccount(validationCode: string): Observable> { + verifyAccount(validationCode: string): Observable> { return this.http .put( `${this.baseUrl}/email-validations/${validationCode}`, @@ -202,7 +204,7 @@ export class AuthenticationService { ); } - logout(): Observable> { + logout(): Observable> { return this.http .get(`${this.baseUrl}/logout`, this.defaultOptions) .pipe( diff --git a/src/app/shared/data-access/log/event-log-http.service.ts b/src/app/shared/data-access/log/event-log-http.service.ts index a56bbd55..3162948c 100644 --- a/src/app/shared/data-access/log/event-log-http.service.ts +++ b/src/app/shared/data-access/log/event-log-http.service.ts @@ -4,9 +4,9 @@ import { Event } from './eventlog.types'; import { catchError, map, Observable, of } from 'rxjs'; import { EnvironmentService } from '../environment.service'; import { - APIResponse, asError, asSuccess, + OldAPIResponse, } from '../../util/http/response.types'; @Injectable({ @@ -22,7 +22,7 @@ export class EventLogHttpService { private http: HttpClient ) {} - postEvent$(event: Event): Observable> { + postEvent$(event: Event): Observable> { return this.http .post( `${this.environment.getApiUrl()}/eventlog`, diff --git a/src/app/shared/data-access/profile/profile-http.service.ts b/src/app/shared/data-access/profile/profile-http.service.ts index a3eb2abf..22c4bbbf 100644 --- a/src/app/shared/data-access/profile/profile-http.service.ts +++ b/src/app/shared/data-access/profile/profile-http.service.ts @@ -3,9 +3,9 @@ import { Injectable } from '@angular/core'; import { catchError, map, Observable, of, shareReplay } from 'rxjs'; import { EnvironmentService } from '../environment.service'; import { - APIResponse, asError, asSuccess, + OldAPIResponse, } from '../../util/http/response.types'; import { Location, @@ -31,7 +31,7 @@ export class ProfileHttpService { this.locationApiUrl = `${this.environment.getApiUrl()}/location`; } - getProfile$(): Observable> { + getProfile$(): Observable> { return this.http .get(`${this.profileApiUrl}`, this.defaultOptions) .pipe( @@ -45,7 +45,7 @@ export class ProfileHttpService { ); } - updateProfile$(profile: Profile): Observable> { + updateProfile$(profile: Profile): Observable> { return this.http .put(`${this.profileApiUrl}`, profile, this.defaultOptions) .pipe( @@ -62,7 +62,7 @@ export class ProfileHttpService { checkNameAvailability$( dancerName: string - ): Observable> { + ): Observable> { return this.http .get( `${this.profileApiUrl}/checkDancerNameAvailability/${dancerName}`, @@ -79,7 +79,7 @@ export class ProfileHttpService { ); } - getLocation$(zipCode: string): Observable> { + getLocation$(zipCode: string): Observable> { // there is only one country 'GER' at the moment return this.http .get( diff --git a/src/app/shared/data-access/profile/profile-old.service.ts b/src/app/shared/data-access/profile/profile-old.service.ts index f6417415..fc404048 100644 --- a/src/app/shared/data-access/profile/profile-old.service.ts +++ b/src/app/shared/data-access/profile/profile-old.service.ts @@ -9,7 +9,7 @@ import { import { BehaviorSubject, filter, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { isNonNull } from '../../util/rxjs.utils'; -import { APIResponse } from '../../util/http/response.types'; +import { OldAPIResponse } from '../../util/http/response.types'; import { EnvironmentService } from '../environment.service'; import { ImageService } from '../image.service'; @@ -44,7 +44,7 @@ export class ProfileOldService { }); } - updateProfile(profile: Profile): Observable> { + updateProfile(profile: Profile): Observable> { const request = this.profileHttpService.updateProfile$(profile); request.subscribe((response) => { if (response.isSuccess) { @@ -56,7 +56,7 @@ export class ProfileOldService { patchAndUpdateProfile( profile: Partial - ): Observable> { + ): Observable> { if (this._profile.value === null) { // the profile should be already fetched for all modifications throw new Error("profile hasn't been fetched yet"); @@ -71,23 +71,25 @@ export class ProfileOldService { return this._profile.value; } - setProfile(profile: Partial): Observable> { + setProfile(profile: Partial): Observable> { return this.patchAndUpdateProfile(profile); } - setDancerName(dancerName: string): Observable> { + setDancerName(dancerName: string): Observable> { return this.patchAndUpdateProfile({ dancerName }); } - setPersonalData(personalData: PersonalData): Observable> { + setPersonalData( + personalData: PersonalData + ): Observable> { return this.patchAndUpdateProfile(personalData); } - setOwnDances(ableTo: Dance[]): Observable> { + setOwnDances(ableTo: Dance[]): Observable> { return this.patchAndUpdateProfile({ ableTo }); } - setPartnerDances(wantsTo: Dance[]): Observable> { + setPartnerDances(wantsTo: Dance[]): Observable> { return this.patchAndUpdateProfile({ wantsTo }); } diff --git a/src/app/shared/util/auth/with-credentials-interceptor.service.ts b/src/app/shared/util/auth/with-credentials-interceptor.service.ts index 8972ee92..09a0e0eb 100644 --- a/src/app/shared/util/auth/with-credentials-interceptor.service.ts +++ b/src/app/shared/util/auth/with-credentials-interceptor.service.ts @@ -22,7 +22,9 @@ export class WithCredentialsInterceptor implements HttpInterceptor { ): Observable> { let modifiedRequest; if (this.environment.isLocalDevelopment()) { - modifiedRequest = this.addJwtAuthorization(request); + // enable to use the jwt token instead during local development + // modifiedRequest = this.addJwtAuthorization(request); + modifiedRequest = this.addCredentialsCookie(request); } else { modifiedRequest = this.addCredentialsCookie(request); } diff --git a/src/app/shared/util/http/response.types.ts b/src/app/shared/util/http/response.types.ts index 1c846264..8aedc130 100644 --- a/src/app/shared/util/http/response.types.ts +++ b/src/app/shared/util/http/response.types.ts @@ -24,7 +24,7 @@ export type ResponseError = { error: APIError; }; -export type APIResponse = ResponseError | ResponseSuccess; +export type OldAPIResponse = ResponseError | ResponseSuccess; export const asError = (error: APIError): ResponseError => ({ isSuccess: false, @@ -37,3 +37,22 @@ export const asSuccess: AsSuccess = (payload) => ({ isSuccess: true, payload, }); + +export type ApiResponse = + | ApiResponseSuccess + | ApiResponseError + | ApiResponseLoading; + +export type ApiResponseLoading = { + fetchStatus: 'loading'; +}; + +export type ApiResponseSuccess = { + fetchStatus: 'success'; + payload: T; +}; + +export type ApiResponseError = { + fetchStatus: 'error'; + httpStatusCode: number; +}; diff --git a/src/app/shared/util/http/response.utils.ts b/src/app/shared/util/http/response.utils.ts new file mode 100644 index 00000000..56710ace --- /dev/null +++ b/src/app/shared/util/http/response.utils.ts @@ -0,0 +1,24 @@ +import { catchError, map, Observable, of } from 'rxjs'; +import { ApiResponse } from './response.types'; +import { HttpErrorResponse } from '@angular/common/http'; +import { startWith } from 'rxjs/operators'; + +export function toApiResponse( + obs$: Observable +): Observable> { + return obs$.pipe( + map((payload) => ({ + fetchStatus: 'success' as const, + payload, + })), + catchError((error: HttpErrorResponse) => { + return of({ + fetchStatus: 'error' as const, + httpStatusCode: error.status, + }); + }), + startWith({ + fetchStatus: 'loading' as const, + }) + ); +} diff --git a/src/app/shared/util/signal.utils.ts b/src/app/shared/util/signal.utils.ts new file mode 100644 index 00000000..28104f8d --- /dev/null +++ b/src/app/shared/util/signal.utils.ts @@ -0,0 +1,49 @@ +import { computed, Signal } from '@angular/core'; +import { catchError, map, Observable, of } from 'rxjs'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { startWith } from 'rxjs/operators'; + +export function toSignalWithError( + obs$: Observable, + initialValue: T | undefined = undefined +): { + value: Signal; + error: Signal; +} { + let source: Signal< + | { + value: T; + error: undefined; + } + | { + value: undefined; + error: any; + } + | undefined + >; + + if (initialValue) { + source = toSignal( + obs$.pipe( + map((value) => ({ value, error: undefined })), + catchError((err) => of({ value: undefined, error: err })), + startWith({ value: initialValue, error: undefined }) + ) + ); + } else { + source = toSignal( + obs$.pipe( + map((value) => ({ value, error: undefined })), + catchError((err) => of({ value: undefined, error: err })) + ), + { + requireSync: true, + } + ); + } + + const value = computed(() => source()?.value); + const error = computed(() => source()?.error); + + return { value, error }; +}