Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export class ProfileLayoutComponent {
const profile = this.profile();
if (!profile?.profile) return '';

return profile.profile.title || '';
return profile.profile.job_title || '';
});
}

Expand All @@ -156,7 +156,7 @@ export class ProfileLayoutComponent {

const parts = [];
if (profile.profile.city) parts.push(profile.profile.city);
if (profile.profile.state) parts.push(profile.profile.state);
if (profile.profile.state_province) parts.push(profile.profile.state_province);
if (profile.profile.country) parts.push(profile.profile.country);

return parts.join(', ');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,28 @@
</lfx-breadcrumb>
</div>

<div class="flex flex-col md:flex-row md:items-center justify-between">
<div class="flex flex-col items-start gap-3">
<div class="flex flex-col md:flex-row md:items-center justify-between w-full">
<div class="flex justify-between items-start gap-3 w-full">
<div class="flex flex-col gap-1 w-full">
<!-- Project Title -->
<h1 class="text-2xl font-display font-semibold text-gray-900 mb-3">
{{ projectTitle() }}
</h1>

<!-- Project Description -->
@if (projectDescription(); as description) {
<p class="text-gray-600 text-sm mb-6 max-w-3xl leading-relaxed">
{{ description }}
</p>
}
</div>

<!-- Category Badge -->
@if (categoryLabel(); as category) {
<div class="flex justify-start">
<img [src]="projectLogo()" class="h-10 w-full" />
<img [src]="projectLogo()" class="h-16 w-full" />
</div>
}

<!-- Project Title -->
<h1 class="text-2xl font-display font-semibold text-gray-900 mb-3">
{{ projectTitle() }}
</h1>

<!-- Project Description -->
@if (projectDescription(); as description) {
<p class="text-gray-600 text-sm mb-6 max-w-3xl leading-relaxed">
{{ description }}
</p>
}
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ <h3 class="text-base font-medium text-gray-900" data-testid="profile-edit-person
<lfx-input-text
size="small"
[form]="profileForm"
control="first_name"
control="given_name"
id="first-name"
placeholder="Enter your first name"
styleClass="w-full"
Expand All @@ -51,7 +51,7 @@ <h3 class="text-base font-medium text-gray-900" data-testid="profile-edit-person
<lfx-input-text
size="small"
[form]="profileForm"
control="last_name"
control="family_name"
id="last-name"
placeholder="Enter your last name"
styleClass="w-full"
Expand Down Expand Up @@ -97,7 +97,7 @@ <h3 class="text-base font-medium text-gray-900" data-testid="profile-edit-profes
<lfx-input-text
size="small"
[form]="profileForm"
control="title"
control="job_title"
id="job-title"
placeholder="Enter your job title"
styleClass="w-full"
Expand Down Expand Up @@ -167,7 +167,7 @@ <h3 class="text-base font-medium text-gray-900" data-testid="profile-edit-locati
<lfx-select
size="small"
[form]="profileForm"
control="state"
control="state_province"
[options]="stateOptions"
id="state"
placeholder="Select your state"
Expand All @@ -179,7 +179,7 @@ <h3 class="text-base font-medium text-gray-900" data-testid="profile-edit-locati
<lfx-input-text
size="small"
[form]="profileForm"
control="state"
control="state_province"
id="state"
placeholder="Enter your state or province"
styleClass="w-full"
Expand Down Expand Up @@ -222,7 +222,7 @@ <h3 class="text-base font-medium text-gray-900" data-testid="profile-edit-locati
<lfx-input-text
size="small"
[form]="profileForm"
control="zipcode"
control="postal_code"
id="zipcode"
placeholder="Enter your postal or zip code"
styleClass="w-full"
Expand Down Expand Up @@ -269,7 +269,7 @@ <h3 class="text-base font-medium text-gray-900" data-testid="profile-edit-additi
<lfx-select
size="small"
[form]="profileForm"
control="tshirt_size"
control="t_shirt_size"
[options]="tshirtSizeOptions"
id="tshirt-size"
placeholder="Select your t-shirt size"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Component, computed, inject, OnInit, Signal, signal } from '@angular/co
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { COUNTRIES, markFormControlsAsTouched, TSHIRT_SIZES, US_STATES } from '@lfx-one/shared';
import { CombinedProfile, UpdateProfileDetailsRequest, UpdateUserProfileRequest } from '@lfx-one/shared/interfaces';
import { CombinedProfile, ProfileUpdateRequest, UserMetadata } from '@lfx-one/shared/interfaces';
import { UserService } from '@services/user.service';
import { ButtonComponent } from '@shared/components/button/button.component';
import { CardComponent } from '@shared/components/card/card.component';
Expand All @@ -16,7 +16,7 @@ import { SelectComponent } from '@shared/components/select/select.component';
import { MessageService } from 'primeng/api';
import { ToastModule } from 'primeng/toast';
import { TooltipModule } from 'primeng/tooltip';
import { BehaviorSubject, catchError, finalize, forkJoin, of, switchMap, tap } from 'rxjs';
import { BehaviorSubject, catchError, finalize, of, switchMap, tap } from 'rxjs';

