Sistemato un po' il codice

This commit is contained in:
Lorenzo Sanesi
2026-04-04 12:56:09 +02:00
parent aa5ca9bbe3
commit 3d4eac4ea5
12 changed files with 254 additions and 188 deletions
@@ -4,6 +4,7 @@ import {CommonModule} from '@angular/common';
import {MatIconModule} from '@angular/material/icon';
import {filter, map} from 'rxjs';
import {LanguageService} from '../../services/language';
import {ThemeService} from '../../services/theme';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -15,6 +16,8 @@ import {LanguageService} from '../../services/language';
export class App {
private router = inject(Router);
private langService = inject(LanguageService);
// Iniettato qui per garantire l'inizializzazione del tema al primo caricamento
private themeService = inject(ThemeService);
fillIcon = "'FILL' 1";
showBottomNav$ = this.router.events.pipe(
@@ -36,47 +36,53 @@
</div>
<!-- History List -->
<div *ngIf="isLoading()" class="flex flex-col items-center justify-center py-12">
<div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin"></div>
<p class="mt-4 text-on-surface-variant text-sm font-medium">Loading history...</p>
</div>
@if (isLoading()) {
<div class="flex flex-col items-center justify-center py-12">
<div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin"></div>
<p class="mt-4 text-on-surface-variant text-sm font-medium">Loading history...</p>
</div>
} @else {
<div class="space-y-4">
@for (challenge of historyChallenges(); track challenge.id) {
<div class="bg-surface rounded-3xl p-5 soft-shadow border border-black/5 active:scale-[0.98] transition-transform cursor-pointer" [routerLink]="['/challenge-detail', challenge.id]">
<div class="flex justify-between items-start mb-4">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-2xl bg-surface-container flex items-center justify-center">
<mat-icon class="text-primary">history</mat-icon>
</div>
<div>
<h4 class="font-headline font-bold text-on-surface">{{ challenge.title }}</h4>
<p class="text-xs text-on-surface-variant font-medium">{{ challenge.endDate | date:'mediumDate' }}</p>
</div>
</div>
<span
class="px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest"
[class.bg-secondary-container]="challenge.status === 'COMPLETED'"
[class.text-secondary]="challenge.status === 'COMPLETED'"
[class.bg-error-container]="challenge.status === 'EXPIRED'"
[class.text-error]="challenge.status === 'EXPIRED'"
>
{{ challenge.status }}
</span>
</div>
<div class="flex justify-between items-center pt-2 border-t border-black/5 mt-2">
<div class="flex items-center gap-2">
<mat-icon class="text-sm text-on-surface-variant">groups</mat-icon>
<span class="text-xs font-bold text-on-surface-variant">{{ challenge.participants.length }}</span>
</div>
<div class="text-lg font-headline font-black text-primary">€{{ challenge.stake }}</div>
</div>
</div>
}
<div *ngIf="!isLoading()" class="space-y-4">
<div *ngFor="let challenge of historyChallenges()" class="bg-surface rounded-3xl p-5 soft-shadow border border-black/5 active:scale-[0.98] transition-transform cursor-pointer" [routerLink]="['/challenge-detail', challenge._id]">
<div class="flex justify-between items-start mb-4">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-2xl bg-surface-container flex items-center justify-center">
<mat-icon class="text-primary">history</mat-icon>
</div>
<div>
<h4 class="font-headline font-bold text-on-surface">{{ challenge.title }}</h4>
<p class="text-xs text-on-surface-variant font-medium">{{ challenge.endDate | date:'mediumDate' }}</p>
</div>
@if (historyChallenges().length === 0) {
<div class="flex flex-col items-center justify-center py-20 text-center bg-surface-container-low rounded-3xl border border-dashed border-on-surface-variant/20">
<mat-icon class="text-6xl text-on-surface-variant/10 mb-4">history_toggle_off</mat-icon>
<h3 class="text-xl font-headline font-bold">No History Found</h3>
<p class="text-on-surface-variant mt-2">Try searching for something else or join a new bet!</p>
</div>
<span
class="px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest"
[class.bg-secondary-container]="challenge.status === 'COMPLETED'"
[class.text-secondary]="challenge.status === 'COMPLETED'"
[class.bg-error-container]="challenge.status === 'EXPIRED'"
[class.text-error]="challenge.status === 'EXPIRED'"
>
{{ challenge.status }}
</span>
</div>
<div class="flex justify-between items-center pt-2 border-t border-black/5 mt-2">
<div class="flex items-center gap-2">
<mat-icon class="text-sm text-on-surface-variant">groups</mat-icon>
<span class="text-xs font-bold text-on-surface-variant">{{ challenge.participants.length }}</span>
</div>
<div class="text-lg font-headline font-black text-primary">€{{ challenge.stake }}</div>
</div>
}
</div>
<div *ngIf="historyChallenges().length === 0" class="flex flex-col items-center justify-center py-20 text-center bg-surface-container-low rounded-3xl border border-dashed border-on-surface-variant/20">
<mat-icon class="text-6xl text-on-surface-variant/10 mb-4">history_toggle_off</mat-icon>
<h3 class="text-xl font-headline font-bold">No History Found</h3>
<p class="text-on-surface-variant mt-2">Try searching for something else or join a new bet!</p>
</div>
</div>
}
</main>
</div>
@@ -60,15 +60,25 @@
<span class="text-sm font-medium text-primary">{{ data.participants.length }} Joined</span>
</div>
<div class="flex flex-wrap gap-3">
<div *ngFor="let p of data.participants" class="flex flex-col items-center gap-1">
<div class="relative">
<img [src]="'https://picsum.photos/seed/' + p.user + '/100/100'" class="h-12 w-12 rounded-full ring-2 ring-primary/20 object-cover" alt="Participant">
<div *ngIf="p.role === 'OBSERVER'" class="absolute -bottom-1 -right-1 bg-surface-container rounded-full p-0.5 shadow-sm">
<mat-icon class="text-[10px] text-on-surface-variant">visibility</mat-icon>
@for (p of data.participants; track $index) {
<div class="flex flex-col items-center gap-1">
<div class="relative">
@if ($any(p.user)?.avatarUrl) {
<img [src]="$any(p.user).avatarUrl" class="h-12 w-12 rounded-full ring-2 ring-primary/20 object-cover" [alt]="$any(p.user).name">
} @else {
<div class="h-12 w-12 rounded-full ring-2 ring-primary/20 bg-primary-container/20 flex items-center justify-center">
<mat-icon class="text-primary">person</mat-icon>
</div>
}
@if (p.role === 'OBSERVER') {
<div class="absolute -bottom-1 -right-1 bg-surface-container rounded-full p-0.5 shadow-sm">
<mat-icon class="text-[10px] text-on-surface-variant">visibility</mat-icon>
</div>
}
</div>
<span class="text-[10px] font-medium text-on-surface-variant">{{ $any(p.user)?.name || 'Utente' }}</span>
</div>
<span class="text-[10px] font-medium text-on-surface-variant">User #{{ p.user }}</span>
</div>
}
</div>
</section>
} @else {
@@ -45,7 +45,9 @@
<div class="relative">
<select id="groupId" formControlName="groupId" class="w-full bg-surface-container-highest border-none rounded-full px-6 py-4 focus:ring-2 focus:ring-primary text-on-surface appearance-none">
<option value="">No Group</option>
<option *ngFor="let group of availableGroups()" [value]="group.id">{{ group.name }}</option>
@for (group of availableGroups(); track group.id) {
<option [value]="group.id">{{ group.name }}</option>
}
</select>
<mat-icon class="absolute right-5 top-1/2 -translate-y-1/2 text-on-surface-variant pointer-events-none">expand_more</mat-icon>
</div>
@@ -83,36 +85,43 @@
class="w-full bg-surface-container rounded-full pl-12 pr-6 py-3 focus:ring-2 focus:ring-primary text-sm border-none">
<!-- Search Results Dropdown -->
<div *ngIf="searchResults().length > 0" class="absolute top-full left-0 right-0 mt-2 bg-surface shadow-xl rounded-2xl overflow-hidden z-10 border border-black/5">
<div *ngFor="let user of searchResults()"
(click)="addParticipant(user)"
class="flex items-center gap-3 p-3 hover:bg-surface-container-highest cursor-pointer transition-colors">
<img [src]="user.avatarUrl || 'assets/default-avatar.png'" class="w-10 h-10 rounded-full object-cover">
<div>
<p class="text-sm font-bold">{{ user.name }}</p>
<p class="text-xs text-on-surface-variant">{{ user.email }}</p>
</div>
<mat-icon class="ml-auto text-primary">add_circle</mat-icon>
@if (searchResults().length > 0) {
<div class="absolute top-full left-0 right-0 mt-2 bg-surface shadow-xl rounded-2xl overflow-hidden z-10 border border-black/5">
@for (user of searchResults(); track user.id) {
<div (click)="addParticipant(user)"
class="flex items-center gap-3 p-3 hover:bg-surface-container-highest cursor-pointer transition-colors">
<img [src]="user.avatarUrl || 'assets/default-avatar.png'" class="w-10 h-10 rounded-full object-cover">
<div>
<p class="text-sm font-bold">{{ user.name }}</p>
<p class="text-xs text-on-surface-variant">{{ user.email }}</p>
</div>
<mat-icon class="ml-auto text-primary">add_circle</mat-icon>
</div>
}
</div>
</div>
}
</div>
<!-- Selected Participants -->
<div class="flex gap-4 overflow-x-auto no-scrollbar py-2">
<div *ngFor="let user of selectedParticipants()" class="flex-shrink-0 text-center space-y-2 group relative">
<div class="relative">
<img [src]="user.avatarUrl || 'assets/default-avatar.png'" class="w-16 h-16 rounded-full object-cover ring-4 ring-secondary-container" alt="Participant">
<button (click)="removeParticipant(user.id)"
class="absolute -top-1 -right-1 bg-error text-white w-6 h-6 rounded-full flex items-center justify-center shadow-md">
<mat-icon class="text-[14px]">close</mat-icon>
</button>
@for (user of selectedParticipants(); track user.id) {
<div class="flex-shrink-0 text-center space-y-2 group relative">
<div class="relative">
<img [src]="user.avatarUrl || 'assets/default-avatar.png'" class="w-16 h-16 rounded-full object-cover ring-4 ring-secondary-container" alt="Participant">
<button (click)="removeParticipant(user.id)"
class="absolute -top-1 -right-1 bg-error text-white w-6 h-6 rounded-full flex items-center justify-center shadow-md">
<mat-icon class="text-[14px]">close</mat-icon>
</button>
</div>
<p class="text-xs font-medium">{{ user.name }}</p>
</div>
<p class="text-xs font-medium">{{ user.name }}</p>
</div>
<div *ngIf="selectedParticipants().length === 0" class="flex items-center justify-center w-full py-8 text-on-surface-variant text-sm italic">
Add at least one participant...
</div>
}
@if (selectedParticipants().length === 0) {
<div class="flex items-center justify-center w-full py-8 text-on-surface-variant text-sm italic">
Add at least one participant...
</div>
}
</div>
</section>
@@ -66,7 +66,7 @@ export class CreateBet implements OnInit {
if (bet) {
this.isEditing = true;
this.editingId = bet._id || bet.id;
this.editingId = bet.id;
this.betForm.patchValue({
title: bet.title,
description: bet.description,
@@ -77,32 +77,36 @@
class="w-full bg-surface-container-low rounded-full py-3 pl-12 pr-6 border-none focus:ring-2 focus:ring-primary/20">
<!-- Search Results Dropdown -->
<div *ngIf="searchResults().length > 0" class="absolute top-full left-0 right-0 mt-2 bg-surface shadow-xl rounded-2xl overflow-hidden z-10 border border-black/5">
<div *ngFor="let user of searchResults()"
(click)="addMember(user)"
class="flex items-center gap-3 p-3 hover:bg-surface-container-highest cursor-pointer transition-colors">
<img [src]="user.avatarUrl || 'assets/default-avatar.png'" class="w-10 h-10 rounded-full object-cover">
<div>
<p class="text-sm font-bold">{{ user.name }}</p>
<p class="text-xs text-on-surface-variant">{{ user.email }}</p>
</div>
<mat-icon class="ml-auto text-primary">person_add</mat-icon>
@if (searchResults().length > 0) {
<div class="absolute top-full left-0 right-0 mt-2 bg-surface shadow-xl rounded-2xl overflow-hidden z-10 border border-black/5">
@for (user of searchResults(); track user.id) {
<div (click)="addMember(user)"
class="flex items-center gap-3 p-3 hover:bg-surface-container-highest cursor-pointer transition-colors">
<img [src]="user.avatarUrl || 'assets/default-avatar.png'" class="w-10 h-10 rounded-full object-cover">
<div>
<p class="text-sm font-bold">{{ user.name }}</p>
<p class="text-xs text-on-surface-variant">{{ user.email }}</p>
</div>
<mat-icon class="ml-auto text-primary">person_add</mat-icon>
</div>
}
</div>
</div>
}
</div>
<!-- Selected Members -->
<div class="grid grid-cols-2 gap-3">
<div *ngFor="let member of selectedMembers()"
class="flex items-center justify-between p-3 bg-surface rounded-2xl border border-black/5">
<div class="flex items-center gap-3">
<img [src]="member.avatarUrl || 'assets/default-avatar.png'" class="w-10 h-10 rounded-full object-cover" alt="Member">
<p class="font-bold text-sm truncate max-w-[80px]">{{ member.name }}</p>
@for (member of selectedMembers(); track member.id) {
<div class="flex items-center justify-between p-3 bg-surface rounded-2xl border border-black/5">
<div class="flex items-center gap-3">
<img [src]="member.avatarUrl || 'assets/default-avatar.png'" class="w-10 h-10 rounded-full object-cover" alt="Member">
<p class="font-bold text-sm truncate max-w-[80px]">{{ member.name }}</p>
</div>
<button type="button" (click)="removeMember(member.id)" class="text-error hover:bg-error/10 p-1 rounded-full transition-colors">
<mat-icon class="text-lg">remove_circle_outline</mat-icon>
</button>
</div>
<button type="button" (click)="removeMember(member.id)" class="text-error hover:bg-error/10 p-1 rounded-full transition-colors">
<mat-icon class="text-lg">remove_circle_outline</mat-icon>
</button>
</div>
}
</div>
</section>
@@ -81,55 +81,62 @@
</div>
</div>
<div *ngIf="isLoading()" class="flex flex-col items-center justify-center py-12">
<div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin"></div>
<p class="mt-4 text-on-surface-variant text-sm font-medium">Scanning the arena...</p>
</div>
<div *ngIf="!isLoading()" class="space-y-4">
<!-- Public Bet Cards -->
<div *ngFor="let bet of publicBets()"
class="bg-surface p-6 rounded-2xl soft-shadow relative overflow-hidden border border-black/5 active:scale-[0.98] transition-transform cursor-pointer"
[routerLink]="['/challenge-detail', bet._id]">
<div class="flex justify-between items-start mb-6">
<div class="flex items-center gap-3">
<div class="p-2 bg-indigo-50 rounded-full">
<mat-icon class="text-primary">public</mat-icon>
</div>
<div>
<p class="text-[10px] font-bold uppercase tracking-widest text-on-surface-variant">Public Challenge</p>
<h4 class="font-bold">{{ bet.title }}</h4>
</div>
</div>
<div class="text-right">
<span class="text-xs text-on-surface-variant block mb-1">Stake</span>
<span class="font-headline font-bold text-primary">€{{ bet.stake }}</span>
</div>
</div>
<p class="text-sm text-on-surface-variant mb-6 line-clamp-2">{{ bet.description || 'Join this public bet and prove your skills!' }}</p>
<div class="flex items-center justify-between border-t border-surface-container pt-4">
<div class="flex items-center gap-2">
<div class="flex -space-x-1">
<img *ngFor="let p of bet.participants.slice(0, 3)" [src]="'https://picsum.photos/seed/' + p.user + '/50/50'" class="w-6 h-6 rounded-full ring-2 ring-surface" alt="User">
</div>
<span class="text-xs text-on-surface-variant font-medium">{{ bet.participants.length }} Participants</span>
</div>
<div class="flex gap-4">
<mat-icon class="text-on-surface-variant text-xl">share</mat-icon>
<mat-icon class="text-on-surface-variant text-xl">bookmark_border</mat-icon>
</div>
</div>
@if (isLoading()) {
<div class="flex flex-col items-center justify-center py-12">
<div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin"></div>
<p class="mt-4 text-on-surface-variant text-sm font-medium">Scanning the arena...</p>
</div>
} @else {
<div class="space-y-4">
<!-- Public Bet Cards -->
@for (bet of publicBets(); track bet.id) {
<div class="bg-surface p-6 rounded-2xl soft-shadow relative overflow-hidden border border-black/5 active:scale-[0.98] transition-transform cursor-pointer"
[routerLink]="['/challenge-detail', bet.id]">
<div class="flex justify-between items-start mb-6">
<div class="flex items-center gap-3">
<div class="p-2 bg-indigo-50 rounded-full">
<mat-icon class="text-primary">public</mat-icon>
</div>
<div>
<p class="text-[10px] font-bold uppercase tracking-widest text-on-surface-variant">Public Challenge</p>
<h4 class="font-bold">{{ bet.title }}</h4>
</div>
</div>
<div class="text-right">
<span class="text-xs text-on-surface-variant block mb-1">Stake</span>
<span class="font-headline font-bold text-primary">€{{ bet.stake }}</span>
</div>
</div>
<div *ngIf="publicBets().length === 0" class="flex flex-col items-center justify-center py-20 text-center bg-surface-container-low rounded-3xl border border-dashed border-on-surface-variant/20">
<mat-icon class="text-6xl text-on-surface-variant/10 mb-4">public_off</mat-icon>
<h3 class="text-xl font-headline font-bold">No Public Bets</h3>
<p class="text-on-surface-variant mt-2">The arena is quiet right now. Why not create a public bet?</p>
<button class="mt-6 bg-primary text-white px-8 py-3 rounded-full font-bold" [routerLink]="['/create-bet']">Create Public Bet</button>
<p class="text-sm text-on-surface-variant mb-6 line-clamp-2">{{ bet.description || 'Join this public bet and prove your skills!' }}</p>
<div class="flex items-center justify-between border-t border-surface-container pt-4">
<div class="flex items-center gap-2">
<div class="flex -space-x-1">
@for (p of bet.participants.slice(0, 3); track $index) {
<img [src]="'https://picsum.photos/seed/' + p.user + '/50/50'" class="w-6 h-6 rounded-full ring-2 ring-surface" alt="User">
}
</div>
<span class="text-xs text-on-surface-variant font-medium">{{ bet.participants.length }} Participants</span>
</div>
<div class="flex gap-4">
<mat-icon class="text-on-surface-variant text-xl">share</mat-icon>
<mat-icon class="text-on-surface-variant text-xl">bookmark_border</mat-icon>
</div>
</div>
</div>
}
@if (publicBets().length === 0) {
<div class="flex flex-col items-center justify-center py-20 text-center bg-surface-container-low rounded-3xl border border-dashed border-on-surface-variant/20">
<mat-icon class="text-6xl text-on-surface-variant/10 mb-4">public_off</mat-icon>
<h3 class="text-xl font-headline font-bold">No Public Bets</h3>
<p class="text-on-surface-variant mt-2">The arena is quiet right now. Why not create a public bet?</p>
<button class="mt-6 bg-primary text-white px-8 py-3 rounded-full font-bold" [routerLink]="['/create-bet']">Create Public Bet</button>
</div>
}
</div>
</div>
}
</section>
</main>
</div>
@@ -30,17 +30,25 @@
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
<div class="absolute bottom-4 left-4 right-4 flex justify-between items-end">
<div>
<span *ngIf="data.isPrivate" class="inline-flex items-center gap-1 bg-tertiary-container text-tertiary px-3 py-1 rounded-full text-[10px] font-bold tracking-wider mb-2">
<mat-icon class="text-[12px]">lock</mat-icon>
PRIVATE
</span>
@if (data.isPrivate) {
<span class="inline-flex items-center gap-1 bg-tertiary-container text-tertiary px-3 py-1 rounded-full text-[10px] font-bold tracking-wider mb-2">
<mat-icon class="text-[12px]">lock</mat-icon>
PRIVATE
</span>
}
<h2 class="text-white font-headline font-extrabold text-2xl tracking-tight">{{ data.name }}</h2>
<p class="text-white/80 text-sm font-medium">{{ data.members?.length || 0 }} Members</p>
</div>
<div class="flex -space-x-3" *ngIf="data.members && data.members.length > 0">
<img *ngFor="let member of data.members.slice(0, 3)" [src]="member.avatarUrl || 'assets/default-avatar.png'" class="w-8 h-8 rounded-full border-2 border-surface object-cover" [alt]="member.name">
<div *ngIf="data.members.length > 3" class="w-8 h-8 rounded-full bg-primary text-white flex items-center justify-center text-[10px] font-bold border-2 border-surface">+{{ data.members.length - 3 }}</div>
</div>
@if (data.members && data.members.length > 0) {
<div class="flex -space-x-3">
@for (member of data.members.slice(0, 3); track $index) {
<img [src]="member.avatarUrl || 'assets/default-avatar.png'" class="w-8 h-8 rounded-full border-2 border-surface object-cover" [alt]="member.name">
}
@if (data.members.length > 3) {
<div class="w-8 h-8 rounded-full bg-primary text-white flex items-center justify-center text-[10px] font-bold border-2 border-surface">+{{ data.members.length - 3 }}</div>
}
</div>
}
</div>
</div>
</section>
@@ -58,16 +66,18 @@
<span class="text-primary font-medium text-sm">Manage</span>
</div>
<div class="grid grid-cols-1 gap-3">
<div *ngFor="let member of data.members" class="flex items-center justify-between p-3 bg-surface rounded-2xl border border-black/5">
<div class="flex items-center gap-3">
<img [src]="member.avatarUrl || 'assets/default-avatar.png'" class="w-10 h-10 rounded-full object-cover" alt="Member">
<div>
<p class="font-bold text-sm">{{ member.name }}</p>
<p class="text-[10px] text-on-surface-variant">Score: {{ member.score }}</p>
@for (member of data.members; track member.id) {
<div class="flex items-center justify-between p-3 bg-surface rounded-2xl border border-black/5">
<div class="flex items-center gap-3">
<img [src]="member.avatarUrl || 'assets/default-avatar.png'" class="w-10 h-10 rounded-full object-cover" alt="Member">
<div>
<p class="font-bold text-sm">{{ member.name }}</p>
<p class="text-[10px] text-on-surface-variant">Score: {{ member.score }}</p>
</div>
</div>
<mat-icon class="text-on-surface-variant/30 text-sm">chevron_right</mat-icon>
</div>
<mat-icon class="text-on-surface-variant/30 text-sm">chevron_right</mat-icon>
</div>
}
</div>
</section>
} @else {
@@ -25,38 +25,52 @@
</section>
<!-- Groups List -->
<div *ngIf="isLoading()" class="flex flex-col items-center justify-center py-12">
<div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin"></div>
<p class="mt-4 text-on-surface-variant text-sm font-medium">Loading your groups...</p>
</div>
@if (isLoading()) {
<div class="flex flex-col items-center justify-center py-12">
<div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin"></div>
<p class="mt-4 text-on-surface-variant text-sm font-medium">Loading your groups...</p>
</div>
} @else {
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
@for (group of groupsList(); track group.id) {
<div class="bg-surface p-6 rounded-2xl soft-shadow border border-black/5 flex flex-col gap-4 relative overflow-hidden group cursor-pointer" [routerLink]="['/group-detail', group.id]">
<div class="flex justify-between items-start z-10">
<div class="w-14 h-14 rounded-2xl overflow-hidden shadow-md bg-surface-container">
<img [src]="group.avatarUrl || 'https://picsum.photos/seed/' + group.id + '/200/200'" class="w-full h-full object-cover" alt="Group Icon">
</div>
@if (group.isPrivate) {
<span class="bg-tertiary-container text-tertiary px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider">Private</span>
}
</div>
<div class="space-y-1 z-10">
<h3 class="font-headline text-lg font-bold">{{ group.name }}</h3>
<p class="text-on-surface-variant text-sm line-clamp-2">{{ group.description || 'No description' }}</p>
</div>
<div class="flex items-center justify-between z-10 pt-2">
@if (group.members && group.members.length > 0) {
<div class="flex -space-x-3">
@for (member of group.members.slice(0, 3); track $index) {
<img [src]="member.avatarUrl || 'assets/default-avatar.png'" class="w-8 h-8 rounded-full border-2 border-surface" [alt]="member.name">
}
@if (group.members.length > 3) {
<div class="w-8 h-8 rounded-full border-2 border-surface bg-surface-container text-[10px] font-bold flex items-center justify-center text-on-surface-variant">+{{ group.members.length - 3 }}</div>
}
</div>
}
<span class="text-primary font-bold text-sm">{{ group.members?.length || 0 }} Members</span>
</div>
</div>
}
<div *ngIf="!isLoading()" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div *ngFor="let group of groupsList()" class="bg-surface p-6 rounded-2xl soft-shadow border border-black/5 flex flex-col gap-4 relative overflow-hidden group cursor-pointer" [routerLink]="['/group-detail', group.id]">
<div class="flex justify-between items-start z-10">
<div class="w-14 h-14 rounded-2xl overflow-hidden shadow-md bg-surface-container">
<img [src]="group.avatarUrl || 'https://picsum.photos/seed/' + group.id + '/200/200'" class="w-full h-full object-cover" alt="Group Icon">
@if (groupsList().length === 0) {
<div class="col-span-full py-12 text-center bg-surface-container-low rounded-3xl border border-dashed border-on-surface-variant/20">
<mat-icon class="text-4xl text-on-surface-variant/20 mb-2">groups</mat-icon>
<p class="text-on-surface-variant font-medium">You haven't joined any groups yet.</p>
<button class="mt-4 text-primary font-bold" [routerLink]="['/create-group']">Create your first group</button>
</div>
<span *ngIf="group.isPrivate" class="bg-tertiary-container text-tertiary px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider">Private</span>
</div>
<div class="space-y-1 z-10">
<h3 class="font-headline text-lg font-bold">{{ group.name }}</h3>
<p class="text-on-surface-variant text-sm line-clamp-2">{{ group.description || 'No description' }}</p>
</div>
<div class="flex items-center justify-between z-10 pt-2">
<div class="flex -space-x-3" *ngIf="group.members && group.members.length > 0">
<img *ngFor="let member of group.members.slice(0, 3)" [src]="member.avatarUrl || 'assets/default-avatar.png'" class="w-8 h-8 rounded-full border-2 border-surface" [alt]="member.name">
<div *ngIf="group.members.length > 3" class="w-8 h-8 rounded-full border-2 border-surface bg-surface-container text-[10px] font-bold flex items-center justify-center text-on-surface-variant">+{{ group.members.length - 3 }}</div>
</div>
<span class="text-primary font-bold text-sm">{{ group.members?.length || 0 }} Members</span>
</div>
}
</div>
<div *ngIf="groupsList().length === 0" class="col-span-full py-12 text-center bg-surface-container-low rounded-3xl border border-dashed border-on-surface-variant/20">
<mat-icon class="text-4xl text-on-surface-variant/20 mb-2">groups</mat-icon>
<p class="text-on-surface-variant font-medium">You haven't joined any groups yet.</p>
<button class="mt-4 text-primary font-bold" [routerLink]="['/create-group']">Create your first group</button>
</div>
</div>
}
<!-- Social Pulse -->
<div class="bg-surface-container-low p-4 rounded-2xl flex items-center gap-4 border border-black/5">
@@ -84,7 +84,7 @@ export class Home {
};
const prize = parseFloat(bet.stake.replace(/[^0-9.]/g, '')) || 0;
return {
id: bet._id,
id: bet.id,
title: bet.title,
description: bet.description ?? '',
target: 100, // Default target
@@ -2,8 +2,11 @@
<header class="bg-background sticky top-0 z-50 px-6 py-4 flex justify-between items-center shadow-sm">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full overflow-hidden bg-primary-container/20">
<img *ngIf="userProfile()?.avatarUrl" [src]="userProfile()?.avatarUrl" class="w-full h-full object-cover" alt="User Avatar">
<mat-icon *ngIf="!userProfile()?.avatarUrl" class="text-primary w-full h-full flex items-center justify-center">person</mat-icon>
@if (userProfile()?.avatarUrl) {
<img [src]="userProfile()?.avatarUrl" class="w-full h-full object-cover" alt="User Avatar">
} @else {
<mat-icon class="text-primary w-full h-full flex items-center justify-center">person</mat-icon>
}
</div>
<h1 class="font-headline font-black tracking-tight text-xl text-primary">{{ t('nav.profile') }}</h1>
</div>
@@ -49,12 +49,12 @@ export type BetStatus = 'ACTIVE' | 'AWAITING_VALIDATION' | 'COMPLETED' | 'EXPIRE
export type ParticipantRole = 'PARTICIPANT' | 'OBSERVER';
export interface BetParticipant {
user: string; // PostgreSQL User ID
user: User | string | number; // popolato dal backend con l'oggetto User
role: ParticipantRole;
}
export interface Bet {
_id: string;
id: string;
title: string;
description?: string;
stake: string;