Skip to content

Commit ec37073

Browse files
committed
feat: refactor link caching logic and improve UI component styles
1 parent 0bf2be7 commit ec37073

File tree

8 files changed

+60
-42
lines changed

8 files changed

+60
-42
lines changed

apps/web/app/app.clikz/(dashboard)/[slug]/_components/dashboard-sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function DashboardSidebar() {
6161
const currentNavigation = isSettingsPage ? settingsNavigation : navigation;
6262

6363
return (
64-
<Sidebar className="bg-slate-700">
64+
<Sidebar>
6565
<AnimatePresence mode="wait">
6666
<motion.div
6767
key={

apps/web/components/preview/social-preview.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ const ImagePreview = ({
3333
className="relative size-full rounded-[inherit] object-cover"
3434
/>
3535
) : (
36-
<div className="flex size-full flex-col items-center justify-center space-y-4 bg-white">
37-
<ImageIcon className="h-8 w-8 text-gray-400" />
36+
<div className="flex flex-col items-center justify-center space-y-4 bg-white size-full">
37+
<ImageIcon className="size-8 text-gray-400" />
3838
<p className="text-sm text-gray-400">
3939
Enter a link to generate a preview.
4040
</p>
@@ -70,8 +70,8 @@ export const WebPreview = ({
7070
return (
7171
<ImagePreview image={image} loading={loading}>
7272
<div className="px-1 pt-2 space-y-1">
73-
<h3 className="text-sm">{title}</h3>
74-
<p className="text-xs">{description}</p>
73+
<h3 className="text-sm line-clamp-1">{title}</h3>
74+
<p className="text-xs line-clamp-2">{description}</p>
7575
</div>
7676
</ImagePreview>
7777
);
@@ -80,8 +80,8 @@ export const WebPreview = ({
8080
export const XPreview = ({ loading, title, image }: BasePreviewProps) => {
8181
return (
8282
<ImagePreview image={image} loading={loading} className="rounded-2xl">
83-
<div className="space-y-1 absolute left-2 bottom-2">
84-
<h3 className="text-sm bg-gray-900/35 text-white px-2 py-1 rounded-md">
83+
<div className="absolute space-y-1 left-2 bottom-2">
84+
<h3 className="px-2 py-1 text-sm text-white rounded-md bg-gray-900/35 line-clamp-1">
8585
{title}
8686
</h3>
8787
</div>
@@ -101,9 +101,9 @@ export const FacebookPreview = ({
101101
}: FacebookPreviewProps) => {
102102
return (
103103
<ImagePreview image={image} loading={loading} className="rounded-none">
104-
<div className="p-3 space-y-1 bg-gray-100 border-1 border-t-0 border-gray-800 rounded-none">
104+
<div className="p-3 space-y-1 bg-gray-100 border-t-0 border-gray-800 rounded-none border-1 line-clamp-1">
105105
<h3 className="text-sm">{title}</h3>
106-
<p className="text-xs">{description}</p>
106+
<p className="text-xs line-clamp-2">{description}</p>
107107
</div>
108108
</ImagePreview>
109109
);
@@ -121,7 +121,7 @@ export const LinkedInPreview = ({
121121
}: LinkedInPreviewProps) => {
122122
return (
123123
<Card>
124-
<CardContent className="p-2 rounded-md grid grid-cols-2 gap-4 my-auto">
124+
<CardContent className="grid grid-cols-2 gap-4 p-2 my-auto rounded-md">
125125
{loading ? (
126126
<div className="absolute inset-0 z-[5] flex items-center justify-center rounded-[inherit] bg-white">
127127
<LoaderCircle className="animate-spin size-5" />
@@ -131,20 +131,20 @@ export const LinkedInPreview = ({
131131
<img
132132
src={image}
133133
alt="Preview"
134-
className="w-full object-contain rounded-md"
134+
className="object-contain w-full rounded-md"
135135
/>
136136
</div>
137137
) : (
138-
<div className="flex size-full flex-col items-center justify-center space-y-2 text-center bg-gray-200 p-4">
139-
<ImageIcon className="size-5 text-gray-400" />
138+
<div className="flex flex-col items-center justify-center p-4 space-y-2 text-center bg-gray-200 size-full">
139+
<ImageIcon className="text-gray-400 size-5" />
140140
<p className="text-sm text-gray-400 text-pretty">
141141
Enter a link to generate a preview.
142142
</p>
143143
</div>
144144
)}
145-
<div className="py-2 flex flex-col justify-between">
145+
<div className="flex flex-col justify-between py-2">
146146
<h3 className="text-sm line-clamp-2 ">{title}</h3>
147-
<p className="text-xs text-muted-foreground">
147+
<p className="text-xs line-clamp-2 text-muted-foreground">
148148
{url
149149
? (isValidUrl(url) && new URL(url).host) || "example.com"
150150
: url}

apps/web/features/link/server.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import { after } from "next/server";
2+
13
import { zValidator } from "@hono/zod-validator";
24
import { Hono } from "hono";
35

46
import { roleMiddleware } from "~/lib/backend/role-middleware";
57
import { sessionMiddleware } from "~/lib/backend/session-middleware";
8+
import { deleteLinkFromRedis, setLinkToRedis } from "~/lib/cache/link";
69
import { BASE_DOMAIN, BASE_URL } from "~/lib/constants";
710
import { db } from "~/lib/db";
811
import {
@@ -92,7 +95,7 @@ const linksApp = new Hono()
9295
"/:linkId",
9396
sessionMiddleware,
9497
roleMiddleware(),
95-
zValidator("json", linkSchema.partial()),
98+
zValidator("json", linkSchema),
9699
zValidator("query", workspaceSlugSchema),
97100
async (c) => {
98101
const { comment, destination, slug, domain } = c.req.valid("json");
@@ -123,6 +126,10 @@ const linksApp = new Hono()
123126
},
124127
});
125128

129+
after(() => {
130+
setLinkToRedis(slug, domain || BASE_DOMAIN, link);
131+
});
132+
126133
return c.json({ link });
127134
}
128135
)
@@ -132,6 +139,7 @@ const linksApp = new Hono()
132139
const link = await db.link.delete({
133140
where: { id: linkId },
134141
});
142+
after(() => deleteLinkFromRedis(link.key, link.domain));
135143

136144
return c.json({ link });
137145
})

apps/web/features/workspace/components/workspace-switcher.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ const WorkspaceSwitcher = ({ className }: WorkspaceSwitcherProps) => {
4444
<Skeleton className="w-full h-11" />
4545
) : (
4646
<Select onValueChange={onSelect} value={slug}>
47-
<SelectTrigger className="w-full font-medium p-1 border-0 ring-0">
47+
<SelectTrigger className="w-full h-auto font-medium border-0 ring-0">
4848
<SelectValue placeholder="No workspace selected" />
4949
</SelectTrigger>
5050
<SelectContent>
5151
{Number(workspaces?.length ?? 0) > 0 ? (
5252
workspaces?.map((workspace) => (
5353
<SelectItem key={workspace.id} value={workspace.slug}>
54-
<div className="flex justify-start items-center gap-3 font-medium">
54+
<div className="flex items-center justify-start gap-3 font-medium">
5555
<WorkspaceIcon
5656
name={workspace.slug}
5757
image={workspace.icon ?? undefined}
@@ -60,7 +60,7 @@ const WorkspaceSwitcher = ({ className }: WorkspaceSwitcherProps) => {
6060
/>
6161
<div className="flex flex-col items-start">
6262
<span className="truncate">{workspace.name}</span>
63-
<span className="truncate text-xs text-muted-foreground">
63+
<span className="text-xs truncate text-muted-foreground">
6464
Free
6565
</span>
6666
</div>

apps/web/lib/cache/link.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { RequiredLinkProp } from "../types";
2+
import { redis } from "./redis";
3+
4+
const getCacheKey = (key: string, domain: string) => `link:${domain}:${key}`;
5+
6+
export const getLinkViaRedis = async (key: string, domain: string) => {
7+
const cacheKey = getCacheKey(key, domain);
8+
const cached = await redis.get(cacheKey);
9+
return cached as RequiredLinkProp | null;
10+
};
11+
12+
export const setLinkToRedis = async (
13+
key: string,
14+
domain: string,
15+
link: RequiredLinkProp,
16+
expireInDay: number = 1
17+
) => {
18+
const cacheKey = getCacheKey(key, domain);
19+
await redis.set(cacheKey, link, {
20+
ex:
21+
process.env.NODE_ENV === "development" ? 60 : 60 * 60 * 24 * expireInDay, // expire in 1 day as default in production and 1 minute in development
22+
});
23+
};
24+
25+
export const deleteLinkFromRedis = async (key: string, domain: string) => {
26+
const cacheKey = getCacheKey(key, domain);
27+
await redis.del(cacheKey);
28+
};
29+
30+
export const flushAllLinkCache = async () => {
31+
await redis.flushall();
32+
};
File renamed without changes.

apps/web/lib/middleware/link.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { NextRequest, NextResponse, after } from "next/server";
22

33
import { recordClickEvent } from "../analytics/click-events";
4+
import { getLinkViaRedis, setLinkToRedis } from "../cache/link";
45
import { RequiredLinkProp } from "../types";
56
import { getLinkViaEdgeWithKey, parse } from "./utils";
67
import { getFinalUrl } from "./utils/final-url";
7-
import { getLinkViaRedis, setLinkToRedis } from "./utils/link-utlis";
88

99
const LinkMiddleware = async (req: NextRequest) => {
1010
const { fullKey: originalKey, domain } = parse(req);
Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { NextRequest } from "next/server";
22

3-
import { redis } from "~/lib/redis";
4-
import { RequiredLinkProp } from "~/lib/types";
5-
63
export const detectBot = (req: NextRequest) => {
74
const searchParams = new URL(req.url).searchParams;
85
if (searchParams.get("bot")) return true;
@@ -30,22 +27,3 @@ export const detectQR = (req: NextRequest) => {
3027
const searchParams = req.nextUrl.searchParams;
3128
return searchParams.get("qr") === "1";
3229
};
33-
34-
export const getLinkViaRedis = async (key: string, domain: string) => {
35-
const cacheKey = `link:${domain}:${key}`;
36-
const cached = await redis.get(cacheKey);
37-
return cached as RequiredLinkProp | null;
38-
};
39-
40-
export const setLinkToRedis = async (
41-
key: string,
42-
domain: string,
43-
link: RequiredLinkProp,
44-
expireInDay: number = 1
45-
) => {
46-
const cacheKey = `link:${domain}:${key}`;
47-
await redis.set(cacheKey, link, {
48-
ex:
49-
process.env.NODE_ENV === "development" ? 60 : 60 * 60 * 24 * expireInDay, // expire in 1 day as default in production and 1 minute in development
50-
});
51-
};

0 commit comments

Comments
 (0)