mirror of
https://github.com/ThisTine/Snip.git
synced 2026-10-18 11:01:38 +07:00
feat: first commit
This commit is contained in:
commit
3395ab6dd3
88 files changed
+10034
No files matched your search
@@ -0,0 +1,118 @@
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useEffect, useState } from "react";
|
||||
import { api } from "../lib/api";
|
||||
import type { ShortLink } from "../lib/types";
|
||||
import { prettyHost, shortUrl } from "../lib/format";
|
||||
import { isValidUrl, normalizeUrl } from "../lib/shortcode";
|
||||
import { Modal } from "./ui/Modal";
|
||||
import { Button } from "./ui/Button";
|
||||
|
||||
interface Props {
|
||||
link: ShortLink | null;
|
||||
onClose: () => void;
|
||||
onSaved: (updated: ShortLink, message: string) => void;
|
||||
}
|
||||
|
||||
export function EditLinkModal({ link, onClose, onSaved }: Props) {
|
||||
const open = Boolean(link);
|
||||
const [draft, setDraft] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Reset the form whenever a different link opens the modal.
|
||||
useEffect(() => {
|
||||
setDraft(link?.longUrl ?? "");
|
||||
setError(null);
|
||||
setBusy(false);
|
||||
}, [link?.id, link?.longUrl]);
|
||||
|
||||
if (!link) return <Modal open={false} onClose={onClose} children={null} />;
|
||||
|
||||
const changed = normalizeUrl(draft) !== link.longUrl;
|
||||
|
||||
async function save() {
|
||||
if (!link) return;
|
||||
if (!isValidUrl(draft)) {
|
||||
setError("That doesn't look like a valid URL.");
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
const updated = await api.updateLink(link.id, {
|
||||
longUrl: normalizeUrl(draft),
|
||||
});
|
||||
onSaved(updated, "Link updated");
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : "Could not save changes.");
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose} title="Edit link">
|
||||
{/* Short code is permanent — shown read-only so existing shares keep working. */}
|
||||
<div className="-mt-2 mb-4 flex items-center justify-between rounded-2xl border-[1.5px] border-line bg-surface-2 px-4 py-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-[11px] font-medium uppercase tracking-wide text-muted">
|
||||
Short link
|
||||
</p>
|
||||
<p className="truncate font-mono text-base font-bold text-ink">
|
||||
{shortUrl(link.code)}
|
||||
</p>
|
||||
</div>
|
||||
<span className="shrink-0 rounded-full border-[1.5px] border-line px-2 py-0.5 text-[10px] font-medium text-muted">
|
||||
can't change
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<label className="mb-2 block text-sm font-semibold text-ink">
|
||||
Destination URL
|
||||
</label>
|
||||
<div
|
||||
className={`flex items-center gap-2 rounded-2xl border-[1.5px] bg-surface-2 px-3.5 transition-colors ${
|
||||
error ? "border-red-400/70" : "border-line"
|
||||
}`}
|
||||
>
|
||||
<span className="text-base opacity-50">🔗</span>
|
||||
<input
|
||||
autoFocus
|
||||
value={draft}
|
||||
onChange={(e) => {
|
||||
setDraft(e.target.value);
|
||||
if (error) setError(null);
|
||||
}}
|
||||
onKeyDown={(e) => e.key === "Enter" && save()}
|
||||
spellCheck={false}
|
||||
className="h-12 w-full bg-transparent font-mono text-sm text-ink outline-none"
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-2 text-xs text-muted">
|
||||
Where <span className="font-mono text-ink">{shortUrl(link.code)}</span>{" "}
|
||||
sends visitors — currently {prettyHost(link.longUrl)}.
|
||||
</p>
|
||||
|
||||
<AnimatePresence>
|
||||
{error && (
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: -4 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="mt-3 text-sm font-medium text-red-500"
|
||||
>
|
||||
{error}
|
||||
</motion.p>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<div className="mt-5 flex items-center gap-2">
|
||||
<Button onClick={save} disabled={busy || !changed}>
|
||||
{busy ? "Saving…" : "Save changes"}
|
||||
</Button>
|
||||
<Button variant="ghost" onClick={onClose} disabled={busy}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user