feat: frontend redesign

This commit is contained in:
sittichok Ouamsiri committed 2026-06-20 23:01:41 +07:00
1 parent faf2c97462
commit e177c7775a
27 files changed
+761 -912

No files matched your search

+29 -58
View File
@@ -7,6 +7,7 @@ import {
shortUrl,
} from "../lib/format";
import { Sparkline } from "./Sparkline";
import { Copy, Qr, Lock, Edit, Trash } from "./icons";
interface Props {
link: ShortLink;
@@ -17,26 +18,6 @@ interface Props {
onShowQr: (link: ShortLink) => void;
}
function QrGlyph() {
return (
<svg
width="15"
height="15"
viewBox="0 0 15 15"
fill="currentColor"
aria-hidden
>
<path d="M1 1h5v5H1V1zm1 1v3h3V2H2z" />
<path d="M9 1h5v5H9V1zm1 1v3h3V2h-3z" />
<path d="M1 9h5v5H1V9zm1 1v3h3v-3H2z" />
<rect x="9" y="9" width="2" height="2" />
<rect x="12" y="9" width="2" height="2" />
<rect x="9" y="12" width="2" height="2" />
<rect x="12" y="12" width="2" height="2" />
</svg>
);
}
function IconButton({
label,
onClick,
@@ -49,19 +30,16 @@ function IconButton({
children: React.ReactNode;
}) {
return (
<motion.button
whileTap={{ scale: 0.88 }}
whileHover={{ y: -2 }}
transition={{ type: "spring", stiffness: 500, damping: 16 }}
<button
onClick={onClick}
title={label}
aria-label={label}
className={`focusable grid h-9 w-9 place-items-center rounded-xl border-[1.5px] border-line bg-surface text-sm ${
danger ? "hover:!border-red-400 hover:text-red-500" : "hover:bg-surface-2"
className={`focusable grid h-8 w-8 place-items-center rounded-lg border border-line bg-surface text-muted transition-colors ${
danger ? "hover:border-red-400 hover:text-red-500" : "hover:bg-surface-2 hover:text-ink"
}`}
>
{children}
</motion.button>
</button>
);
}
@@ -87,45 +65,38 @@ export function UrlRow({
return (
<motion.div
layout
initial={{ opacity: 0, y: 12 }}
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.96, transition: { duration: 0.15 } }}
transition={{ type: "spring", stiffness: 320, damping: 28 }}
exit={{ opacity: 0, scale: 0.98, transition: { duration: 0.12 } }}
transition={{ duration: 0.18, ease: "easeOut" }}
className="card flex flex-col gap-3 px-4 py-3 sm:flex-row sm:items-center sm:gap-4"
>
{/* identity */}
<div className="flex min-w-0 flex-1 items-center gap-3">
<span className="hidden h-9 w-9 shrink-0 place-items-center rounded-xl bg-accent text-xs font-bold text-accent-ink sm:grid">
{link.mode === "custom" ? "✏️" : link.mode === "memorable" ? "🌿" : "🎲"}
</span>
<div className="min-w-0">
<div className="flex items-center gap-1.5">
<a
href={fullShortUrl(link.code)}
onClick={(e) => e.preventDefault()}
className="focusable truncate rounded font-mono text-[15px] font-bold text-ink hover:text-accent"
>
{shortUrl(link.code)}
</a>
{link.hasPin && <span className="text-[11px]">🔒</span>}
</div>
<p className="truncate text-[12px] text-muted" title={link.longUrl}>
{prettyHost(link.longUrl)}
</p>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5">
<a
href={fullShortUrl(link.code)}
onClick={(e) => e.preventDefault()}
className="focusable truncate rounded font-mono text-sm font-semibold text-ink hover:text-accent"
>
{shortUrl(link.code)}
</a>
{link.hasPin && <Lock size={12} className="shrink-0 text-muted" />}
</div>
<p className="truncate text-[12px] text-muted" title={link.longUrl}>
{prettyHost(link.longUrl)}
</p>
</div>
{/* stats */}
<div className="flex shrink-0 items-center gap-3">
<div className="text-right">
<p className="font-display text-base font-bold leading-none text-ink">
<p className="text-sm font-semibold leading-none text-ink">
{compactNumber(link.totalClicks)}
</p>
<p className="text-[10px] font-medium uppercase tracking-wide text-muted">
clicks
</p>
<p className="text-[10px] uppercase tracking-wide text-muted">clicks</p>
</div>
<span className="hidden items-center rounded-md bg-accent/15 px-1 text-[11px] font-bold text-accent-ink sm:inline-flex">
<span className="hidden text-[12px] font-medium text-accent sm:inline">
+{compactNumber(weekTotal)}
</span>
<div className="hidden sm:block">
@@ -136,19 +107,19 @@ export function UrlRow({
{/* actions */}
<div className="flex shrink-0 items-center gap-1.5">
<IconButton label="Copy link" onClick={copy}>
⧉
<Copy size={15} />
</IconButton>
<IconButton label="Show QR code" onClick={() => onShowQr(link)}>
<QrGlyph />
<Qr size={15} />
</IconButton>
<IconButton label="Manage PIN" onClick={() => onManagePin(link)}>
🔒
<Lock size={15} />
</IconButton>
<IconButton label="Edit destination" onClick={() => onEdit(link)}>
✏️
<Edit size={15} />
</IconButton>
<IconButton label="Delete link" danger onClick={() => onDelete(link)}>
🗑
<Trash size={15} />
</IconButton>
</div>
</motion.div>