feat: Add pm

This commit is contained in:
2026-04-27 00:45:16 +02:00
parent bc2fa7de22
commit 11c2588e45
65 changed files with 3653 additions and 214 deletions

View File

@@ -1,5 +1,6 @@
import {
Component,
computed,
effect,
inject,
signal
@@ -7,7 +8,7 @@ import {
import { CommonModule } from '@angular/common';
import { Store } from '@ngrx/store';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideChevronDown } from '@ng-icons/lucide';
import { lucideCheck, lucideChevronDown } from '@ng-icons/lucide';
import { UserAvatarComponent } from '../user-avatar/user-avatar.component';
import { UserStatusService } from '../../../core/services/user-status.service';
import { User, UserStatus } from '../../../shared-kernel';
@@ -19,6 +20,7 @@ import {
ProcessedProfileAvatar
} from '../../../domains/profile-avatar';
import { UsersActions } from '../../../store/users/users.actions';
import { selectUsersEntities } from '../../../store/users/users.selectors';
@Component({
selector: 'app-profile-card',
@@ -28,11 +30,20 @@ import { UsersActions } from '../../../store/users/users.actions';
NgIcon,
UserAvatarComponent
],
viewProviders: [provideIcons({ lucideChevronDown })],
viewProviders: [provideIcons({ lucideCheck, lucideChevronDown })],
templateUrl: './profile-card.component.html'
})
export class ProfileCardComponent {
readonly user = signal<User>({ id: '', oderId: '', username: '', displayName: '', status: 'offline', role: 'member', joinedAt: 0 });
private readonly store = inject(Store);
private readonly users = this.store.selectSignal(selectUsersEntities);
readonly displayedUser = computed(() => {
const snapshot = this.user();
const entities = this.users();
const liveUser = entities[snapshot.id] ?? entities[snapshot.oderId];
return liveUser ? { ...snapshot, ...liveUser } : snapshot;
});
readonly editable = signal(false);
readonly showStatusMenu = signal(false);
readonly avatarAccept = PROFILE_AVATAR_ACCEPT_ATTRIBUTE;
@@ -50,11 +61,10 @@ export class ProfileCardComponent {
];
private readonly userStatus = inject(UserStatusService);
private readonly store = inject(Store);
private readonly profileAvatar = inject(ProfileAvatarFacade);
private readonly profileAvatarEditor = inject(ProfileAvatarEditorService);
private readonly syncProfileDrafts = effect(() => {
const user = this.user();
const user = this.displayedUser();
const editingField = this.editingField();
if (editingField !== 'displayName') {
@@ -68,7 +78,7 @@ export class ProfileCardComponent {
}, { allowSignalWrites: true });
currentStatusColor(): string {
switch (this.user().status) {
switch (this.displayedUser().status) {
case 'online': return 'bg-green-500';
case 'away': return 'bg-yellow-500';
case 'busy': return 'bg-red-500';
@@ -79,7 +89,7 @@ export class ProfileCardComponent {
}
currentStatusLabel(): string {
switch (this.user().status) {
switch (this.displayedUser().status) {
case 'online': return 'Online';
case 'away': return 'Away';
case 'busy': return 'Do Not Disturb';
@@ -98,6 +108,14 @@ export class ProfileCardComponent {
this.showStatusMenu.set(false);
}
isStatusOptionSelected(status: UserStatus | null): boolean {
const currentStatus = this.displayedUser().status;
return status === null
? currentStatus === 'online'
: currentStatus === status;
}
onDisplayNameInput(event: Event): void {
this.displayNameDraft.set((event.target as HTMLInputElement).value);
}
@@ -168,7 +186,7 @@ export class ProfileCardComponent {
}
async applyAvatar(avatar: ProcessedProfileAvatar): Promise<void> {
const currentUser = this.user();
const currentUser = this.displayedUser();
this.avatarSaving.set(true);
this.avatarError.set(null);
@@ -202,7 +220,7 @@ export class ProfileCardComponent {
return;
}
const user = this.user();
const user = this.displayedUser();
const description = this.normalizeDescription(this.descriptionDraft());
if (