Skip to content

Commit 0522c49

Browse files
committed
feat(cache): cache list user & profile
1 parent 33b0a86 commit 0522c49

File tree

13 files changed

+202
-122
lines changed

13 files changed

+202
-122
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,6 @@ yarn-error.log*
3939
# typescript
4040
*.tsbuildinfo
4141
next-env.d.ts
42+
43+
# tmux
44+
tmux*

app/[locale]/(private)/profile/actions.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,29 @@
22

33
import { handleErrorServerWithAuth } from "@/utils/handleErrorServer";
44
import { prisma } from "@/configs/prisma/db";
5-
import { revalidatePath } from "next/cache";
5+
import { revalidateTag, unstable_cache } from "next/cache";
66

77
const getProfile = async () =>
88
handleErrorServerWithAuth({
9-
cb: async ({ user }) => {
10-
const nickname = await prisma.nickname.findUnique({
11-
where: {
12-
authorId: user!.id
13-
}
14-
});
15-
16-
return {
17-
...user,
18-
nickname: nickname?.content
19-
};
20-
}
9+
cb: ({ user }) =>
10+
unstable_cache(
11+
async () => {
12+
const nickname = await prisma.nickname.findUnique({
13+
where: {
14+
authorId: user!.id
15+
}
16+
});
17+
18+
console.info("[actions.ts:18] ", "refetch user profile", user!.email);
19+
20+
return {
21+
...user,
22+
nickname: nickname?.content
23+
};
24+
},
25+
["profile", user!.id],
26+
{ tags: [`profile::${user!.id}`] }
27+
)()
2128
});
2229

2330
const updateNickname = async (nickname: string) =>
@@ -44,7 +51,8 @@ const updateNickname = async (nickname: string) =>
4451
}
4552
});
4653

47-
revalidatePath("/[locale]", "page");
54+
revalidateTag("nicknames");
55+
revalidateTag(`profile::${user!.id}`);
4856
return updatedNickname;
4957
}
5058
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
2+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
3+
import { Label } from "@/components/ui/label";
4+
import { User } from "@supabase/supabase-js";
5+
import { getTranslations } from "next-intl/server";
6+
7+
const Profile = async ({ user }: { user: User }) => {
8+
const t = await getTranslations("profile");
9+
10+
return (
11+
<Card>
12+
<CardHeader>
13+
<CardTitle className='uppercase font-bold text-xl'>{t("title")}</CardTitle>
14+
<CardDescription className='text-muted-foreground'>{t("description")}</CardDescription>
15+
</CardHeader>
16+
<CardContent className='space-y-4'>
17+
<div className='flex items-center space-x-4'>
18+
<Avatar className='w-20 h-20'>
19+
<AvatarImage src={user?.user_metadata.avatar_url} alt={user?.user_metadata.full_name} />
20+
<AvatarFallback>{user?.user_metadata.full_name?.charAt(0)}</AvatarFallback>
21+
</Avatar>
22+
<div>
23+
<h2 className='text-2xl font-bold'>{user?.user_metadata.full_name}</h2>
24+
<p className='text-muted-foreground'>{user?.email}</p>
25+
</div>
26+
</div>
27+
</CardContent>
28+
<CardFooter className='flex justify-center'>
29+
<Label className='uppercase font-bold text-center'>
30+
{t("loginWith")} {user?.app_metadata.provider}
31+
</Label>
32+
</CardFooter>
33+
</Card>
34+
);
35+
};
36+
37+
export { Profile };

app/[locale]/(private)/profile/client/FormClient.tsx renamed to app/[locale]/(private)/profile/form.client.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
44
import { Input } from "@/components/ui/input";
55
import { useHandleError } from "@/hooks/useHandleError";
66
import { useState } from "react";
7-
import { updateNickname } from "../actions";
7+
import { updateNickname } from "./actions";
88
import { useTranslations } from "next-intl";
99

1010
type FormClientType = { nickname: string };
Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
2-
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
3-
import { FormClient } from "./client/FormClient";
4-
import { Label } from "@/components/ui/label";
1+
import { FormClient } from "./form.client";
52
import { User as SupabaseUser } from "@supabase/supabase-js";
63
import { getProfile } from "./actions";
74
import { getTranslations } from "next-intl/server";
85
import { Metadata } from "next";
6+
import { Suspense } from "react";
7+
import { LoadingComponent } from "@/components/custom/Loading";
8+
import { Profile } from "./dynamic";
99

1010
export const dynamic = "force-dynamic";
1111

