Skip to content

Commit 6c4ddbd

Browse files
authored
feat: QR Codes (#4)
1 parent 38e4343 commit 6c4ddbd

File tree

14 files changed

+612
-41
lines changed

14 files changed

+612
-41
lines changed

.vscode/mcp.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"servers": {
3+
"next-devtools": {
4+
"command": "npx",
5+
"args": ["-y", "next-devtools-mcp@latest"]
6+
}
7+
}
8+
}

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
"@radix-ui/react-label": "^2.1.7",
2828
"@radix-ui/react-select": "^2.2.6",
2929
"@radix-ui/react-slot": "^1.2.3",
30+
"@radix-ui/react-tabs": "^1.1.13",
3031
"@radix-ui/react-toggle": "^1.1.10",
32+
"@radix-ui/react-tooltip": "^1.2.8",
3133
"@scalar/nextjs-api-reference": "^0.8.23",
3234
"@t3-oss/env-nextjs": "^0.13.8",
3335
"@tanstack/react-query": "^5.90.5",
@@ -44,6 +46,7 @@
4446
"next": "15.5.2",
4547
"next-themes": "^0.4.6",
4648
"pg": "^8.16.3",
49+
"qr-code-styling": "^1.9.2",
4750
"react": "19.1.0",
4851
"react-dom": "19.1.0",
4952
"react-hook-form": "^7.62.0",

pnpm-lock.yaml

Lines changed: 114 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/globals.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,9 @@
195195
[role="button"]:not(:disabled) {
196196
cursor: pointer;
197197
}
198+
}
199+
200+
div#qr-code-preview-container canvas {
201+
width: 100% !important;
202+
height: 100% !important;
198203
}

src/app/layout.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from "next"
22
import "./globals.css"
33
import { QueryProvider } from "@/components/query-provider"
44
import { Toaster } from "@/components/ui/sonner"
5+
import { TooltipProvider } from "@/components/ui/tooltip"
56

