From a678a3a74885064298cc2eafa46b16772ac7dce6 Mon Sep 17 00:00:00 2001 From: lucasnevespereira Date: Sun, 10 Aug 2025 19:41:06 +0200 Subject: [PATCH] feat(leaderboard): cleaned date related and unused code --- locales/en.ts | 1 + locales/es.ts | 1 + locales/fr.ts | 1 + locales/pt.ts | 1 + locales/ru.ts | 1 + locales/zh-CN.ts | 1 + .../actions/get-top-workout-users.action.ts | 12 +-- .../hooks/use-top-workout-users.ts | 5 +- src/features/leaderboard/models/types.ts | 1 - .../leaderboard/ui/leaderboard-item.tsx | 76 +++++++------------ src/shared/lib/date.ts | 11 ++- 11 files changed, 49 insertions(+), 62 deletions(-) diff --git a/locales/en.ts b/locales/en.ts index 3784d58..f8c62ae 100644 --- a/locales/en.ts +++ b/locales/en.ts @@ -1681,6 +1681,7 @@ export default { }, }, commons: { + just_now: "just now", signup_with: "Sign up with {provider}", signin_with: "Sign in with {provider}", signup: "Sign up", diff --git a/locales/es.ts b/locales/es.ts index b49d711..045e6a5 100644 --- a/locales/es.ts +++ b/locales/es.ts @@ -818,6 +818,7 @@ export default { }, }, commons: { + just_now: "ahora mismo", signup_with: "Registrarse con {provider}", signin_with: "Iniciar sesión con {provider}", signup: "Registrarse", diff --git a/locales/fr.ts b/locales/fr.ts index 9b5053a..9f513f4 100644 --- a/locales/fr.ts +++ b/locales/fr.ts @@ -1707,6 +1707,7 @@ export default { }, }, commons: { + just_now: "à l'instant", signup_with: "S'inscrire avec {provider}", signin_with: "Se connecter avec {provider}", signup: "S'inscrire", diff --git a/locales/pt.ts b/locales/pt.ts index 38869cf..2ea84a6 100644 --- a/locales/pt.ts +++ b/locales/pt.ts @@ -1691,6 +1691,7 @@ export default { }, commons: { + just_now: "agora mesmo", signup_with: "Inscrever com {provider}", signin_with: "Entrar com {provider}", signup: "Inscrever-se", diff --git a/locales/ru.ts b/locales/ru.ts index 76368f2..383bf5a 100644 --- a/locales/ru.ts +++ b/locales/ru.ts @@ -1682,6 +1682,7 @@ export default { }, }, commons: { + just_now: "только что", signup_with: "Регистрация через {provider}", signin_with: "Вход через {provider}", signup: "Регистрация", diff --git a/locales/zh-CN.ts b/locales/zh-CN.ts index ec7d27e..0188987 100644 --- a/locales/zh-CN.ts +++ b/locales/zh-CN.ts @@ -813,6 +813,7 @@ export default { }, }, commons: { + just_now: "刚刚", signup_with: "使用 {provider} 注册", signin_with: "使用 {provider} 登录", signup: "注册", diff --git a/src/features/leaderboard/actions/get-top-workout-users.action.ts b/src/features/leaderboard/actions/get-top-workout-users.action.ts index 3440087..ee8b5a9 100644 --- a/src/features/leaderboard/actions/get-top-workout-users.action.ts +++ b/src/features/leaderboard/actions/get-top-workout-users.action.ts @@ -1,5 +1,6 @@ "use server"; +import dayjs from "dayjs"; import { prisma } from "@/shared/lib/prisma"; import { actionClient } from "@/shared/api/safe-actions"; @@ -47,22 +48,17 @@ export const getTopWorkoutUsersAction = actionClient.action(async () => { const users: TopWorkoutUser[] = topUsers.map((user) => { const totalWorkouts = user._count.WorkoutSession; - const firstWorkout = user.WorkoutSession[0]; const lastWorkout = user.WorkoutSession[0]; const lastWorkoutAt = lastWorkout?.endedAt || lastWorkout?.startedAt || null; - // Calculate weeks since first workout or account creation - const startDate = firstWorkout?.startedAt || user.createdAt; - const now = new Date(); - const weeksSinceStart = Math.max(1, Math.ceil((now.getTime() - startDate.getTime()) / (7 * 24 * 60 * 60 * 1000))); + const startDate = user.createdAt; + const weeksSinceStart = Math.max(1, Math.ceil(dayjs().diff(dayjs(startDate), "week", true))); - // Calculate average workouts per week - const averageWorkoutsPerWeek = Math.round((totalWorkouts / weeksSinceStart) * 10) / 10; // Round to 1 decimal + const averageWorkoutsPerWeek = Math.round((totalWorkouts / weeksSinceStart) * 10) / 10; return { userId: user.id, userName: user.name, - userEmail: user.email, userImage: user.image, totalWorkouts, lastWorkoutAt: lastWorkoutAt, diff --git a/src/features/leaderboard/hooks/use-top-workout-users.ts b/src/features/leaderboard/hooks/use-top-workout-users.ts index 4b123d0..b18972f 100644 --- a/src/features/leaderboard/hooks/use-top-workout-users.ts +++ b/src/features/leaderboard/hooks/use-top-workout-users.ts @@ -5,15 +5,14 @@ import { useQuery } from "@tanstack/react-query"; import { getTopWorkoutUsersAction } from "../actions/get-top-workout-users.action"; export interface UseTopWorkoutUsersOptions { - limit?: number; refetchInterval?: number; } export function useTopWorkoutUsers(options: UseTopWorkoutUsersOptions = {}) { - const { limit = 20, refetchInterval } = options; + const { refetchInterval } = options; return useQuery({ - queryKey: ["top-workout-users", limit], + queryKey: ["top-workout-users"], queryFn: async () => { const result = await getTopWorkoutUsersAction(); return result?.data || []; diff --git a/src/features/leaderboard/models/types.ts b/src/features/leaderboard/models/types.ts index 510c98c..ac56f93 100644 --- a/src/features/leaderboard/models/types.ts +++ b/src/features/leaderboard/models/types.ts @@ -1,7 +1,6 @@ export interface TopWorkoutUser { userId: string; userName: string; - userEmail: string; userImage: string | null; totalWorkouts: number; lastWorkoutAt: Date | null; diff --git a/src/features/leaderboard/ui/leaderboard-item.tsx b/src/features/leaderboard/ui/leaderboard-item.tsx index 28579e8..a95c792 100644 --- a/src/features/leaderboard/ui/leaderboard-item.tsx +++ b/src/features/leaderboard/ui/leaderboard-item.tsx @@ -2,8 +2,7 @@ import React from "react"; import Image from "next/image"; -import { Trophy, Medal, Award } from "lucide-react"; - +import {Trophy, Medal, Award} from "lucide-react" import { useCurrentLocale, useI18n } from "locales/client"; import { formatDateShort, formatRelativeTime } from "@/shared/lib/date"; @@ -12,55 +11,38 @@ import { Badge } from "@/components/ui/badge"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -const getRankIcon = (rank: number) => { - switch (rank) { - case 1: - return ; - case 2: - return ; - case 3: - return ; - default: - return #{rank}; - } -}; - -const getRankBadge = (rank: number, t: any) => { - switch (rank) { - case 1: - return {t("leaderboard.champion_badge")}; - case 2: - return {t("leaderboard.runner_up_badge")}; - case 3: - return {t("leaderboard.third_place_badge")}; - default: - return null; - } -}; - - const LeaderboardItem: React.FC<{ user: TopWorkoutUser; rank: number }> = ({ user, rank }) => { const t = useI18n(); const locale = useCurrentLocale(); + const dicebearUrl = `https://api.dicebear.com/7.x/micah/svg?seed=${encodeURIComponent(user.userId)}&backgroundColor=b6e3f4,c0aede,d1d4f9,ffd5dc,ffdfbf`; - const generateAvatarUrl = (seed: string) => { - return `https://api.dicebear.com/7.x/micah/svg?seed=${encodeURIComponent(seed)}&backgroundColor=b6e3f4,c0aede,d1d4f9,ffd5dc,ffdfbf`; - }; + const getRankIcon = (rank: number) => { + switch (rank) { + case 1: + return ; + case 2: + return ; + case 3: + return ; + default: + return #{rank}; + } + }; - // Use userId as seed for consistency, fallback to userName or email - const avatarSeed = user.userId || user.userName || user.userEmail; - const dicebearUrl = generateAvatarUrl(avatarSeed); + const getRankBadge = (rank: number) => { + switch (rank) { + case 1: + return {t("leaderboard.champion_badge")}; + case 2: + return {t("leaderboard.runner_up_badge")}; + case 3: + return {t("leaderboard.third_place_badge")}; + default: + return null; + } + }; - const formatMemberSince = (date: Date) => { - return formatDateShort(date, locale); - }; - - const formatLastWorkout = (date: Date | null) => { - if (!date) return null - return formatRelativeTime(date, locale); - }; - return (
{/* Rank */} @@ -94,15 +76,15 @@ const LeaderboardItem: React.FC<{ user: TopWorkoutUser; rank: number }> = ({ use

{user.userName}

- {rank <= 3 && getRankBadge(rank, t)} + {rank <= 3 && getRankBadge(rank)}

- {t("leaderboard.member_since")} {formatMemberSince(user.memberSince)} + {t("leaderboard.member_since")} {formatDateShort(user.memberSince, locale)}

{user.lastWorkoutAt && (

- {t("leaderboard.last_workout")} {formatLastWorkout(user.lastWorkoutAt)} + {t("leaderboard.last_workout")} {formatRelativeTime(user.lastWorkoutAt, locale, t("commons.just_now"))}

)}
diff --git a/src/shared/lib/date.ts b/src/shared/lib/date.ts index 74e1475..129e987 100644 --- a/src/shared/lib/date.ts +++ b/src/shared/lib/date.ts @@ -64,21 +64,26 @@ export const formatDateShort = (date: string | Date, locale: string = "en"): str /** * Format relative time (e.g., "2 hours ago", "3 days ago") */ -export const formatRelativeTime = (date: string | Date | null, locale: string = "en"): string | null => { +export const formatRelativeTime = ( + date: string | Date | null, + locale: string = "en", + justNowText: string = "just now" +): string | null => { if (!date) return null; const target = dayjs(date).locale(locale); const now = dayjs().locale(locale); + // Safety check: if date is in the future, treat as "just now" if (target.isAfter(now)) { console.warn("date is in the future:", target.format(), "treating as \"just now\""); - return "just now"; + return justNowText; } // If less than 1 minute ago, show "just now" instead of "in a few seconds" if (now.diff(target, "minute") < 1) { - return "just now"; + return justNowText; } return target.fromNow();