@@ -22,7 +22,6 @@ export async function generateMetadata(): Promise<Metadata> {
2222

2323
export default async function Page() {
2424
const { data, error } = await getProfile();
25-
const t = await getTranslations("profile");
2625

2726
if (error) {
2827
throw new Error(error.message);
@@ -31,30 +30,12 @@ export default async function Page() {
3130

3231
return (
3332
<section className='flex flex-col space-y-2 w-[400px]'>
34-
<FormClient nickname={user?.nickname || ""} />
35-
<Card>
36-
<CardHeader>
37-
<CardTitle className='uppercase font-bold text-xl'>{t("title")}</CardTitle>
38-
<CardDescription className='text-muted-foreground'>{t("description")}</CardDescription>
39-
</CardHeader>
40-
<CardContent className='space-y-4'>
41-
<div className='flex items-center space-x-4'>
42-
<Avatar className='w-20 h-20'>
43-
<AvatarImage src={user?.user_metadata.avatar_url} alt={user?.user_metadata.full_name} />
44-
<AvatarFallback>{user?.user_metadata.full_name?.charAt(0)}</AvatarFallback>
45-
</Avatar>
46-
<div>
47-
<h2 className='text-2xl font-bold'>{user?.user_metadata.full_name}</h2>
48-
<p className='text-muted-foreground'>{user?.email}</p>
49-
</div>
50-
</div>
51-
</CardContent>
52-
<CardFooter className='flex justify-center'>
53-
<Label className='uppercase font-bold text-center'>
54-
{t("loginWith")} {user?.app_metadata.provider}
55-
</Label>
56-
</CardFooter>
57-
</Card>
33+
<Suspense fallback={<LoadingComponent />}>
34+
<FormClient nickname={user?.nickname || ""} />
35+
</Suspense>
36+
<Suspense fallback={<LoadingComponent />}>
37+
<Profile user={user} />
38+
</Suspense>
5839
</section>
5940
);
6041
}

app/[locale]/(public)/auth/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getTranslations, setRequestLocale } from "next-intl/server";
2-
import { LoginClient } from "./client/LoginClient";
2+
import { LoginClient } from "./auth.client";
33
import { Metadata } from "next";
44
import { locale } from "@/types/global";
55

app/[locale]/actions.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@
22

33
import { prisma } from "@/configs/prisma/db";
44
import { handleErrorServerNoAuth } from "@/utils/handleErrorServer";
5+
import { unstable_cache } from "next/cache";
56

67
const getAllNickname = async () =>
78
handleErrorServerNoAuth({
8-
cb: async () => {
9-
const nicknames = await prisma.nickname.findMany({
10-
select: {
11-
content: true,
12-
updatedAt: true
13-
}
14-
});
9+
cb: unstable_cache(
10+
async () => {
11+
const nicknames = await prisma.nickname.findMany({
12+
select: {
13+
content: true,
14+
updatedAt: true
15+
}
16+
});
1517

16-
return nicknames;
17-
}
18+
console.info("[actions.ts:16] ", "refetch all nicknames");
19+
20+
return nicknames;
21+
},
22+
["nicknames"],
23+
{ tags: ["nicknames"] }
24+
)
1825
});
1926

2027
export { getAllNickname };

app/[locale]/dynamic.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Link } from "@/configs/i18n/routing";
2+
import { _ROUTE_PROFILE } from "@/constants/route";
3+
import { getTranslations } from "next-intl/server";
4+
import { getAllNickname } from "./actions";
5+
import { getRandomPastelColor, handleDatetime } from "@/utils/handleDatetime";
6+
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
7+
import { Badge } from "@/components/ui/badge";
8+
9+
type NicknameType = {
10+
content: string;
11+
updatedAt: string;
12+
color: string;
13+
};
14+
15+
const UserList = async () => {
16+
const t = await getTranslations("home.nickname");
17+
const tCommonText = await getTranslations("common.text");
18+
19+
const { data, error } = await getAllNickname();
20+
if (error) {
21+
throw new Error(error.message);
22+
}
23+
const nicknames = data?.payload as Array<NicknameType>;
24+
25+
return (
26+
<div className='flex flex-col justify-center items-center space-y-2'>
27+
<div className='text-center'>
28+
<h2 className='text-xl uppercase font-bold'>{t("title")}</h2>
29+
<p className='text-sm text-muted-foreground italic'>
30+
{t("description")}{" "}
31+
<Link href={_ROUTE_PROFILE} className='text-primary hover:underline'>
32+
{tCommonText("here")}.
33+
</Link>
34+
</p>
35+
</div>
36+
<div className='flex flex-wrap space-x-2'>
37+
{nicknames?.map((nickname, index) => {
38+
const randomBackgroundColor = getRandomPastelColor();
39+
40+
return (
41+
<Tooltip key={index}>
42+
<TooltipTrigger>
43+
<Badge style={{ background: randomBackgroundColor }} className='text-md rounded-md'>
44+
{nickname.content}
45+
</Badge>
46+
</TooltipTrigger>
47+
<TooltipContent side='bottom'>
48+
<p className='text-muted-foreground'>{handleDatetime(new Date(nickname.updatedAt))}</p>
49+
</TooltipContent>
50+
</Tooltip>
51+
);
52+
})}
53+
</div>
54+
</div>
55+
);
56+
};
57+
58+
export { UserList };

app/[locale]/loading.tsx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,5 @@
1-
import { Skeleton } from "@/components/ui/skeleton";
1+
import { LoadingPage } from "@/components/custom/Loading";
22

33
export default function Loading() {
4-
return (
5-
<section className=' py-4 h-full flex flex-col justify-center items-center'>
6-
<div className='flex gap-4 justify-center items-center'>
7-
<Skeleton className='h-12 w-12 rounded-full' />
8-
<div className='space-y-2'>
9-
<Skeleton className='h-4 w-[250px]' />
10-
<Skeleton className='h-4 w-[200px]' />
11-
</div>
12-
</div>
13-
<div className='mt-4 flex flex-col gap-4'>
14-
<Skeleton className='h-4 w-[300px]' />
15-
<Skeleton className='h-4 w-[250px]' />
16-
<Skeleton className='h-4 w-[200px]' />
17-
</div>
18-
</section>
19-
);
4+
return <LoadingPage />;
205
}

0 commit comments

Comments
 (0)