Sistemato un po' il codice
This commit is contained in:
@@ -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>
|
||||
|
||||
<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>
|
||||
@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>
|
||||
<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 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>
|
||||
<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>
|
||||
@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>
|
||||
}
|
||||
</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>
|
||||
|
||||
<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">
|
||||
</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>
|
||||
@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="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>
|
||||
@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>
|
||||
}
|
||||
</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;
|
||||
|
||||
Reference in New Issue
Block a user