67
export const metadata: Metadata = {
78
title: "PoliNetwork Short URLs",
@@ -17,8 +18,10 @@ export default function RootLayout({
1718
<html lang="en" className="dark">
1819
<body>
1920
<QueryProvider>
20-
{children}
21-
<Toaster />
21+
<TooltipProvider>
22+
{children}
23+
<Toaster />
24+
</TooltipProvider>
2225
</QueryProvider>
2326
</body>
2427
</html>

src/assets/logo.svg

Lines changed: 1 addition & 0 deletions
Loading

src/components/dashboard.tsx

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { copyToClipboard } from "@/lib/utils"
3737
import { CreateUrlDialog } from "./create-url-dialog"
3838
import { type EditDialogState, EditUrlDialog } from "./edit-url-dialog"
3939
import { PaginationControls } from "./pagination"
40+
import { QrCodeDialog } from "./qr-code-dialog"
4041
import { Toggle } from "./ui/toggle"
4142
import { MobileRow, UrlRecordRow } from "./url-record-row"
4243

@@ -59,6 +60,10 @@ export function Dashboard() {
5960

6061
const [createDialogOpen, setCreateDialogOpen] = useState(false)
6162
const [editDialog, setEditDialog] = useState<EditDialogState>({ open: false })
63+
const [qrDialog, setQrDialog] = useState<{
64+
open: boolean
65+
url?: UrlRecord
66+
}>({ open: false })
6267

6368
const handleCustomOnlyToggle = () => {
6469
setQueryParams((prev) => ({
@@ -111,15 +116,17 @@ export function Dashboard() {
111116

112117
return (
113118
<div className="container mx-auto p-6 space-y-6">
114-
<div className="flex gap-4 items-center">
115-
<Image src={logo} alt="PoliNetwork Logo" className="h-16 w-16" />
116-
<div className="mr-auto gap-2">
117-
<h1 className="text-3xl font-bold">polinet.cc</h1>
118-
<p className="text-muted-foreground">
119-
PoliNetwork's URL shortener dashboard
120-
</p>
119+
<div className="flex justify-between gap-4 items-center max-md:flex-col">
120+
<div className="flex items-center gap-4">
121+
<Image src={logo} alt="PoliNetwork Logo" className="h-16 w-16" />
122+
<div className="gap-2">
123+
<h1 className="text-3xl font-bold">polinet.cc</h1>
124+
<p className="text-muted-foreground max-md:text-sm">
125+
PoliNetwork's URL shortener dashboard
126+
</p>
127+
</div>
121128
</div>
122-
<div className="flex gap-4">
129+
<div className="flex justify-end gap-4 max-md:gap-2 flex-wrap">
123130
<a
124131
href="https://github.com/PoliNetworkOrg/polinet.cc"
125132
className="underline flex items-center gap-1"
@@ -141,18 +148,7 @@ export function Dashboard() {
141148
<span>API Docs</span>
142149
</Button>
143150
</Link>
144-
<Button
145-
size="icon-lg"
146-
className="md:hidden"
147-
onClick={() => setCreateDialogOpen(true)}
148-
>
149-
<Plus />
150-
</Button>
151-
<Button
152-
size="lg"
153-
className="max-md:hidden"
154-
onClick={() => setCreateDialogOpen(true)}
155-
>
151+
<Button size="lg" onClick={() => setCreateDialogOpen(true)}>
156152
<Plus />
157153
<span>Create Short URL</span>
158154
</Button>
@@ -178,9 +174,9 @@ export function Dashboard() {
178174
className="pl-9"
179175
/>
180176
</div>
181-
<div className="flex gap-4 items-center justify-between max-md:w-full max-md:order-3">
177+
<div className="flex gap-4 items-center justify-between max-md:w-full max-md:order-3 flex-wrap">
182178
<Select value={currentSort} onValueChange={handleSortChange}>
183-
<SelectTrigger className="w-[170px] max-md:flex-1">
179+
<SelectTrigger className="w-[170px] flex-100">
184180
<SelectValue placeholder="Sort by" />
185181
</SelectTrigger>
186182
<SelectContent>
@@ -201,7 +197,7 @@ export function Dashboard() {
201197
pressed={queryParams.customOnly}
202198
onPressedChange={handleCustomOnlyToggle}
203199
variant="outline"
204-
className="data-[state=on]:*:[svg]:fill-yellow-300 data-[state=on]:*:[svg]:stroke-yellow-300"
200+
className="data-[state=on]:*:[svg]:fill-yellow-300 data-[state=on]:*:[svg]:stroke-yellow-300 flex-[1_0_auto]"
205201
>
206202
<Star className="h-4 w-4" />
207203
Show Custom Only
@@ -230,6 +226,7 @@ export function Dashboard() {
230226
}
231227
onDelete={(url) => handleDelete(url.short_code)}
232228
onEdit={(url) => setEditDialog({ open: true, url })}
229+
onQrCode={(url) => setQrDialog({ open: true, url })}
233230
/>
234231
))}
235232
</div>
@@ -256,6 +253,7 @@ export function Dashboard() {
256253
}
257254
onDelete={(url) => handleDelete(url.short_code)}
258255
onEdit={(url) => setEditDialog({ open: true, url })}
256+
onQrCode={(url) => setQrDialog({ open: true, url })}
259257
/>
260258
))}
261259
</TableBody>
@@ -304,6 +302,12 @@ export function Dashboard() {
304302
setEditDialog({ open: false })
305303
}}
306304
/>
305+
306+
<QrCodeDialog
307+
open={qrDialog.open}
308+
url={qrDialog.url}
309+
onOpenChange={(open) => setQrDialog((prev) => ({ ...prev, open }))}
310+
/>
307311
</div>
308312
)
309313
}

0 commit comments

Comments
 (0)