diff --git a/frontend/bun.lock b/frontend/bun.lock index 04ad32c..69a84ad 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -15,6 +15,7 @@ "next-themes": "^0.4.6", "react": "19.2.0", "react-dom": "19.2.0", + "react-hook-form": "^7.66.0", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", }, @@ -727,6 +728,8 @@ "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], + "react-hook-form": ["react-hook-form@7.66.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-xXBqsWGKrY46ZqaHDo+ZUYiMUgi8suYu5kdrS20EG8KiL7VRQitEbNjm+UcrDYrNi1YLyfpmAeGjCZYXLT9YBw=="], + "react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], diff --git a/frontend/package.json b/frontend/package.json index e888d91..b68ef2c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,6 +22,7 @@ "next-themes": "^0.4.6", "react": "19.2.0", "react-dom": "19.2.0", + "react-hook-form": "^7.66.0", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0" }, diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 89eefa3..ae1e47d 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -96,20 +96,6 @@ @apply border-border outline-ring/50; } body { - @apply bg-background text-foreground; - } - - .gradient-text { - background: linear-gradient( - 90deg, - #3b82f6 0%, - #8b5cf6 25%, - #ec4899 50%, - #8b5cf6 75%, - #3b82f6 100% - ); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; + background-color: #0b101b; } } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index bf3bcee..b558774 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -6,17 +6,19 @@ export default function Home() {
- lnk + lnk
-
+
-

- Shorten Your Loooong Links :) +

+ + Shorten Your Loooong Links :) +

-

+

lnk is an efficient and easy-to-use URL shortening service that streamlines your online experience.

diff --git a/frontend/src/components/url-dialog.tsx b/frontend/src/components/url-dialog.tsx new file mode 100644 index 0000000..8dc351a --- /dev/null +++ b/frontend/src/components/url-dialog.tsx @@ -0,0 +1,89 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogTitle, +} from "@radix-ui/react-dialog"; +import { CheckCircle2, Copy, QrCode } from "lucide-react"; +import Image from "next/image"; +import { useState } from "react"; +import { Button } from "./ui/button"; +import { DialogHeader } from "./ui/dialog"; +import { Input } from "./ui/input"; + +export function UrlDialog() { + const [dialogOpen, setDialogOpen] = useState(false); + const [shortUrl, setShortUrl] = useState(""); + const [url, setUrl] = useState(""); + const [copied, setCopied] = useState(false); + + return ( + + + + + + Your Short URL is Ready! + + + Share this shortened link anywhere + + + +
+
+ + +
+ +
+
+ +
+ QR Code +
+
+
+ QR Code +
+
+ +
+
+ Original URL +
+
{url}
+
+ + +
+
+
+ ); +} diff --git a/frontend/src/components/url-input.tsx b/frontend/src/components/url-input.tsx new file mode 100644 index 0000000..bcc2251 --- /dev/null +++ b/frontend/src/components/url-input.tsx @@ -0,0 +1,78 @@ +import { LinkIcon } from "lucide-react"; +import { useForm } from "react-hook-form"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; + +interface UrlInputProps { + url: string; + setUrl: (url: string) => void; + requestShorten: () => void; +} + +interface FormData { + url: string; +} + +export function UrlInput({ url, setUrl, requestShorten }: UrlInputProps) { + const { + register, + handleSubmit, + formState: { isValid }, + } = useForm({ + mode: "onChange", + defaultValues: { + url, + }, + }); + + const onSubmit = (data: FormData) => { + if (isValid) { + setUrl(data.url); + requestShorten(); + } + }; + + const validateUrl = (value: string) => { + if (!value) { + return "URL is required"; + } + try { + new URL(value); + return true; + } catch { + return "Please enter a valid URL starting with http:// or https://"; + } + }; + + const { onChange, ...registerProps } = register("url", { + required: "URL is required", + validate: validateUrl, + }); + + return ( +
+
+
+ + { + onChange(e); + setUrl(e.target.value); + }} + className="bg-transparent dark:bg-transparent h-full text-foreground ml-3 sm:ml-4 outline-none border-none focus-visible:outline-none focus-visible:border-none focus-visible:ring-0 focus-visible:ring-offset-0 text-sm sm:text-base py-3 sm:py-0" + type="url" + placeholder="Enter the link here" + /> +
+ +
+
+ ); +} diff --git a/frontend/src/components/url-shortener.tsx b/frontend/src/components/url-shortener.tsx index dac7d08..753248c 100644 --- a/frontend/src/components/url-shortener.tsx +++ b/frontend/src/components/url-shortener.tsx @@ -1,174 +1,22 @@ "use client"; -import { CheckCircle2, Copy, Link2, Loader2, QrCode } from "lucide-react"; -import Image from "next/image"; import { useState } from "react"; -import { toast } from "sonner"; -import { shortenUrl } from "@/app/actions"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; +import { UrlDialog } from "./url-dialog"; +import { UrlInput } from "./url-input"; export function UrlShortener() { const [url, setUrl] = useState(""); - const [shortUrl, setShortUrl] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [dialogOpen, setDialogOpen] = useState(false); - const [copied, setCopied] = useState(false); - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - try { - new URL(url); - } catch { - toast.error("Invalid URL", { - description: - "Please enter a valid URL starting with http:// or https://", - }); - return; - } - - setIsLoading(true); - - try { - const result = await shortenUrl(url); - setShortUrl(result.shortUrl); - setDialogOpen(true); - setCopied(false); - } catch { - toast.error("Error", { - description: "Failed to shorten URL. Please try again.", - }); - } finally { - setIsLoading(false); - } - }; - - const handleCopy = async (text: string) => { - try { - await navigator.clipboard.writeText(text); - setCopied(true); - toast.success("Copied!", { - description: "Short URL copied to clipboard", - }); - setTimeout(() => setCopied(false), 2000); - } catch { - toast.error("Copy failed", { - description: "Please copy the URL manually", - }); - } + const requestShorten = async () => { + // const response = await shortenUrl(url); + console.log("banana"); }; return (
-
-
-
-
- - setUrl(e.target.value)} - className="pl-12 h-14 text-base bg-background border-2" - disabled={isLoading} - required - /> -
- -
-
-
- - - - - - - Your Short URL is Ready! - - - Share this shortened link anywhere - - - -
-
- - -
- -
-
- -
- QR Code -
-
-
- QR Code -
-
- -
-
- Original URL -
-
{url}
-
+ - -
-
-
+
); }