Formatting
This commit is contained in:
@@ -1,16 +1,17 @@
|
|||||||
import type { MetadataRoute } from 'next'
|
import type { MetadataRoute } from "next"
|
||||||
|
|
||||||
export default function manifest(): MetadataRoute.Manifest {
|
export default function manifest(): MetadataRoute.Manifest {
|
||||||
return {
|
return {
|
||||||
name: "Linker - Smart URL Shortener & Link Management",
|
name: "Linker - Smart URL Shortener & Link Management",
|
||||||
short_name: "Linker",
|
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: "/",
|
start_url: "/",
|
||||||
display: "standalone",
|
display: "standalone",
|
||||||
icons: [{
|
icons: [{
|
||||||
src: '/favicon.ico',
|
src: "/favicon.ico",
|
||||||
sizes: 'any',
|
sizes: "any",
|
||||||
type: 'image/x-icon',
|
type: "image/x-icon"
|
||||||
}],
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getAllUrls } from '@/lib/db/urls'
|
import { getAllUrls } from "@/lib/db/urls"
|
||||||
import type { MetadataRoute } from 'next'
|
import type { MetadataRoute } from "next"
|
||||||
|
|
||||||
export default async function robots(): Promise<MetadataRoute.Robots> {
|
export default async function robots(): Promise<MetadataRoute.Robots> {
|
||||||
const urls = await getAllUrls()
|
const urls = await getAllUrls()
|
||||||
@@ -8,7 +8,7 @@ export default async function robots(): Promise<MetadataRoute.Robots> {
|
|||||||
if (u.crawlable) {
|
if (u.crawlable) {
|
||||||
return `/r/${u.slug}`
|
return `/r/${u.slug}`
|
||||||
}
|
}
|
||||||
}).filter(v => typeof v === 'string')
|
}).filter(v => typeof v === "string")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -17,7 +17,8 @@ export default async function robots(): Promise<MetadataRoute.Robots> {
|
|||||||
allow: "/",
|
allow: "/",
|
||||||
disallow: ["/api", "/dasboard", "/sign-in"],
|
disallow: ["/api", "/dasboard", "/sign-in"],
|
||||||
crawlDelay: 1
|
crawlDelay: 1
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
userAgent: "*",
|
userAgent: "*",
|
||||||
disallow: "/r/",
|
disallow: "/r/",
|
||||||
allow: crawlableUrls,
|
allow: crawlableUrls,
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import {
|
|
||||||
ChevronDownIcon,
|
|
||||||
ChevronLeftIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Button, buttonVariants } from "@/components/ui/button"
|
import { Button, buttonVariants } from "@/components/ui/button"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
function Calendar({
|
function Calendar({
|
||||||
className,
|
className,
|
||||||
@@ -36,9 +32,8 @@ function Calendar({
|
|||||||
)}
|
)}
|
||||||
captionLayout={captionLayout}
|
captionLayout={captionLayout}
|
||||||
formatters={{
|
formatters={{
|
||||||
formatMonthDropdown: (date) =>
|
formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }),
|
||||||
date.toLocaleString("default", { month: "short" }),
|
...formatters
|
||||||
...formatters,
|
|
||||||
}}
|
}}
|
||||||
classNames={{
|
classNames={{
|
||||||
root: cn("w-fit", defaultClassNames.root),
|
root: cn("w-fit", defaultClassNames.root),
|
||||||
@@ -119,7 +114,7 @@ function Calendar({
|
|||||||
defaultClassNames.disabled
|
defaultClassNames.disabled
|
||||||
),
|
),
|
||||||
hidden: cn("invisible", defaultClassNames.hidden),
|
hidden: cn("invisible", defaultClassNames.hidden),
|
||||||
...classNames,
|
...classNames
|
||||||
}}
|
}}
|
||||||
components={{
|
components={{
|
||||||
Root: ({ className, rootRef, ...props }) => {
|
Root: ({ className, rootRef, ...props }) => {
|
||||||
@@ -134,9 +129,7 @@ function Calendar({
|
|||||||
},
|
},
|
||||||
Chevron: ({ className, orientation, ...props }) => {
|
Chevron: ({ className, orientation, ...props }) => {
|
||||||
if (orientation === "left") {
|
if (orientation === "left") {
|
||||||
return (
|
return <ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
||||||
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orientation === "right") {
|
if (orientation === "right") {
|
||||||
@@ -148,9 +141,7 @@ function Calendar({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <ChevronDownIcon className={cn("size-4", className)} {...props} />
|
||||||
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
DayButton: CalendarDayButton,
|
DayButton: CalendarDayButton,
|
||||||
WeekNumber: ({ children, ...props }) => {
|
WeekNumber: ({ children, ...props }) => {
|
||||||
@@ -162,7 +153,7 @@ function Calendar({
|
|||||||
</td>
|
</td>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
...components,
|
...components
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -188,12 +179,10 @@ function CalendarDayButton({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
data-day={day.date.toLocaleDateString()}
|
data-day={day.date.toLocaleDateString()}
|
||||||
data-selected-single={
|
data-selected-single={modifiers.selected &&
|
||||||
modifiers.selected &&
|
|
||||||
!modifiers.range_start &&
|
!modifiers.range_start &&
|
||||||
!modifiers.range_end &&
|
!modifiers.range_end &&
|
||||||
!modifiers.range_middle
|
!modifiers.range_middle}
|
||||||
}
|
|
||||||
data-range-start={modifiers.range_start}
|
data-range-start={modifiers.range_start}
|
||||||
data-range-end={modifiers.range_end}
|
data-range-end={modifiers.range_end}
|
||||||
data-range-middle={modifiers.range_middle}
|
data-range-middle={modifiers.range_middle}
|
||||||
|
|||||||
@@ -1,26 +1,18 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import {
|
import * as React from "react"
|
||||||
Controller,
|
import { Controller, type ControllerProps, type FieldPath, type FieldValues, FormProvider, useFormContext, useFormState } from "react-hook-form"
|
||||||
FormProvider,
|
|
||||||
useFormContext,
|
|
||||||
useFormState,
|
|
||||||
type ControllerProps,
|
|
||||||
type FieldPath,
|
|
||||||
type FieldValues,
|
|
||||||
} from "react-hook-form"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const Form = FormProvider
|
const Form = FormProvider
|
||||||
|
|
||||||
type FormFieldContextValue<
|
type FormFieldContextValue<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
> = {
|
> = {
|
||||||
name: TName
|
name: TName
|
||||||
}
|
}
|
||||||
@@ -31,7 +23,7 @@ const FormFieldContext = React.createContext<FormFieldContextValue>(
|
|||||||
|
|
||||||
const FormField = <
|
const FormField = <
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
>({
|
>({
|
||||||
...props
|
...props
|
||||||
}: ControllerProps<TFieldValues, TName>) => {
|
}: ControllerProps<TFieldValues, TName>) => {
|
||||||
@@ -61,7 +53,7 @@ const useFormField = () => {
|
|||||||
formItemId: `${id}-form-item`,
|
formItemId: `${id}-form-item`,
|
||||||
formDescriptionId: `${id}-form-item-description`,
|
formDescriptionId: `${id}-form-item-description`,
|
||||||
formMessageId: `${id}-form-item-message`,
|
formMessageId: `${id}-form-item-message`,
|
||||||
...fieldState,
|
...fieldState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,11 +103,9 @@ function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
|||||||
<Slot
|
<Slot
|
||||||
data-slot="form-control"
|
data-slot="form-control"
|
||||||
id={formItemId}
|
id={formItemId}
|
||||||
aria-describedby={
|
aria-describedby={!error
|
||||||
!error
|
|
||||||
? `${formDescriptionId}`
|
? `${formDescriptionId}`
|
||||||
: `${formDescriptionId} ${formMessageId}`
|
: `${formDescriptionId} ${formMessageId}`}
|
||||||
}
|
|
||||||
aria-invalid={!!error}
|
aria-invalid={!!error}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -155,13 +145,4 @@ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField }
|
||||||
useFormField,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormMessage,
|
|
||||||
FormField,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
@@ -45,4 +45,4 @@ function PopoverAnchor({
|
|||||||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||||
import { XIcon } from "lucide-react"
|
import { XIcon } from "lucide-react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
@@ -127,13 +127,4 @@ function SheetDescription({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger }
|
||||||
Sheet,
|
|
||||||
SheetTrigger,
|
|
||||||
SheetClose,
|
|
||||||
SheetContent,
|
|
||||||
SheetHeader,
|
|
||||||
SheetFooter,
|
|
||||||
SheetTitle,
|
|
||||||
SheetDescription,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,29 +1,18 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import { cva, VariantProps } from "class-variance-authority"
|
import { cva, VariantProps } from "class-variance-authority"
|
||||||
import { PanelLeftIcon } from "lucide-react"
|
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 { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import {
|
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
|
||||||
Sheet,
|
|
||||||
SheetContent,
|
|
||||||
SheetDescription,
|
|
||||||
SheetHeader,
|
|
||||||
SheetTitle,
|
|
||||||
} from "@/components/ui/sheet"
|
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
import {
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
||||||
Tooltip,
|
import { useIsMobile } from "@/hooks/use-mobile"
|
||||||
TooltipContent,
|
import { cn } from "@/lib/utils"
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from "@/components/ui/tooltip"
|
|
||||||
|
|
||||||
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
||||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
||||||
@@ -121,7 +110,7 @@ function SidebarProvider({
|
|||||||
isMobile,
|
isMobile,
|
||||||
openMobile,
|
openMobile,
|
||||||
setOpenMobile,
|
setOpenMobile,
|
||||||
toggleSidebar,
|
toggleSidebar
|
||||||
}),
|
}),
|
||||||
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
|
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
|
||||||
)
|
)
|
||||||
@@ -131,13 +120,11 @@ function SidebarProvider({
|
|||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<div
|
<div
|
||||||
data-slot="sidebar-wrapper"
|
data-slot="sidebar-wrapper"
|
||||||
style={
|
style={{
|
||||||
{
|
|
||||||
"--sidebar-width": SIDEBAR_WIDTH,
|
"--sidebar-width": SIDEBAR_WIDTH,
|
||||||
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
||||||
...style,
|
...style
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties}
|
||||||
}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
|
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
|
||||||
className
|
className
|
||||||
@@ -188,11 +175,9 @@ function Sidebar({
|
|||||||
data-slot="sidebar"
|
data-slot="sidebar"
|
||||||
data-mobile="true"
|
data-mobile="true"
|
||||||
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
|
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
|
||||||
style={
|
style={{
|
||||||
{
|
"--sidebar-width": SIDEBAR_WIDTH_MOBILE
|
||||||
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
|
} as React.CSSProperties}
|
||||||
} as React.CSSProperties
|
|
||||||
}
|
|
||||||
side={side}
|
side={side}
|
||||||
>
|
>
|
||||||
<SheetHeader className="sr-only">
|
<SheetHeader className="sr-only">
|
||||||
@@ -480,18 +465,18 @@ const sidebarMenuButtonVariants = cva(
|
|||||||
variant: {
|
variant: {
|
||||||
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
||||||
outline:
|
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: {
|
size: {
|
||||||
default: "h-8 text-sm",
|
default: "h-8 text-sm",
|
||||||
sm: "h-7 text-xs",
|
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: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default"
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -528,7 +513,7 @@ function SidebarMenuButton({
|
|||||||
|
|
||||||
if (typeof tooltip === "string") {
|
if (typeof tooltip === "string") {
|
||||||
tooltip = {
|
tooltip = {
|
||||||
children: tooltip,
|
children: tooltip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,11 +612,9 @@ function SidebarMenuSkeleton({
|
|||||||
<Skeleton
|
<Skeleton
|
||||||
className="h-4 max-w-(--skeleton-width) flex-1"
|
className="h-4 max-w-(--skeleton-width) flex-1"
|
||||||
data-sidebar="menu-skeleton-text"
|
data-sidebar="menu-skeleton-text"
|
||||||
style={
|
style={{
|
||||||
{
|
"--skeleton-width": width
|
||||||
"--skeleton-width": width,
|
} as React.CSSProperties}
|
||||||
} as React.CSSProperties
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -722,5 +705,5 @@ export {
|
|||||||
SidebarRail,
|
SidebarRail,
|
||||||
SidebarSeparator,
|
SidebarSeparator,
|
||||||
SidebarTrigger,
|
SidebarTrigger,
|
||||||
useSidebar,
|
useSidebar
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,11 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
|||||||
<Sonner
|
<Sonner
|
||||||
theme={theme as ToasterProps["theme"]}
|
theme={theme as ToasterProps["theme"]}
|
||||||
className="toaster group"
|
className="toaster group"
|
||||||
style={
|
style={{
|
||||||
{
|
|
||||||
"--normal-bg": "var(--popover)",
|
"--normal-bg": "var(--popover)",
|
||||||
"--normal-text": "var(--popover-foreground)",
|
"--normal-text": "var(--popover-foreground)",
|
||||||
"--normal-border": "var(--border)",
|
"--normal-border": "var(--border)"
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties}
|
||||||
}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
|||||||
@@ -104,13 +104,4 @@ function TableCaption({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow }
|
||||||
Table,
|
|
||||||
TableHeader,
|
|
||||||
TableBody,
|
|
||||||
TableFooter,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
TableCell,
|
|
||||||
TableCaption,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
@@ -58,4 +58,4 @@ function TooltipContent({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use server"
|
"use server"
|
||||||
|
|
||||||
|
import { revalidatePath } from "next/cache"
|
||||||
import { getSession } from "../auth/session"
|
import { getSession } from "../auth/session"
|
||||||
import { insertUrl } from "../db/urls"
|
import { insertUrl } from "../db/urls"
|
||||||
import { advancedUrlSchema, editUrlSchema, urlFormSchema } from "../schema/url"
|
|
||||||
import { deleteUrl as deleteUrlDb, updateUrl as updateUrlDb } from "../db/urls"
|
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"
|
import { getWebsiteTitle } from "../websiteTitle"
|
||||||
|
|
||||||
type Response = {
|
type Response = {
|
||||||
@@ -68,7 +68,7 @@ export async function createAdvanceUrl(unsafeData: unknown): Promise<Response> {
|
|||||||
...data,
|
...data,
|
||||||
slug: data.slug?.length === 0 ? undefined : data.slug,
|
slug: data.slug?.length === 0 ? undefined : data.slug,
|
||||||
title: data.title?.length === 0 ? await getWebsiteTitle(data.url) : 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
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { betterAuth } from "better-auth"
|
import { betterAuth } from "better-auth"
|
||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle"
|
import { drizzleAdapter } from "better-auth/adapters/drizzle"
|
||||||
|
import { nextCookies } from "better-auth/next-js"
|
||||||
import { genericOAuth } from "better-auth/plugins"
|
import { genericOAuth } from "better-auth/plugins"
|
||||||
import { db } from "../drizzle/db"
|
import { db } from "../drizzle/db"
|
||||||
import { env } from "../env/server"
|
import { env } from "../env/server"
|
||||||
import { nextCookies } from "better-auth/next-js"
|
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: drizzleAdapter(db, {
|
database: drizzleAdapter(db, {
|
||||||
@@ -18,6 +18,6 @@ export const auth = betterAuth({
|
|||||||
clientSecret: env.AUTHENTIK_CLIENT_SECRET,
|
clientSecret: env.AUTHENTIK_CLIENT_SECRET,
|
||||||
discoveryUrl: env.AUTHENTIK_DISCOVERY_URL
|
discoveryUrl: env.AUTHENTIK_DISCOVERY_URL
|
||||||
}]
|
}]
|
||||||
}),
|
})
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { headers } from "next/headers"
|
import { headers } from "next/headers"
|
||||||
import { auth } from "./auth"
|
|
||||||
import { redirect } from "next/navigation"
|
import { redirect } from "next/navigation"
|
||||||
|
import { auth } from "./auth"
|
||||||
|
|
||||||
export async function getSession() {
|
export async function getSession() {
|
||||||
const session = await auth.api.getSession({
|
const session = await auth.api.getSession({
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { eq, desc, count } from "drizzle-orm";
|
import { count, desc, eq } from "drizzle-orm"
|
||||||
import { db } from "../drizzle/db";
|
import { revalidateTag } from "next/cache"
|
||||||
import { urls, visits } from "../drizzle/schema";
|
import { cacheTag } from "next/dist/server/use-cache/cache-tag"
|
||||||
import { cacheTag } from "next/dist/server/use-cache/cache-tag";
|
import { db } from "../drizzle/db"
|
||||||
import { revalidateTag } from "next/cache";
|
import { urls, visits } from "../drizzle/schema"
|
||||||
|
|
||||||
export async function getAllUrls() {
|
export async function getAllUrls() {
|
||||||
"use cache"
|
"use cache"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
export function generateRandomSlug(): string {
|
export function generateRandomSlug(): string {
|
||||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
let result = '';
|
let result = ""
|
||||||
|
|
||||||
for (let i = 0; i < 5; i++) {
|
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
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod"
|
||||||
|
|
||||||
export const urlFormSchema = z.object({
|
export const urlFormSchema = z.object({
|
||||||
url: z.string().url("Please enter a valid URL"),
|
url: z.string().url("Please enter a valid URL"),
|
||||||
@@ -12,7 +12,7 @@ export const advancedUrlSchema = z.object({
|
|||||||
maxVisits: z.number().int().positive().nullable(),
|
maxVisits: z.number().int().positive().nullable(),
|
||||||
expDate: z.date().optional(),
|
expDate: z.date().optional(),
|
||||||
forwardQueryParams: z.boolean(),
|
forwardQueryParams: z.boolean(),
|
||||||
crawlable: z.boolean(),
|
crawlable: z.boolean()
|
||||||
})
|
})
|
||||||
|
|
||||||
export const editUrlSchema = z.object({
|
export const editUrlSchema = z.object({
|
||||||
@@ -22,5 +22,5 @@ export const editUrlSchema = z.object({
|
|||||||
maxVisits: z.number().int().positive().nullable(),
|
maxVisits: z.number().int().positive().nullable(),
|
||||||
expDate: z.date().nullable(),
|
expDate: z.date().nullable(),
|
||||||
forwardQueryParams: z.boolean(),
|
forwardQueryParams: z.boolean(),
|
||||||
crawlable: z.boolean(),
|
crawlable: z.boolean()
|
||||||
})
|
})
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
export async function getWebsiteTitle(url: string): Promise<string | null> {
|
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) {
|
if (!res.ok) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = res.headers.get('content-type')
|
const contentType = res.headers.get("content-type")
|
||||||
if (!contentType || !contentType.includes('text/html')) {
|
if (!contentType || !contentType.includes("text/html")) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@ export async function getWebsiteTitle(url: string): Promise<string | null> {
|
|||||||
|
|
||||||
if (titleMatch && titleMatch[1]) {
|
if (titleMatch && titleMatch[1]) {
|
||||||
const title = titleMatch[1]
|
const title = titleMatch[1]
|
||||||
.replace(/</g, '<')
|
.replace(/</g, "<")
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, ">")
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, "&")
|
||||||
.replace(/"/g, '"')
|
.replace(/"/g, "\"")
|
||||||
.replace(/'/g, "'")
|
.replace(/'/g, "'")
|
||||||
.replace(/ /g, ' ')
|
.replace(/ /g, " ")
|
||||||
.trim()
|
.trim()
|
||||||
|
|
||||||
return title || null
|
return title || null
|
||||||
|
|||||||
@@ -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) {
|
export async function middleware(request: NextRequest) {
|
||||||
const sessionCookie = getSessionCookie(request);
|
const sessionCookie = getSessionCookie(request)
|
||||||
if (!sessionCookie) {
|
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"] }
|
||||||
|
|||||||
Reference in New Issue
Block a user