"use client"; import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@mdxui/primitives"; import { cn } from "@mdxui/primitives/lib/utils"; import { format } from "date-fns"; import { Copy, ExternalLink, Link as LinkIcon, Trash2, TrendingUp, } from "lucide-react"; import * as React from "react"; import { toast } from "sonner"; import type { LinkShortenerProps, ShortenedLink } from "./types"; function generateShortCode(length: number = 6): string { const characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; let result = ""; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * characters.length)); } return result; } function isValidUrl(url: string): boolean { try { new URL(url); return true; } catch { return false; } } export function LinkShortener({ baseUrl = typeof window !== "undefined" ? window.location.origin : "https://example.com.ai", onCreate, onCopy, onDelete, links: externalLinks = [], showLinkList = true, maxShortCodeLength = 20, className, }: LinkShortenerProps) { const [originalUrl, setOriginalUrl] = React.useState(""); const [customCode, setCustomCode] = React.useState(""); const [title, setTitle] = React.useState(""); const [isCreating, setIsCreating] = React.useState(false); const [localLinks, setLocalLinks] = React.useState([]); const links = externalLinks.length > 0 ? externalLinks : localLinks; const handleCreate = async () => { if (!originalUrl.trim()) { toast.error("Please enter a URL to shorten"); return; } if (!isValidUrl(originalUrl)) { toast.error("Please enter a valid URL"); return; } const shortCode = customCode.trim() || generateShortCode(); if (shortCode.length > maxShortCodeLength) { toast.error( `Short code must be ${maxShortCodeLength} characters or less`, ); return; } // Check if short code already exists if (links.some((link) => link.shortCode === shortCode)) { toast.error("This short code is already in use"); return; } setIsCreating(true); const newLink: ShortenedLink = { id: crypto.randomUUID(), originalUrl: originalUrl.trim(), shortCode, shortUrl: `${baseUrl}/${shortCode}`, title: title.trim() || undefined, clicks: 0, createdAt: new Date(), }; try { await onCreate?.(newLink); if (externalLinks.length === 0) { setLocalLinks((prev) => [newLink, ...prev]); } setOriginalUrl(""); setCustomCode(""); setTitle(""); toast.success("Link shortened successfully!"); } catch (error) { toast.error("Failed to create shortened link"); } finally { setIsCreating(false); } }; const handleCopy = (link: ShortenedLink) => { navigator.clipboard.writeText(link.shortUrl); toast.success("Link copied to clipboard!"); onCopy?.(link); }; const handleDelete = async (linkId: string) => { try { await onDelete?.(linkId); if (externalLinks.length === 0) { setLocalLinks((prev) => prev.filter((link) => link.id !== linkId)); } toast.success("Link deleted"); } catch (error) { toast.error("Failed to delete link"); } }; return (
Shorten a Link Create a shortened URL that's easy to share
setOriginalUrl(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { handleCreate(); } }} />
{baseUrl}/ setCustomCode( e.target.value.toLowerCase().replace(/[^a-z0-9-_]/g, ""), ) } maxLength={maxShortCodeLength} />
setTitle(e.target.value)} />
{showLinkList && links.length > 0 && ( Your Shortened Links {links.length} link{links.length !== 1 ? "s" : ""}
Short Link Original URL Clicks Created Actions {links.map((link) => (
{link.shortCode}
{link.title && (
{link.title}
)}
{link.originalUrl} {link.clicks.toLocaleString()} {format(link.createdAt, "MMM d, yyyy")}
))}
)}
); }