diff --git a/src/app/(admin)/dashboard/page.tsx b/src/app/(admin)/dashboard/page.tsx index fa65733..601baaa 100644 --- a/src/app/(admin)/dashboard/page.tsx +++ b/src/app/(admin)/dashboard/page.tsx @@ -16,16 +16,10 @@ export default async function Dashboard() { // Determine the most visited URL display value const mostVisitedDisplay = stats.mostVisitedUrl ? stats.mostVisitedUrl.visitCount > 0 - ? `${stats.mostVisitedUrl.title} (${stats.mostVisitedUrl.visitCount})` + ? `${stats.mostVisitedUrl.title || stats.mostVisitedUrl.slug || "Untitled"} (${stats.mostVisitedUrl.visitCount})` : "No visits" : "No URLs" - const mostVisitedDescription = stats.mostVisitedUrl - ? stats.mostVisitedUrl.visitCount > 0 - ? `/${stats.mostVisitedUrl.slug} - ${stats.mostVisitedUrl.visitCount} visits` - : "No URLs have been visited yet" - : "Create your first shortened URL" - return (

Dashboard

@@ -46,7 +40,7 @@ export default async function Dashboard() { title="Most Visited URL" value={mostVisitedDisplay} icon={} - description={mostVisitedDescription} + description="Most popular shortened link" />
diff --git a/src/app/r/[slug]/not-found.tsx b/src/app/r/[slug]/not-found.tsx new file mode 100644 index 0000000..cd762e1 --- /dev/null +++ b/src/app/r/[slug]/not-found.tsx @@ -0,0 +1,21 @@ +export default function NotFound() { + return ( +
+
+

404

+

+ Link Not Found +

+

+ The link you're looking for doesn't exist or has expired. +

+ + Go Home + +
+
+ ) +} diff --git a/src/app/r/[slug]/page.tsx b/src/app/r/[slug]/page.tsx new file mode 100644 index 0000000..2ba5da9 --- /dev/null +++ b/src/app/r/[slug]/page.tsx @@ -0,0 +1,41 @@ +import { getUrlBySlug, getVisitsBySlugById, trackVisit } from "@/lib/db/urls" +import { headers } from "next/headers" +import { notFound, redirect } from "next/navigation" + +export default async function RedirectPage({ + params +}: { + params: Promise<{ slug: string }> +}) { + const { slug } = await params + + const urlRecord = await getUrlBySlug(slug) + + if (!urlRecord) { + notFound() + } + + const visits = await getVisitsBySlugById(urlRecord.id) + + if (urlRecord.expDate && new Date() > urlRecord.expDate) { + notFound() + } + + if (urlRecord.maxVisits && visits[0].count >= urlRecord.maxVisits) { + notFound() + } + + const headersList = await headers() + const userAgent = headersList.get("user-agent") || "Unknown" + const forwardedFor = headersList.get("x-forwarded-for") + const realIp = headersList.get("x-real-ip") + const ipAddress = forwardedFor?.split(",")[0] || realIp || "Unknown" + + await trackVisit({ + urlId: urlRecord.id, + ipAddress: ipAddress, + userAgent + }) + + redirect(urlRecord.url) +} diff --git a/src/components/dashboard/advanced-url-form-card.tsx b/src/components/dashboard/advanced-url-form-card.tsx index 56dd39d..ae3c5d6 100644 --- a/src/components/dashboard/advanced-url-form-card.tsx +++ b/src/components/dashboard/advanced-url-form-card.tsx @@ -207,7 +207,7 @@ export function AdvancedUrlFormCard() { className="w-full" disabled={form.formState.isSubmitting} > - {form.formState.isSubmitting ? "Creating..." : "Create Advanced Short Link"} + {form.formState.isSubmitting ? "Creating..." : "Create Short Link"} diff --git a/src/lib/db/urls.ts b/src/lib/db/urls.ts index 2059529..10a1f3f 100644 --- a/src/lib/db/urls.ts +++ b/src/lib/db/urls.ts @@ -1,6 +1,6 @@ import { eq, desc } from "drizzle-orm"; import { db } from "../drizzle/db"; -import { urls } from "../drizzle/schema"; +import { urls, visits } from "../drizzle/schema"; export function getAllUrls() { return db.query.urls.findMany({ @@ -8,6 +8,12 @@ export function getAllUrls() { }) } +export function getUrlBySlug(slug: string) { + return db.query.urls.findFirst({ + where: eq(urls.slug, slug) + }) +} + export function insertUrl(data: typeof urls.$inferInsert) { return db.insert(urls).values(data) } @@ -18,4 +24,8 @@ export function updateUrl(id: string, data: Omit urls.id), - ipAdress: varchar("ip_address").notNull(), + ipAddress: varchar("ip_address").notNull(), userAgent: varchar("user_agent").notNull(), createdAt, updatedAt