Skip to content

Commit b8847a5

Browse files
committed
Walk calendar (#27)
* Added walk calendar * Added Walk Activity heatmap calendar * Replace old cards with the new Card component * Fix tooltip on mobile * Fix unverified user card
1 parent 7531bc1 commit b8847a5

File tree

11 files changed

+717
-133
lines changed

11 files changed

+717
-133
lines changed

app/[locale]/globals.css

+3-2
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@
7575
@layer base {
7676
* {
7777
@apply border-border;
78-
min-width: 0
78+
min-width: 0;
79+
box-sizing: border-box;
7980
}
8081
*:focus {
8182
outline: none;
@@ -88,7 +89,7 @@
8889
@layer utilities {
8990
.scrollbar::-webkit-scrollbar {
9091
width: 8px;
91-
height: 12px;
92+
height: 8px;
9293
}
9394

9495
.scrollbar::-webkit-scrollbar-track {

app/[locale]/profile/[username]/_components/LastWalks.tsx

+112-67
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@ import { ChevronRightIcon } from "@radix-ui/react-icons";
22
import { cookies } from "next/headers";
33
import Link from "next/link";
44
import { useTranslations } from "next-intl";
5+
import { getTranslations } from "next-intl/server";
56

67
import { EnduranceIcon, SpeedlineIcon } from "@/assets";
78
import { Button } from "@/components/ui/button";
9+
import {
10+
Card,
11+
CardContent,
12+
CardDescription,
13+
CardHeader,
14+
CardTitle,
15+
} from "@/components/ui/card";
816
import {
917
Popover,
1018
PopoverContent,
@@ -15,103 +23,140 @@ import type { Database } from "@/utils/supabase/database.types";
1523
import { useSupabaseServer } from "@/utils/supabase/server";
1624

1725
import FormattedDate from "./FormattedDate";
26+
import { Heatmap, YearSwitcher } from "./WalkActivityCalendar";
1827

1928
export const dynamic = "force-dynamic";
29+
type Entry =
30+
| Database["public"]["Tables"]["entry"]["Row"] & {
31+
highline: Database["public"]["Tables"]["highline"]["Row"] | null;
32+
};
2033

2134
interface Props {
2235
username: string;
36+
year?: string;
2337
}
2438

25-
export default async function LastWalks({ username }: Props) {
39+
export default async function LastWalks({ username, year }: Props) {
2640
const cookieStore = cookies();
2741
const supabase = useSupabaseServer(cookieStore);
42+
const t = await getTranslations("profile.lastWalks");
2843

2944
const { data: entries } = await supabase
3045
.from("entry")
3146
.select(
3247
`
33-
*,
34-
highline (*)
35-
`
48+
*,
49+
highline (*)
50+
`
3651
)
3752
.eq("instagram", `@${username}`)
38-
.limit(5)
3953
.order("created_at", { ascending: false });
4054

41-
return <LastWalksContent entries={entries || []} />;
55+
if (!entries) return null;
56+
57+
function groupByDay(entries: Entry[]) {
58+
const groupedByDay: Record<string, number> = {};
59+
const years: Array<string> = [];
60+
61+
entries.forEach((entry) => {
62+
const day = entry.created_at.split("T")[0];
63+
const year = day.split("-")[0];
64+
65+
groupedByDay[day] = groupedByDay[day] + 1 || 1;
66+
if (years[years.length - 1] !== year) {
67+
years.push(year);
68+
}
69+
});
70+
71+
return {
72+
years: years,
73+
data: groupedByDay,
74+
};
75+
}
76+
77+
const { data, years } = groupByDay(entries);
78+
const selectedYear = year && years.includes(year) ? year : years[0];
79+
80+
return (
81+
<Card>
82+
<CardContent>
83+
<CardHeader className="flex-row justify-between space-y-0 p-0 pt-6">
84+
<div className="flex-1">
85+
<CardTitle>{t("title")}</CardTitle>
86+
<CardDescription>{t("description")}</CardDescription>
87+
</div>
88+
<YearSwitcher years={years} selectedYear={selectedYear} />
89+
</CardHeader>
90+
91+
<Heatmap year={parseInt(selectedYear)} data={data} />
92+
<LastWalksContent entries={entries.slice(0, 5)} />
93+
</CardContent>
94+
</Card>
95+
);
4296
}
4397

4498
interface ContentProps {
45-
entries:
46-
| (Database["public"]["Tables"]["entry"]["Row"] & {
47-
highline: Database["public"]["Tables"]["highline"]["Row"] | null;
48-
})[];
99+
entries: Entry[];
49100
}
50101
function LastWalksContent({ entries }: ContentProps) {
51102
const t = useTranslations("profile.lastWalks");
52103

53104
return (
54-
<div className="max-w-screen-md rounded-xl border border-gray-200 bg-white px-2 py-4 shadow dark:divide-gray-700 dark:border-gray-700 dark:bg-gray-800">
55-
<h3 className="text-xl font-semibold dark:text-white">{t("title")}</h3>
56-
<ul className="mt-4 divide-y divide-gray-200 dark:divide-gray-700">
57-
{entries.length > 0 ? (
58-
entries.map((entry) => (
59-
<li
60-
key={entry.id}
61-
className="overflow-hidden py-4 first:pt-0 last:pb-0"
62-
>
63-
<Popover>
64-
<PopoverTrigger className="mb-0.5 truncate text-base font-semibold leading-none text-blue-500 dark:text-blue-400">
65-
{entry.highline?.name}
66-
</PopoverTrigger>
67-
<PopoverContent
68-
side="top"
69-
className="max-h-96 overflow-auto p-0"
70-
>
71-
<div className="space-y-2 p-4">
72-
<p>{entry.highline?.description}</p>
73-
<div>
74-
<p>
75-
{t("popover.height")}: {entry.highline?.height}m
76-
</p>
77-
<p>
78-
{t("popover.length")}: {entry.highline?.lenght}m
79-
</p>
80-
</div>
81-
<Button className="w-full" variant={"outline"} asChild>
82-
<Link href={`/${entry.highline?.id}`}>
83-
{t("popover.buttonLabel")}{" "}
84-
<ChevronRightIcon
85-
className="ml-1.5 h-3 w-3"
86-
strokeWidth={2}
87-
stroke="currentColor"
88-
/>
89-
</Link>
90-
</Button>
91-
</div>
92-
</PopoverContent>
93-
</Popover>
94-
<FormattedDate date={new Date(entry.created_at)} />
95-
<div className="text-white-700 mb-1 flex gap-4 truncate text-sm font-normal dark:text-white">
96-
{entry.crossing_time && (
97-
<div className="flex items-end gap-1">
98-
<SpeedlineIcon className="text-gray-900 dark:text-white" />
99-
<p>{transformSecondsToTimeString(entry.crossing_time)}</p>
105+
<ul className="mt-4 divide-y divide-gray-200 dark:divide-gray-700">
106+
{entries.length > 0 ? (
107+
entries.map((entry) => (
108+
<li
109+
key={entry.id}
110+
className="overflow-hidden py-4 first:pt-0 last:pb-0"
111+
>
112+
<Popover>
113+
<PopoverTrigger className="mb-0.5 truncate text-base font-semibold leading-none text-blue-500 dark:text-blue-400">
114+
{entry.highline?.name}
115+
</PopoverTrigger>
116+
<PopoverContent side="top" className="max-h-96 overflow-auto p-0">
117+
<div className="space-y-2 p-4">
118+
<p>{entry.highline?.description}</p>
119+
<div>
120+
<p>
121+
{t("popover.height")}: {entry.highline?.height}m
122+
</p>
123+
<p>
124+
{t("popover.length")}: {entry.highline?.lenght}m
125+
</p>
100126
</div>
101-
)}
127+
<Button className="w-full" variant={"outline"} asChild>
128+
<Link href={`/${entry.highline?.id}`}>
129+
{t("popover.buttonLabel")}{" "}
130+
<ChevronRightIcon
131+
className="ml-1.5 h-3 w-3"
132+
strokeWidth={2}
133+
stroke="currentColor"
134+
/>
135+
</Link>
136+
</Button>
137+
</div>
138+
</PopoverContent>
139+
</Popover>
140+
<FormattedDate date={new Date(entry.created_at)} />
141+
<div className="text-white-700 mb-1 flex gap-4 truncate text-sm font-normal dark:text-white">
142+
{entry.crossing_time && (
102143
<div className="flex items-end gap-1">
103-
<EnduranceIcon className="text-gray-900 dark:text-white" />
104-
<p>{entry.distance_walked}m</p>
144+
<SpeedlineIcon className="text-gray-900 dark:text-white" />
145+
<p>{transformSecondsToTimeString(entry.crossing_time)}</p>
105146
</div>
147+
)}
148+
<div className="flex items-end gap-1">
149+
<EnduranceIcon className="text-gray-900 dark:text-white" />
150+
<p>{entry.distance_walked}m</p>
106151
</div>
107-
<p>{entry.comment}</p>
108-
</li>
109-
))
110-
) : (
111-
<p>{t("empty")}</p>
112-
)}
113-
</ul>
114-
</div>
152+
</div>
153+
<p>{entry.comment}</p>
154+
</li>
155+
))
156+
) : (
157+
<p>{t("empty")}</p>
158+
)}
159+
</ul>
115160
);
116161
}
117162

app/[locale]/profile/[username]/_components/Stats.tsx

+35-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useTranslations } from "next-intl";
22

3+
import { Card, CardContent } from "@/components/ui/card";
4+
35
interface Props {
46
total_distance_walked: number;
57
total_cadenas: number;
@@ -14,37 +16,39 @@ function Stats({
1416
const displayDistanceInKM = total_distance_walked > 10000;
1517

1618
return (
17-
<dl className="grid max-w-screen-md grid-cols-3 gap-3 divide-x divide-gray-200 rounded-xl border border-gray-200 bg-white px-2 py-4 shadow dark:divide-gray-700 dark:border-gray-700 dark:bg-gray-800 sm:mx-auto sm:gap-8">
18-
<div className="flex flex-col items-center justify-center">
19-
<dt className="mb-2 text-3xl font-extrabold md:text-4xl">
20-
{displayDistanceInKM
21-
? total_distance_walked / 1000
22-
: total_distance_walked}
23-
<span className="text-xl font-extrabold text-gray-500 dark:text-gray-400 md:text-2xl">
24-
{displayDistanceInKM ? "km" : "m"}
25-
</span>
26-
</dt>
27-
<dd className="font-light text-gray-500 dark:text-gray-400">
28-
{t("walked")}
29-
</dd>
30-
</div>
31-
<div className="flex flex-col items-center justify-center">
32-
<dt className="mb-2 text-3xl font-extrabold md:text-4xl">
33-
{total_cadenas}
34-
</dt>
35-
<dd className="font-light text-gray-500 dark:text-gray-400">
36-
{t("sent")}
37-
</dd>
38-
</div>
39-
<div className="flex flex-col items-center justify-center">
40-
<dt className="mb-2 text-3xl font-extrabold md:text-4xl">
41-
{total_full_lines}
42-
</dt>
43-
<dd className="font-light text-gray-500 dark:text-gray-400">
44-
{t("fullLines")}
45-
</dd>
46-
</div>
47-
</dl>
19+
<Card>
20+
<CardContent className="grid grid-cols-3 gap-3 divide-x divide-gray-200 px-2 py-4 sm:gap-8">
21+
<div className="flex flex-col items-center justify-center">
22+
<dt className="mb-2 text-3xl font-extrabold md:text-4xl">
23+
{displayDistanceInKM
24+
? total_distance_walked / 1000
25+
: total_distance_walked}
26+
<span className="text-xl font-extrabold text-gray-500 dark:text-gray-400 md:text-2xl">
27+
{displayDistanceInKM ? "km" : "m"}
28+
</span>
29+
</dt>
30+
<dd className="font-light text-gray-500 dark:text-gray-400">
31+
{t("walked")}
32+
</dd>
33+
</div>
34+
<div className="flex flex-col items-center justify-center">
35+
<dt className="mb-2 text-3xl font-extrabold md:text-4xl">
36+
{total_cadenas}
37+
</dt>
38+
<dd className="font-light text-gray-500 dark:text-gray-400">
39+
{t("sent")}
40+
</dd>
41+
</div>
42+
<div className="flex flex-col items-center justify-center">
43+
<dt className="mb-2 text-3xl font-extrabold md:text-4xl">
44+
{total_full_lines}
45+
</dt>
46+
<dd className="font-light text-gray-500 dark:text-gray-400">
47+
{t("fullLines")}
48+
</dd>
49+
</div>
50+
</CardContent>
51+
</Card>
4852
);
4953
}
5054

0 commit comments

Comments
 (0)