Formatting

This commit is contained in:
2025-06-28 00:15:51 +02:00
parent 0088b8f02a
commit 4ac986745d
22 changed files with 1180 additions and 1245 deletions

View File

@@ -1,16 +1,17 @@
import type { MetadataRoute } from 'next'
import type { MetadataRoute } from "next"
export default function manifest(): MetadataRoute.Manifest {
return {
name: "Linker - Smart URL Shortener & Link Management",
short_name: "Linker",
description: "Transform long URLs into short, shareable links. Perfect for social media, emails, and anywhere you need clean, manageable links. Get started for free!",
description:
"Transform long URLs into short, shareable links. Perfect for social media, emails, and anywhere you need clean, manageable links. Get started for free!",
start_url: "/",
display: "standalone",
icons: [{
src: '/favicon.ico',
sizes: 'any',
type: 'image/x-icon',
}],
src: "/favicon.ico",
sizes: "any",
type: "image/x-icon"
}]
}
}

View File

@@ -1,5 +1,5 @@
import { getAllUrls } from '@/lib/db/urls'
import type { MetadataRoute } from 'next'
import { getAllUrls } from "@/lib/db/urls"
import type { MetadataRoute } from "next"
export default async function robots(): Promise<MetadataRoute.Robots> {
const urls = await getAllUrls()
@@ -8,7 +8,7 @@ export default async function robots(): Promise<MetadataRoute.Robots> {
if (u.crawlable) {
return `/r/${u.slug}`
}
}).filter(v => typeof v === 'string')
}).filter(v => typeof v === "string")
return {
rules: [
@@ -17,7 +17,8 @@ export default async function robots(): Promise<MetadataRoute.Robots> {
allow: "/",
disallow: ["/api", "/dasboard", "/sign-in"],
crawlDelay: 1
}, {
},
{
userAgent: "*",
disallow: "/r/",
allow: crawlableUrls,

View File

@@ -1,15 +1,11 @@
"use client"
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
import * as React from "react"
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "lucide-react"
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
import { cn } from "@/lib/utils"
function Calendar({
className,
@@ -36,9 +32,8 @@ function Calendar({
)}
captionLayout={captionLayout}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString("default", { month: "short" }),
...formatters,
formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }),
...formatters
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
@@ -119,7 +114,7 @@ function Calendar({
defaultClassNames.disabled
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
...classNames
}}
components={{
Root: ({ className, rootRef, ...props }) => {
@@ -134,9 +129,7 @@ function Calendar({
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return (
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
)
return <ChevronLeftIcon className={cn("size-4", className)} {...props} />
}
if (orientation === "right") {
@@ -148,9 +141,7 @@ function Calendar({
)
}
return (
<ChevronDownIcon className={cn("size-4", className)} {...props} />
)
return <ChevronDownIcon className={cn("size-4", className)} {...props} />
},
DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props }) => {
@@ -162,7 +153,7 @@ function Calendar({
</td>
)
},
...components,
...components
}}
{...props}
/>
@@ -188,12 +179,10 @@ function CalendarDayButton({
variant="ghost"
size="icon"
data-day={day.date.toLocaleDateString()}
data-selected-single={
modifiers.selected &&
data-selected-single={modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
!modifiers.range_middle}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}

View File

@@ -1,26 +1,18 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
FormProvider,
useFormContext,
useFormState,
type ControllerProps,
type FieldPath,
type FieldValues,
} from "react-hook-form"
import * as React from "react"
import { Controller, type ControllerProps, type FieldPath, type FieldValues, FormProvider, useFormContext, useFormState } from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
import { cn } from "@/lib/utils"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
@@ -31,7 +23,7 @@ const FormFieldContext = React.createContext<FormFieldContextValue>(
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
@@ -61,7 +53,7 @@ const useFormField = () => {
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
...fieldState
}
}
@@ -111,11 +103,9 @@ function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
<Slot
data-slot="form-control"
id={formItemId}
aria-describedby={
!error
aria-describedby={!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
: `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
@@ -155,13 +145,4 @@ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
)
}
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField }

View File

@@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import * as React from "react"
import { cn } from "@/lib/utils"
@@ -45,4 +45,4 @@ function PopoverAnchor({
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }

View File

@@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import * as React from "react"
import { cn } from "@/lib/utils"

View File

@@ -1,8 +1,8 @@
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import * as React from "react"
import { cn } from "@/lib/utils"
@@ -127,13 +127,4 @@ function SheetDescription({
)
}
export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}
export { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger }

View File

@@ -1,29 +1,18 @@
"use client"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, VariantProps } from "class-variance-authority"
import { PanelLeftIcon } from "lucide-react"
import * as React from "react"
import { useIsMobile } from "@/hooks/use-mobile"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet"
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
import { Skeleton } from "@/components/ui/skeleton"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import { useIsMobile } from "@/hooks/use-mobile"
import { cn } from "@/lib/utils"
const SIDEBAR_COOKIE_NAME = "sidebar_state"
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
@@ -121,7 +110,7 @@ function SidebarProvider({
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
toggleSidebar
}),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
)
@@ -131,13 +120,11 @@ function SidebarProvider({
<TooltipProvider delayDuration={0}>
<div
data-slot="sidebar-wrapper"
style={
{
style={{
"--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
...style,
} as React.CSSProperties
}
...style
} as React.CSSProperties}
className={cn(
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
className
@@ -188,11 +175,9 @@ function Sidebar({
data-slot="sidebar"
data-mobile="true"
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties
}
style={{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE
} as React.CSSProperties}
side={side}
>
<SheetHeader className="sr-only">
@@ -480,18 +465,18 @@ const sidebarMenuButtonVariants = cva(
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]"
},
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
},
lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!"
}
},
defaultVariants: {
variant: "default",
size: "default",
},
size: "default"
}
}
)
@@ -528,7 +513,7 @@ function SidebarMenuButton({
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
children: tooltip
}
}
@@ -627,11 +612,9 @@ function SidebarMenuSkeleton({
<Skeleton
className="h-4 max-w-(--skeleton-width) flex-1"
data-sidebar="menu-skeleton-text"
style={
{
"--skeleton-width": width,
} as React.CSSProperties
}
style={{
"--skeleton-width": width
} as React.CSSProperties}
/>
</div>
)
@@ -722,5 +705,5 @@ export {
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
useSidebar
}

View File

@@ -10,13 +10,11 @@ const Toaster = ({ ...props }: ToasterProps) => {
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
style={
{
style={{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
} as React.CSSProperties
}
"--normal-border": "var(--border)"
} as React.CSSProperties}
{...props}
/>
)

View File

@@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as SwitchPrimitive from "@radix-ui/react-switch"
import * as React from "react"
import { cn } from "@/lib/utils"

View File

@@ -104,13 +104,4 @@ function TableCaption({
)
}
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow }

View File

@@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import * as React from "react"
import { cn } from "@/lib/utils"
@@ -58,4 +58,4 @@ function TooltipContent({
)
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }

View File

@@ -1,10 +1,10 @@
"use server"
import { revalidatePath } from "next/cache"
import { getSession } from "../auth/session"
import { insertUrl } from "../db/urls"
import { advancedUrlSchema, editUrlSchema, urlFormSchema } from "../schema/url"
import { deleteUrl as deleteUrlDb, updateUrl as updateUrlDb } from "../db/urls"
import { revalidatePath } from "next/cache"
import { advancedUrlSchema, editUrlSchema, urlFormSchema } from "../schema/url"
import { getWebsiteTitle } from "../websiteTitle"
type Response = {
@@ -68,7 +68,7 @@ export async function createAdvanceUrl(unsafeData: unknown): Promise<Response> {
...data,
slug: data.slug?.length === 0 ? undefined : data.slug,
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
})
return {

View File

@@ -1,9 +1,9 @@
import { betterAuth } from "better-auth"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import { nextCookies } from "better-auth/next-js"
import { genericOAuth } from "better-auth/plugins"
import { db } from "../drizzle/db"
import { env } from "../env/server"
import { nextCookies } from "better-auth/next-js"
export const auth = betterAuth({
database: drizzleAdapter(db, {
@@ -18,6 +18,6 @@ export const auth = betterAuth({
clientSecret: env.AUTHENTIK_CLIENT_SECRET,
discoveryUrl: env.AUTHENTIK_DISCOVERY_URL
}]
}),
})
]
})

View File

@@ -1,6 +1,6 @@
import { headers } from "next/headers"
import { auth } from "./auth"
import { redirect } from "next/navigation"
import { auth } from "./auth"
export async function getSession() {
const session = await auth.api.getSession({

View File

@@ -1,8 +1,8 @@
import { eq, desc, count } from "drizzle-orm";
import { db } from "../drizzle/db";
import { urls, visits } from "../drizzle/schema";
import { cacheTag } from "next/dist/server/use-cache/cache-tag";
import { revalidateTag } from "next/cache";
import { count, desc, eq } from "drizzle-orm"
import { revalidateTag } from "next/cache"
import { cacheTag } from "next/dist/server/use-cache/cache-tag"
import { db } from "../drizzle/db"
import { urls, visits } from "../drizzle/schema"
export async function getAllUrls() {
"use cache"

View File

@@ -1,10 +1,10 @@
export function generateRandomSlug(): string {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
let result = ""
for (let i = 0; i < 5; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
result += characters.charAt(Math.floor(Math.random() * characters.length))
}
return result;
return result
}

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod"
export const urlFormSchema = z.object({
url: z.string().url("Please enter a valid URL"),
@@ -12,7 +12,7 @@ export const advancedUrlSchema = z.object({
maxVisits: z.number().int().positive().nullable(),
expDate: z.date().optional(),
forwardQueryParams: z.boolean(),
crawlable: z.boolean(),
crawlable: z.boolean()
})
export const editUrlSchema = z.object({
@@ -22,5 +22,5 @@ export const editUrlSchema = z.object({
maxVisits: z.number().int().positive().nullable(),
expDate: z.date().nullable(),
forwardQueryParams: z.boolean(),
crawlable: z.boolean(),
crawlable: z.boolean()
})

View File

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

View File

@@ -1,13 +1,13 @@
import { NextRequest, NextResponse } from "next/server";
import { getSessionCookie } from "better-auth/cookies";
import { getSessionCookie } from "better-auth/cookies"
import { NextRequest, NextResponse } from "next/server"
export async function middleware(request: NextRequest) {
const sessionCookie = getSessionCookie(request);
const sessionCookie = getSessionCookie(request)
if (!sessionCookie) {
return NextResponse.redirect(new URL("/", request.url));
return NextResponse.redirect(new URL("/", request.url))
}
return NextResponse.next();
return NextResponse.next()
}
export const config = { matcher: ["/dashboard"] };
export const config = { matcher: ["/dashboard"] }