Skip to content

Commit 35e88ae

Browse files
feat: mobile, footer text, GH link, minor changes
1 parent 5d8f15c commit 35e88ae

File tree

5 files changed

+192
-45
lines changed

5 files changed

+192
-45
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
},
1919
"dependencies": {
2020
"@hookform/resolvers": "^5.2.1",
21+
"@icons-pack/react-simple-icons": "^13.8.0",
2122
"@next/bundle-analyzer": "^16.0.1",
2223
"@openapi-contrib/json-schema-to-openapi-schema": "^4.2.0",
2324
"@radix-ui/react-dialog": "^1.1.15",
@@ -36,7 +37,7 @@
3637
"ajv": "^8.17.1",
3738
"class-variance-authority": "^0.7.1",
3839
"clsx": "^2.1.1",
39-
"lucide-react": "^0.542.0",
40+
"lucide-react": "^0.552.0",
4041
"nanoid": "^5.1.5",
4142
"next": "15.5.2",
4243
"next-themes": "^0.4.6",

pnpm-lock.yaml

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

src/components/dashboard.tsx

Lines changed: 94 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client"
22

3-
import { Search, Star } from "lucide-react"
3+
import { SiGithub as Github } from "@icons-pack/react-simple-icons"
4+
import { FileCodeCorner, Plus, Search, Star } from "lucide-react"
45
import Image from "next/image"
56
import Link from "next/link"
67
import { useState } from "react"
@@ -37,7 +38,7 @@ import { CreateUrlDialog } from "./create-url-dialog"
3738
import { EditUrlDialog } from "./edit-url-dialog"
3839
import { PaginationControls } from "./pagination"
3940
import { Toggle } from "./ui/toggle"
40-
import { UrlRecordRow } from "./url-record-row"
41+
import { MobileRow, UrlRecordRow } from "./url-record-row"
4142

4243
export function Dashboard() {
4344
const [searchInput, setSearchInput] = useState("")
@@ -122,11 +123,41 @@ export function Dashboard() {
122123
</p>
123124
</div>
124125
<div className="flex gap-4">
126+
<a
127+
href="https://github.com/PoliNetworkOrg/polinet.cc"
128+
className="underline flex items-center gap-1"
129+
title="https://github.com/PoliNetworkOrg/polinet.cc"
130+
aria-label="tmsu.cc github repository by Tommaso Morganti"
131+
target="_blank"
132+
rel="noopener noreferrer"
133+
>
134+
<Button size="icon-lg" variant="outline">
135+
<Github />
136+
</Button>
137+
</a>
125138
<Link href="/api" target="_blank" rel="noopener noreferral">
126-
<Button variant="outline">API Docs</Button>
139+
<Button variant="outline" size="icon-lg" className="md:hidden">
140+
<FileCodeCorner />
141+
</Button>
142+
<Button size="lg" variant="outline" className="max-md:hidden">
143+
<FileCodeCorner />
144+
<span>API Docs</span>
145+
</Button>
127146
</Link>
128-
<Button onClick={() => setCreateDialogOpen(true)}>
129-
Create Short URL
147+
<Button
148+
size="icon-lg"
149+
className="md:hidden"
150+
onClick={() => setCreateDialogOpen(true)}
151+
>
152+
<Plus />
153+
</Button>
154+
<Button
155+
size="lg"
156+
className="max-md:hidden"
157+
onClick={() => setCreateDialogOpen(true)}
158+
>
159+
<Plus />
160+
<span>Create Short URL</span>
130161
</Button>
131162
</div>
132163
</div>
@@ -140,8 +171,8 @@ export function Dashboard() {
140171
</CardHeader>
141172
<CardContent className="space-y-4">
142173
{/* Filters */}
143-
<div className="flex gap-4 items-center">
144-
<div className="relative flex-1">
174+
<div className="flex gap-4 items-center max-md:flex-col">
175+
<div className="relative flex-1 max-md:w-full max-md:order-2">
145176
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
146177
<Input
147178
placeholder="Search URLs or short codes..."
@@ -150,31 +181,35 @@ export function Dashboard() {
150181
className="pl-9"
151182
/>
152183
</div>
153-
<Select value={currentSort} onValueChange={handleSortChange}>
154-
<SelectTrigger className="w-[200px]">
155-
<SelectValue placeholder="Sort by" />
156-
</SelectTrigger>
157-
<SelectContent>
158-
<SelectItem value="created_at-desc">Newest First</SelectItem>
159-
<SelectItem value="created_at-asc">Oldest First</SelectItem>
160-
<SelectItem value="updated_at-desc">
161-
Recently Updated
162-
</SelectItem>
163-
<SelectItem value="click_count-desc">Most Clicks</SelectItem>
164-
<SelectItem value="click_count-asc">Least Clicks</SelectItem>
165-
<SelectItem value="short_code-asc">Short Code A-Z</SelectItem>
166-
<SelectItem value="short_code-desc">Short Code Z-A</SelectItem>
167-
</SelectContent>
168-
</Select>
169-
<Toggle
170-
pressed={queryParams.customOnly}
171-
onPressedChange={handleCustomOnlyToggle}
172-
variant="outline"
173-
className="data-[state=on]:*:[svg]:fill-yellow-300 data-[state=on]:*:[svg]:stroke-yellow-300"
174-
>
175-
<Star className="h-4 w-4" />
176-
Show Custom Only
177-
</Toggle>
184+
<div className="flex gap-4 items-center justify-between max-md:w-full max-md:order-3">
185+
<Select value={currentSort} onValueChange={handleSortChange}>
186+
<SelectTrigger className="w-[170px] max-md:flex-1">
187+
<SelectValue placeholder="Sort by" />
188+
</SelectTrigger>
189+
<SelectContent>
190+
<SelectItem value="created_at-desc">Newest First</SelectItem>
191+
<SelectItem value="created_at-asc">Oldest First</SelectItem>
192+
<SelectItem value="updated_at-desc">
193+
Recently Updated
194+
</SelectItem>
195+
<SelectItem value="click_count-desc">Most Clicks</SelectItem>
196+
<SelectItem value="click_count-asc">Least Clicks</SelectItem>
197+
<SelectItem value="short_code-asc">Short Code A-Z</SelectItem>
198+
<SelectItem value="short_code-desc">
199+
Short Code Z-A
200+
</SelectItem>
201+
</SelectContent>
202+
</Select>
203+
<Toggle
204+
pressed={queryParams.customOnly}
205+
onPressedChange={handleCustomOnlyToggle}
206+
variant="outline"
207+
className="data-[state=on]:*:[svg]:fill-yellow-300 data-[state=on]:*:[svg]:stroke-yellow-300"
208+
>
209+
<Star className="h-4 w-4" />
210+
Show Custom Only
211+
</Toggle>
212+
</div>
178213
</div>
179214

180215
{/* Table */}
@@ -188,7 +223,20 @@ export function Dashboard() {
188223
</div>
189224
) : (
190225
<>
191-
<Table>
226+
<div className="lg:hidden flex flex-col gap-4">
227+
{urls.map((url: UrlRecord) => (
228+
<MobileRow
229+
key={url.id}
230+
url={url}
231+
onCopy={(url) =>
232+
copyToClipboard(`https://polinet.cc/${url.short_code}`)
233+
}
234+
onDelete={(url) => handleDelete(url.short_code)}
235+
onEdit={(url) => setEditDialog({ open: true, url })}
236+
/>
237+
))}
238+
</div>
239+
<Table className="max-lg:hidden">
192240
<TableHeader>
193241
<TableRow>
194242
<TableHead className="w-4">
@@ -228,6 +276,19 @@ export function Dashboard() {
228276
)}
229277
</CardContent>
230278
</Card>
279+
<div className="flex text-center items-center justify-center gap-1">
280+
<p className="text-muted-foreground text-sm">
281+
Made with 💙 by{" "}
282+
<a
283+
href="https://polinetwork.org"
284+
target="_blank"
285+
rel="noreferrer noopener"
286+
className="underline underline-offset-2"
287+
>
288+
PoliNetwork
289+
</a>
290+
</p>
291+
</div>
231292

232293
<CreateUrlDialog
233294
open={createDialogOpen}

src/components/pagination.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ export function PaginationControls(
3535
console.log(props.page, props.totalPages)
3636

3737
return (
38-
<div className="flex items-center justify-between">
39-
<div className="flex flex-[1_0_auto] items-center gap-4">
38+
<div className="flex items-center justify-between max-md:flex-col gap-2">
39+
<div className="flex flex-[1_0_auto] items-center gap-4 max-md:order-2">
4040
<p className="text-sm text-muted-foreground">
4141
Showing {(props.page - 1) * limit + 1} to{" "}
4242
{Math.min(props.page * limit, props.total)} of {props.total} results
4343
</p>
4444
</div>
45-
<Pagination>
45+
<Pagination className="max-md:order-1">
4646
<PaginationContent>
4747
<PaginationItem>
4848
<PaginationPrevious
@@ -94,7 +94,7 @@ export function PaginationControls(
9494
</PaginationItem>
9595
</PaginationContent>
9696
</Pagination>
97-
<div className="flex flex-[1_0_auto] items-center gap-2">
97+
<div className="flex flex-[1_0_auto] items-center gap-2 max-md:order-3">
9898
<span className="text-sm text-muted-foreground">Items per page:</span>
9999
<Select value={limit.toString()} onValueChange={handleLimitChange}>
100100
<SelectTrigger className="w-[70px] h-8">

src/components/url-record-row.tsx

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { Copy, Diamond, Edit, Star, Trash2 } from "lucide-react"
1+
import {
2+
ArrowRight,
3+
Copy,
4+
Diamond,
5+
Edit,
6+
Pointer,
7+
Star,
8+
Trash2,
9+
} from "lucide-react"
210
import type { UrlRecord } from "@/lib/schemas"
311
import { copyToClipboard } from "@/lib/utils"
412
import { Button } from "./ui/button"
@@ -11,9 +19,74 @@ export type UrlRecordRowProps = {
1119
onEdit: (url: UrlRecord) => void
1220
}
1321

22+
export function MobileRow({
23+
url,
24+
onCopy,
25+
onDelete,
26+
onEdit,
27+
}: UrlRecordRowProps) {
28+
return (
29+
<div className="flex flex-col gap-1 border rounded-md py-2 px-4">
30+
<div className="flex justify-start gap-2 items-center">
31+
{url.is_custom ? (
32+
<Star className="h-4 w-4 fill-yellow-400 stroke-yellow-400" />
33+
) : (
34+
<Diamond className="h-4 w-4 fill-gray-400 stroke-gray-400" />
35+
)}
36+
37+
<a
38+
href={`https://polinet.cc/${url.short_code}`}
39+
target="_blank"
40+
rel="noopener noreferrer"
41+
className="text-blue-400 hover:underline font-mono"
42+
>
43+
polinet.cc/{url.short_code}
44+
</a>
45+
<Button variant="ghost" size="icon" onClick={() => onCopy(url)}>
46+
<Copy />
47+
</Button>
48+
</div>
49+
<div className="flex justify-start gap-2 items-center">
50+
<ArrowRight className="text-blue-400" />
51+
<a
52+
href={url.original_url}
53+
className="text-blue-400 hover:underline font-mono truncate"
54+
title={url.original_url}
55+
target="_blank"
56+
rel="noopener noreferrer"
57+
>
58+
{url.original_url}
59+
</a>
60+
<Button
61+
variant="ghost"
62+
size="icon"
63+
onClick={() => copyToClipboard(url.original_url)}
64+
>
65+
<Copy />
66+
</Button>
67+
</div>
68+
<div className="flex justify-end gap-2 items-center py-1">
69+
<Pointer className="h-3 w-3 text-muted-foreground" />
70+
<span className="text-sm">{url.click_count}</span>
71+
<div className="flex-1" />
72+
<span className="text-sm text-muted-foreground">
73+
{url.created_at.toLocaleString()}
74+
</span>
75+
76+
<Button variant="ghost" size="icon" onClick={() => onEdit(url)}>
77+
<Edit />
78+
</Button>
79+
<Button variant="ghost" size="icon" onClick={() => onDelete(url)}>
80+
<Trash2 className="stroke-destructive" />
81+
</Button>
82+
</div>
83+
</div>
84+
)
85+
}
86+
1487
export function UrlRecordRow({ url, ...props }: UrlRecordRowProps) {
1588
return (
16-
<TableRow key={url.id}>
89+
<TableRow key={url.id} className="max-sm:hidden">
1790
<TableCell>
1891
{url.is_custom ? (
1992
<Star className="h-4 w-4 fill-yellow-400 stroke-yellow-400" />

0 commit comments

Comments
 (0)