feat: Add user metadata changing display name and description with sync
All checks were successful
Queue Release Build / prepare (push) Successful in 28s
Deploy Web Apps / deploy (push) Successful in 5m2s
Queue Release Build / build-windows (push) Successful in 16m44s
Queue Release Build / build-linux (push) Successful in 27m12s
Queue Release Build / finalize (push) Successful in 22s

This commit is contained in:
2026-04-17 22:04:18 +02:00
parent 3ba8a2c9eb
commit bd21568726
41 changed files with 1176 additions and 191 deletions

View File

@@ -2,63 +2,109 @@
class="w-72 rounded-lg border border-border bg-card shadow-xl"
style="animation: profile-card-in 120ms cubic-bezier(0.2, 0, 0, 1) both"
>
<div class="h-24 rounded-t-lg bg-gradient-to-r from-primary/30 to-primary/10"></div>
@let profileUser = user();
@let isEditable = editable();
@let activeField = editingField();
@let statusColor = currentStatusColor();
@let statusLabel = currentStatusLabel();
<div class="h-20 rounded-t-lg bg-gradient-to-r from-primary/30 to-primary/10"></div>
<div class="relative px-4">
<div class="-mt-9">
@if (editable()) {
<button
#avatarInputButton
type="button"
class="group relative rounded-full focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"
(click)="pickAvatar(avatarInput)"
>
<app-user-avatar
[name]="user().displayName"
[avatarUrl]="user().avatarUrl"
size="xl"
[status]="user().status"
[showStatusBadge]="true"
ringClass="ring-4 ring-card"
/>
<span class="pointer-events-none absolute inset-0 rounded-full bg-black/0 transition-colors group-hover:bg-black/15"></span>
</button>
<input
#avatarInput
type="file"
class="hidden"
[accept]="avatarAccept"
(change)="onAvatarSelected($event)"
/>
} @else {
<div class="-mt-8">
<button
type="button"
class="rounded-full"
(click)="pickAvatar(avatarInput)"
>
<app-user-avatar
[name]="user().displayName"
[avatarUrl]="user().avatarUrl"
[name]="profileUser.displayName"
[avatarUrl]="profileUser.avatarUrl"
size="xl"
[status]="user().status"
[status]="profileUser.status"
[showStatusBadge]="true"
ringClass="ring-4 ring-card"
/>
}
</button>
<input
#avatarInput
type="file"
class="hidden"
[accept]="avatarAccept"
(change)="onAvatarSelected($event)"
/>
</div>
</div>
<div class="px-5 pb-4 pt-3">
<p class="truncate text-base font-semibold text-foreground">{{ user().displayName }}</p>
<p class="truncate text-sm text-muted-foreground">{{ user().username }}</p>
<div class="px-4 pb-3 pt-2.5">
@if (isEditable) {
<div class="space-y-2">
<div>
@if (activeField === 'displayName') {
<input
type="text"
class="w-full rounded-md border border-border bg-background/70 px-2 py-1.5 text-base font-semibold text-foreground outline-none focus:border-primary/70"
[value]="displayNameDraft()"
(input)="onDisplayNameInput($event)"
(blur)="finishEdit('displayName')"
/>
} @else {
<button
type="button"
class="block w-full py-0.5 text-left text-base font-semibold text-foreground"
(click)="startEdit('displayName')"
>
{{ profileUser.displayName }}
</button>
}
@if (editable()) {
<p class="mt-2 text-xs text-muted-foreground">Click avatar to upload and crop a profile picture.</p>
<p class="truncate text-sm text-muted-foreground">{{ profileUser.username }}</p>
</div>
<div>
@if (activeField === 'description') {
<textarea
rows="3"
class="w-full resize-none rounded-md border border-border bg-background/70 px-2 py-2 text-sm leading-5 text-foreground outline-none focus:border-primary/70"
[value]="descriptionDraft()"
placeholder="Add a description"
(input)="onDescriptionInput($event)"
(blur)="finishEdit('description')"
></textarea>
} @else {
<button
type="button"
class="block w-full py-1 text-left text-sm leading-5"
(click)="startEdit('description')"
>
@if (profileUser.description) {
<span class="whitespace-pre-line text-muted-foreground">{{ profileUser.description }}</span>
} @else {
<span class="text-muted-foreground/70">Add a description</span>
}
</button>
}
</div>
</div>
} @else {
<p class="truncate text-base font-semibold text-foreground">{{ profileUser.displayName }}</p>
<p class="truncate text-sm text-muted-foreground">{{ profileUser.username }}</p>
@if (profileUser.description) {
<p class="mt-2 whitespace-pre-line text-sm leading-5 text-muted-foreground">{{ profileUser.description }}</p>
}
}
@if (avatarError()) {
<div class="mt-3 rounded-md border border-red-500/40 bg-red-500/10 px-3 py-2 text-xs text-red-200">
<div class="mt-2.5 rounded-md border border-red-500/40 bg-red-500/10 px-3 py-2 text-xs text-red-200">
{{ avatarError() }}
</div>
}
@if (editable()) {
<div class="relative mt-3">
@if (isEditable) {
<div class="relative mt-2.5">
<button
type="button"
class="flex w-full items-center gap-2 rounded-md border border-border px-2.5 py-1.5 text-xs transition-colors hover:bg-secondary/60"
@@ -66,9 +112,9 @@
>
<span
class="h-2 w-2 rounded-full"
[class]="currentStatusColor()"
[class]="statusColor"
></span>
<span class="flex-1 text-left text-foreground">{{ currentStatusLabel() }}</span>
<span class="flex-1 text-left text-foreground">{{ statusLabel }}</span>
<ng-icon
name="lucideChevronDown"
class="h-3 w-3 text-muted-foreground"
@@ -97,9 +143,9 @@
<div class="mt-2 flex items-center gap-1.5 text-xs text-muted-foreground">
<span
class="h-2 w-2 rounded-full"
[class]="currentStatusColor()"
[class]="statusColor"
></span>
<span>{{ currentStatusLabel() }}</span>
<span>{{ statusLabel }}</span>
</div>
}
</div>