Moved things and added title extraction

This commit is contained in:
2025-06-27 14:30:39 +02:00
parent 50cc087bed
commit e4c382a4dc
18 changed files with 43 additions and 13 deletions

View File

@@ -1,4 +1,4 @@
import { AdvancedUrlFormCard } from "@/components/dashboard/advanced-url-form-card" import { AdvancedUrlFormCard } from "@/app/(admin)/dashboard/_components/advanced-url-form-card"
import { getSession } from "@/lib/auth/session" import { getSession } from "@/lib/auth/session"
export default async function DashboardCreatePage() { export default async function DashboardCreatePage() {

View File

@@ -1,4 +1,4 @@
import { DashboardSidebar } from "@/components/dashboard/sidebar" import { DashboardSidebar } from "@/app/(admin)/dashboard/_components/sidebar"
import { SidebarProvider } from "@/components/ui/sidebar" import { SidebarProvider } from "@/components/ui/sidebar"
import { ReactNode } from "react" import { ReactNode } from "react"

View File

@@ -1,4 +1,4 @@
import { UrlsDataTable } from "@/components/dashboard/urls-data-table" import { UrlsDataTable } from "@/app/(admin)/dashboard/_components/urls-data-table"
import { getSession } from "@/lib/auth/session" import { getSession } from "@/lib/auth/session"
import { getAllUrls } from "@/lib/db/urls" import { getAllUrls } from "@/lib/db/urls"

View File

@@ -1,5 +1,5 @@
import { StatsCard } from "@/components/dashboard/stats-card" import { StatsCard } from "@/app/(admin)/dashboard/_components/stats-card"
import { UrlFormCard } from "@/components/dashboard/url-form-card" import { UrlFormCard } from "@/app/(admin)/dashboard/_components/url-form-card"
import { getSession } from "@/lib/auth/session" import { getSession } from "@/lib/auth/session"
import { getDashboardStats } from "@/lib/dashboard/stats" import { getDashboardStats } from "@/lib/dashboard/stats"
import { LinkIcon, MousePointerClick, TrendingUp } from "lucide-react" import { LinkIcon, MousePointerClick, TrendingUp } from "lucide-react"

View File

@@ -1,5 +1,5 @@
import { UserDropdown } from "@/components/auth/user-dropdown"
import { ThemeToggle } from "@/components/theme-toggle" import { ThemeToggle } from "@/components/theme-toggle"
import { UserDropdown } from "@/components/user-dropdown"
import Link from "next/link" import Link from "next/link"
import { ReactNode } from "react" import { ReactNode } from "react"

View File

@@ -1,4 +1,4 @@
import { OAuthSignInButton } from "@/components/auth/signin-button" import { OAuthSignInButton } from "@/app/sign-in/_components/signin-button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { getSession } from "@/lib/auth/session" import { getSession } from "@/lib/auth/session"
import { LogIn } from "lucide-react" import { LogIn } from "lucide-react"
@@ -11,7 +11,7 @@ export default async function SignInPage() {
} }
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 p-4"> <div className="min-h-screen flex items-center justify-center bg-background p-4">
<Card className="w-full max-w-md shadow-xl"> <Card className="w-full max-w-md shadow-xl">
<CardHeader className="space-y-1 text-center"> <CardHeader className="space-y-1 text-center">
<div className="flex justify-center mb-4"> <div className="flex justify-center mb-4">

View File

@@ -12,7 +12,6 @@ import { cn } from "@/lib/utils"
interface DatePickerProps { interface DatePickerProps {
value?: Date value?: Date
onChange?: (date: Date | undefined) => void onChange?: (date: Date | undefined) => void
placeholder?: string
disabled?: boolean disabled?: boolean
className?: string className?: string
} }
@@ -20,7 +19,6 @@ interface DatePickerProps {
export function DatePicker({ export function DatePicker({
value, value,
onChange, onChange,
placeholder = "Pick a date",
disabled = false, disabled = false,
className className
}: DatePickerProps) { }: DatePickerProps) {
@@ -37,7 +35,7 @@ export function DatePicker({
disabled={disabled} disabled={disabled}
> >
<CalendarIcon className="mr-2 h-4 w-4" /> <CalendarIcon className="mr-2 h-4 w-4" />
{value ? format(value, "PPP") : <span>{placeholder}</span>} {value ? format(value, "PPP") : <span>Pick a date</span>}
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start"> <PopoverContent className="w-auto p-0" align="start">
@@ -45,7 +43,6 @@ export function DatePicker({
mode="single" mode="single"
selected={value} selected={value}
onSelect={onChange} onSelect={onChange}
initialFocus
/> />
</PopoverContent> </PopoverContent>
</Popover> </Popover>

View File

@@ -6,6 +6,7 @@ import { insertUrl } from "../db/urls"
import { advancedUrlSchema, urlFormSchema } from "../schema/url" import { advancedUrlSchema, urlFormSchema } from "../schema/url"
import { deleteUrl as deleteUrlDb } from "../db/urls" import { deleteUrl as deleteUrlDb } from "../db/urls"
import { revalidatePath } from "next/cache" import { revalidatePath } from "next/cache"
import { getWebsiteTitle } from "../websiteTitle"
type Response = { type Response = {
error: boolean error: boolean
@@ -34,6 +35,7 @@ export async function addUrl(unsafeData: unknown): Promise<Response> {
await insertUrl({ await insertUrl({
...data, ...data,
slug: data.slug.length === 0 ? undefined : data.slug, slug: data.slug.length === 0 ? undefined : data.slug,
title: await getWebsiteTitle(data.url)
}) })
revalidatePath("/dashboard") revalidatePath("/dashboard")
@@ -66,7 +68,7 @@ export async function createAdvanceUrl(unsafeData: unknown): Promise<Response> {
await insertUrl({ await insertUrl({
...data, ...data,
slug: data.slug?.length === 0 ? undefined : data.slug, slug: data.slug?.length === 0 ? undefined : data.slug,
title: data.title?.length === 0 ? undefined : data.title, title: data.title?.length === 0 ? await getWebsiteTitle(data.url) : data.title,
maxVisits: data.maxVisits > 0 ? data.maxVisits : undefined, maxVisits: data.maxVisits > 0 ? data.maxVisits : undefined,
}) })

31
src/lib/websiteTitle.ts Normal file
View File

@@ -0,0 +1,31 @@
export async function getWebsiteTitle(url: string): Promise<string | null> {
const res = await fetch(url, { redirect: 'follow' })
if (!res.ok) {
return null
}
const contentType = res.headers.get('content-type')
if (!contentType || !contentType.includes('text/html')) {
return null
}
const html = await res.text()
const titleMatch = html.match(/<title[^>]*>([^<]*)<\/title>/i)
if (titleMatch && titleMatch[1]) {
const title = titleMatch[1]
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&nbsp;/g, ' ')
.trim()
return title || null
}
return null
}