reworked the guest pwa, modernized start and gallery page. added share link functionality.
This commit is contained in:
@@ -43,10 +43,6 @@ function formatRelativeTime(input: string): string {
|
||||
return `vor ${days} Tagen`;
|
||||
}
|
||||
|
||||
function badgeVariant(earned: boolean): string {
|
||||
return earned ? 'bg-emerald-500/10 text-emerald-500 border border-emerald-500/30' : 'bg-muted text-muted-foreground';
|
||||
}
|
||||
|
||||
function Leaderboard({ title, icon: Icon, entries, emptyCopy }: { title: string; icon: React.ElementType; entries: LeaderboardEntry[]; emptyCopy: string }) {
|
||||
return (
|
||||
<Card>
|
||||
@@ -83,6 +79,17 @@ function Leaderboard({ title, icon: Icon, entries, emptyCopy }: { title: string;
|
||||
);
|
||||
}
|
||||
|
||||
function progressMeta(badge: AchievementBadge) {
|
||||
const target = badge.target ?? 0;
|
||||
const progress = badge.progress ?? 0;
|
||||
const ratio = target > 0 ? Math.min(1, progress / target) : 0;
|
||||
return {
|
||||
progress,
|
||||
target,
|
||||
ratio: badge.earned ? 1 : ratio,
|
||||
};
|
||||
}
|
||||
|
||||
function BadgesGrid({ badges }: { badges: AchievementBadge[] }) {
|
||||
if (badges.length === 0) {
|
||||
return (
|
||||
@@ -104,21 +111,43 @@ function BadgesGrid({ badges }: { badges: AchievementBadge[] }) {
|
||||
<CardTitle>Badges</CardTitle>
|
||||
<CardDescription>Dein Fortschritt bei den verfügbaren Erfolgen.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{badges.map((badge) => (
|
||||
<div key={badge.id} className={cn('rounded-xl border px-4 py-3', badgeVariant(badge.earned))}>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div>
|
||||
<p className="text-sm font-semibold">{badge.title}</p>
|
||||
<p className="text-xs text-muted-foreground">{badge.description}</p>
|
||||
<CardContent className="grid grid-cols-2 gap-3 sm:grid-cols-3">
|
||||
{badges.map((badge) => {
|
||||
const { ratio } = progressMeta(badge);
|
||||
const percentage = Math.round(ratio * 100);
|
||||
return (
|
||||
<div
|
||||
key={badge.id}
|
||||
className={cn(
|
||||
'relative overflow-hidden rounded-2xl border px-4 py-3 shadow-sm transition',
|
||||
badge.earned
|
||||
? 'border-emerald-400/40 bg-gradient-to-br from-emerald-500/20 via-emerald-500/5 to-white text-emerald-900'
|
||||
: 'border-border/60 bg-white/80',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-foreground">{badge.title}</p>
|
||||
<p className="text-xs text-muted-foreground line-clamp-2">{badge.description}</p>
|
||||
</div>
|
||||
<span className="rounded-full bg-white/30 p-2 text-pink-500">
|
||||
<Award className="h-4 w-4" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<div className="h-2 w-full overflow-hidden rounded-full bg-muted/40">
|
||||
<div
|
||||
className={cn('h-2 rounded-full transition-all', badge.earned ? 'bg-emerald-500' : 'bg-pink-500')}
|
||||
style={{ width: `${Math.max(8, percentage)}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{badge.earned ? 'Freigeschaltet 🎉' : `Fortschritt: ${badge.progress}/${badge.target}`}
|
||||
</p>
|
||||
</div>
|
||||
<Award className="h-5 w-5" />
|
||||
</div>
|
||||
<div className="mt-2 text-xs text-muted-foreground">
|
||||
{badge.earned ? 'Abgeschlossen' : `Fortschritt: ${badge.progress}/${badge.target}`}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user