@Component({
selector: 'lfx-profile-edit',
Expand Down Expand Up @@ -78,23 +78,23 @@ export class ProfileEditComponent implements OnInit {
return this.selectedCountrySignal() === 'United States';
});

// Profile form
// Profile form with backend-aligned field names
public profileForm: FormGroup = this.fb.group({
// User table fields
first_name: ['', [Validators.maxLength(50)]],
last_name: ['', [Validators.maxLength(50)]],
// Direct user fields
username: [{ value: '', disabled: true }, [Validators.required, Validators.minLength(3), Validators.maxLength(30)]],

// Profile table fields
title: ['', [Validators.maxLength(100)]],
// User metadata fields
given_name: ['', [Validators.maxLength(50)]],
family_name: ['', [Validators.maxLength(50)]],
job_title: ['', [Validators.maxLength(100)]],
organization: ['', [Validators.maxLength(100)]],
country: ['', [Validators.maxLength(50)]],
state: ['', [Validators.maxLength(50)]],
state_province: ['', [Validators.maxLength(50)]],
city: ['', [Validators.maxLength(50)]],
address: ['', [Validators.maxLength(200)]],
zipcode: ['', [Validators.maxLength(20)]],
postal_code: ['', [Validators.maxLength(20)]],
phone_number: ['', [Validators.maxLength(20)]],
tshirt_size: ['', []],
t_shirt_size: ['', []],
});

public constructor() {
Expand All @@ -106,9 +106,9 @@ export class ProfileEditComponent implements OnInit {
.subscribe((country: string) => {
this.selectedCountrySignal.set(country || '');

// Clear state field when country changes to avoid invalid state/country combinations
// Clear state_province field when country changes to avoid invalid state/country combinations
if (country !== 'United States') {
this.profileForm.get('state')?.setValue('');
this.profileForm.get('state_province')?.setValue('');
}
});
}
Expand Down Expand Up @@ -137,28 +137,29 @@ export class ProfileEditComponent implements OnInit {

this.savingSignal.set(true);

// Prepare user update data
const userUpdate: UpdateUserProfileRequest = {
first_name: formValue.first_name || null,
last_name: formValue.last_name || null,
username: formValue.username || null,
// Build user_metadata only if there are fields to update
const userMetadata: Partial<UserMetadata> = {
given_name: formValue.given_name || undefined,
family_name: formValue.family_name || undefined,
job_title: formValue.job_title || undefined,
organization: formValue.organization || undefined,
country: formValue.country || undefined,
state_province: formValue.state_province || undefined,
city: formValue.city || undefined,
address: formValue.address || undefined,
postal_code: formValue.postal_code || undefined,
phone_number: formValue.phone_number || undefined,
t_shirt_size: formValue.t_shirt_size || undefined,
};

// Prepare profile update data
const profileUpdate: UpdateProfileDetailsRequest = {
title: formValue.title || null,
organization: formValue.organization || null,
country: formValue.country || null,
state: formValue.state || null,
city: formValue.city || null,
address: formValue.address || null,
zipcode: formValue.zipcode || null,
phone_number: formValue.phone_number || null,
tshirt_size: formValue.tshirt_size || null,
// Prepare update data - only send user_metadata
const updateData: ProfileUpdateRequest = {
user_metadata: userMetadata as UserMetadata,
};

// Update both user and profile data in parallel using forkJoin
forkJoin([this.userService.updateUserInfo(userUpdate), this.userService.updateProfileDetails(profileUpdate)])
// Update profile via unified endpoint
this.userService
.updateUserProfile(updateData)
.pipe(finalize(() => this.savingSignal.set(false)))
.subscribe({
next: () => {
Expand Down Expand Up @@ -220,21 +221,18 @@ export class ProfileEditComponent implements OnInit {
const countryValue = profile.profile?.country || '';

this.profileForm.patchValue({
// User fields
first_name: profile.user.first_name || '',
last_name: profile.user.last_name || '',
given_name: profile.user.first_name || '',
family_name: profile.user.last_name || '',
username: profile.user.username || '',

// Profile fields
title: profile.profile?.title || '',
job_title: profile.profile?.job_title || '',
organization: profile.profile?.organization || '',
country: countryValue,
state: profile.profile?.state || '',
state_province: profile.profile?.state_province || '',
city: profile.profile?.city || '',
address: profile.profile?.address || '',
zipcode: profile.profile?.zipcode || '',
postal_code: profile.profile?.postal_code || '',
phone_number: profile.profile?.phone_number || '',
tshirt_size: profile.profile?.tshirt_size || '',
t_shirt_size: profile.profile?.t_shirt_size || '',
});

// Set the initial country signal value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,9 @@ export class MeetingCardComponent implements OnInit {
}

public copyMeetingLink(): void {
const meetingLink = environment.urls.home + '/meetings/' + this.meeting().uid;
this.clipboard.copy(meetingLink);
const meetingUrl: URL = new URL(environment.urls.home + '/meetings/' + this.meeting().uid);
meetingUrl.searchParams.set('password', this.meeting().password || '');
this.clipboard.copy(meetingUrl.toString());
this.messageService.add({
severity: 'success',
summary: 'Meeting Link Copied',
Expand Down
17 changes: 5 additions & 12 deletions apps/lfx-one/src/app/shared/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import {
CreateUserPermissionRequest,
EmailManagementData,
EmailPreferences,
ProfileUpdateRequest,
TwoFactorSettings,
UpdateEmailPreferencesRequest,
UpdateProfileDetailsRequest,
UpdateUserProfileRequest,
User,
UserEmail,
} from '@lfx-one/shared/interfaces';
Expand Down Expand Up @@ -43,17 +42,11 @@ export class UserService {
}

/**
* Update user info fields (first_name, last_name, username)
* Update user profile metadata via unified NATS endpoint
* Only sends user_metadata - username and email are extracted from OIDC claim on backend
*/
public updateUserInfo(data: UpdateUserProfileRequest): Observable<User> {
return this.http.patch<User>('/api/profile/user', data).pipe(take(1));
}

/**
* Update profile details fields (title, organization, location, etc.)
*/
public updateProfileDetails(data: UpdateProfileDetailsRequest): Observable<any> {
return this.http.patch('/api/profile/details', data).pipe(take(1));
public updateUserProfile(data: ProfileUpdateRequest): Observable<any> {
return this.http.patch('/api/profile', data).pipe(take(1));
}

// Email management methods
Expand Down
Loading