Moved things and added title extraction
This commit is contained in:
@@ -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() {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
31
src/lib/websiteTitle.ts
Normal 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(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/ /g, ' ')
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
return title || null
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user