draupnir4all-web

git clone git://archive.git.mtrnord.blog/MTRNord/draupnir4all-web.git
Log | Files | Refs | README | LICENSE

commit 67d05c891d769795fa8cdbdca6eb0cef67a30fed
parent 31606ea0d2f344c5d33cd2109ff5ad2546d2a219
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sat, 26 Apr 2025 21:56:59 +0200

react cleanups, Dockerfile and SSR improvements

Signed-off-by: MTRNord <mtrnord1@gmail.com>

Diffstat:
A.dockerignore | 8++++++++
ADockerfile | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnext.config.ts | 3++-
Msrc/app/api/auth/login/route.ts | 7++-----
Msrc/app/api/auth/logout/route.ts | 5++---
Msrc/app/api/auth/register/route.ts | 7++-----
Msrc/app/api/auth/session/route.ts | 6+++---
Msrc/app/api/verify-matrix-token/route.ts | 2++
Msrc/app/dashboard/analytics/page.tsx | 32++------------------------------
Msrc/app/dashboard/layout.tsx | 74+++-----------------------------------------------------------------------
Msrc/app/dashboard/overview/page.tsx | 42+++++++++++++++++++++---------------------
Msrc/app/dashboard/settings/page.tsx | 54+++++-------------------------------------------------
Msrc/app/login/page.tsx | 2++
Msrc/app/page.tsx | 2++
Msrc/app/register/page.tsx | 2++
Asrc/components/analytics/info-card-with-trend.tsx | 32++++++++++++++++++++++++++++++++
Msrc/components/dashboard/dashboard-header.tsx | 8+-------
Msrc/components/matrix-login-form.tsx | 4++++
Msrc/components/ui/alert.tsx | 17+++++++++--------
Msrc/components/ui/avatar.tsx | 37++++++++++++++++++-------------------
Msrc/components/ui/badge.tsx | 6+++---
Msrc/components/ui/breadcrumb.tsx | 28++++++++++++++--------------
Msrc/components/ui/button.tsx | 8++++----
Msrc/components/ui/card.tsx | 27+++++++++++++--------------
Msrc/components/ui/checkbox.tsx | 20++++++++++----------
Msrc/components/ui/dialog.tsx | 62+++++++++++++++++++++++++++++++-------------------------------
Msrc/components/ui/dropdown-menu.tsx | 116++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/components/ui/input.tsx | 8++++----
Msrc/components/ui/label.tsx | 16++++++++--------
Msrc/components/ui/pagination.tsx | 20++++++++++----------
Msrc/components/ui/popover.tsx | 22+++++++++++-----------
Msrc/components/ui/scroll-area.tsx | 40++++++++++++++++++++--------------------
Msrc/components/ui/select.tsx | 112++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/components/ui/separator.tsx | 14+++++++-------
Msrc/components/ui/sheet.tsx | 64++++++++++++++++++++++++++++++++--------------------------------
Msrc/components/ui/skeleton.tsx | 3++-
Msrc/components/ui/table.tsx | 34+++++++++++++++++-----------------
Msrc/components/ui/tabs.tsx | 36++++++++++++++++++------------------
Msrc/components/ui/textarea.tsx | 4++--
Msrc/components/ui/use-mobile.tsx | 6+++---
Dsrc/components/ui/use-toast.ts | 195-------------------------------------------------------------------------------
Msrc/lib/auth.ts | 96+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/middleware.ts | 2+-
43 files changed, 566 insertions(+), 780 deletions(-)

diff --git a/.dockerignore b/.dockerignore @@ -0,0 +1,7 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log +README.md +.next +.git +\ No newline at end of file diff --git a/Dockerfile b/Dockerfile @@ -0,0 +1,62 @@ +# syntax=docker.io/docker/dockerfile:1 + +FROM node:23-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./ +RUN \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ + else echo "Lockfile not found." && exit 1; \ + fi + + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN \ + if [ -f yarn.lock ]; then yarn run build; \ + elif [ -f package-lock.json ]; then npm run build; \ + elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ + else echo "Lockfile not found." && exit 1; \ + fi + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 + +# server.js is created by next build from the standalone output +# https://nextjs.org/docs/pages/api-reference/config/next-config-js/output +ENV HOSTNAME="0.0.0.0" +CMD ["node", "server.js"] +\ No newline at end of file diff --git a/next.config.ts b/next.config.ts @@ -5,7 +5,8 @@ const nextConfig: NextConfig = { experimental: { ppr: 'incremental', useCache: true, - } + }, + output: 'standalone' }; const withBundleAnalyzer = analyzer({ diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts @@ -1,5 +1,5 @@ import { type NextRequest, NextResponse } from "next/server" -import { createSession, setSessionCookie } from "@/lib/auth" +import { createSession } from "@/lib/auth" export async function POST(request: NextRequest) { try { @@ -19,14 +19,11 @@ export async function POST(request: NextRequest) { } // Create a session token - const token = await createSession(user) + await createSession(user) // Create a response const response = NextResponse.json({ success: true, user }) - // Set the session cookie - await setSessionCookie(token, response) - return response } catch (error) { console.error("Login failed:", error) diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server" -import { clearSession } from "@/lib/auth" +import { deleteSession } from "@/lib/auth" export async function POST() { try { @@ -7,8 +7,7 @@ export async function POST() { const response = NextResponse.json({ success: true }) // Clear the session cookie - await clearSession(response) - + await deleteSession() return response } catch (error) { console.error("Logout failed:", error) diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts @@ -1,5 +1,5 @@ import { type NextRequest, NextResponse } from "next/server" -import { createSession, setSessionCookie } from "@/lib/auth" +import { createSession } from "@/lib/auth" export async function POST(request: NextRequest) { try { @@ -19,14 +19,11 @@ export async function POST(request: NextRequest) { } // Create a session token - const token = await createSession(user) + await createSession(user) // Create a response const response = NextResponse.json({ success: true, user }) - // Set the session cookie - await setSessionCookie(token, response) - return response } catch (error) { console.error("Registration failed:", error) diff --git a/src/app/api/auth/session/route.ts b/src/app/api/auth/session/route.ts @@ -1,9 +1,9 @@ -import { type NextRequest, NextResponse } from "next/server" +import { NextResponse } from "next/server" import { getSessionUser } from "@/lib/auth" -export async function GET(request: NextRequest) { +export async function GET() { try { - const user = await getSessionUser(request) + const user = await getSessionUser() if (!user) { return NextResponse.json({ authenticated: false }, { status: 401 }) diff --git a/src/app/api/verify-matrix-token/route.ts b/src/app/api/verify-matrix-token/route.ts @@ -28,6 +28,8 @@ export async function POST(request: Request) { if (response.ok) { const data = (await response.json()) as WellKnownResponse if (data["m.homeserver"]?.base_url) { + // TODO: Fix me + // eslint-disable-next-line @typescript-eslint/no-unused-vars homeserverUrl = data["m.homeserver"].base_url } } diff --git a/src/app/dashboard/analytics/page.tsx b/src/app/dashboard/analytics/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react" -import { TrendingUp, Users } from "lucide-react" +import { Users } from "lucide-react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" @@ -15,35 +15,7 @@ import { BannedServerData, generateHeatmapData, getBannedServersConfig, getMonth import { PlotlyChart } from "@/components/analytics/plotly-chart"; import { Button } from "@/components/ui/button"; import Link from "next/link"; - -interface InfoCardWithTrendProps { - title: string; - changePercent: number; - value: string | number; - trendColorOverride?: string; -} - -function InfoCardWithTrend({ title, changePercent, value, trendColorOverride }: InfoCardWithTrendProps) { - const trendColor = changePercent > 0 ? "text-green-400" : "text-red-400" - const trendIcon = changePercent > 0 ? <TrendingUp className="h-3.5 w-3.5 mr-1" /> : <TrendingUp className="h-3.5 w-3.5 mr-1 rotate-180" /> - const trendText = changePercent > 0 ? `+${changePercent}% from previous period` : `${changePercent}% from previous period` - const trendClass = trendColorOverride || trendColor - - return ( - <Card className="border-gray-800 bg-gray-950"> - <CardHeader className="pb-2"> - <CardTitle className="text-sm font-medium">{title}</CardTitle> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold">{value}</div> - <div className={`flex items-center text-xs ${trendClass}`}> - {trendIcon} - <span>{trendText}</span> - </div> - </CardContent> - </Card> - ) -} +import InfoCardWithTrend from "@/components/analytics/info-card-with-trend"; // Mock data for charts const roomActivityData: RoomActivityData[] = [ diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx @@ -1,81 +1,13 @@ -import type React from "react" +import type { ReactNode } from "react" import { Suspense } from "react" -import { redirect } from "next/navigation" -import { getSessionUser } from "@/lib/auth" import { DashboardHeader } from "../../components/dashboard/dashboard-header" - -// Mock data for teams/bots -const mockTeams = [ - { - id: "team1", - name: "Main Community Bot", - description: "Moderation for main community rooms", - owner: "@admin:matrix.org", - created: "2025-01-15T10:00:00Z", - members: [ - { - id: "user1", - name: "@admin:matrix.org", - role: "owner", - avatar: null, - joined: "2025-01-15T10:00:00Z", - }, - { - id: "user2", - name: "@mod1:matrix.org", - role: "moderator", - avatar: null, - joined: "2025-01-16T14:30:00Z", - }, - { - id: "user3", - name: "@mod2:matrix.org", - role: "moderator", - avatar: null, - joined: "2025-02-01T09:15:00Z", - }, - ], - rooms: ["#general:matrix.org", "#support:matrix.org", "#community:matrix.org"], - }, - { - id: "team2", - name: "Development Team Bot", - description: "Moderation for development-related rooms", - owner: "@admin:matrix.org", - created: "2025-02-10T11:30:00Z", - members: [ - { - id: "user1", - name: "@admin:matrix.org", - role: "owner", - avatar: null, - joined: "2025-02-10T11:30:00Z", - }, - { - id: "user4", - name: "@dev1:matrix.org", - role: "moderator", - avatar: null, - joined: "2025-02-11T13:45:00Z", - }, - ], - rooms: ["#development:matrix.org", "#coding:matrix.org"], - }, -] +import { mockTeams } from "./mockData" export default async function DashboardLayout({ children, }: { - children: React.ReactNode + children: ReactNode }) { - // Check if the user is authenticated on the server - const user = await getSessionUser() - - // If not authenticated, redirect to login - if (!user) { - redirect("/login") - } - return ( <div className="flex min-h-screen flex-col bg-black text-white"> <Suspense diff --git a/src/app/dashboard/overview/page.tsx b/src/app/dashboard/overview/page.tsx @@ -1,35 +1,34 @@ -'use client'; - import { AlertTriangle, Ban, CheckCircle } from "lucide-react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { useSearchParams } from "next/navigation" -import { useEffect, useState } from "react" import AddProtectedRoom from "../../../components/modals/add-protected-room" import ProtectedRomsList from "../../../components/dashboard/protected-rooms-list" -import { mockPolicyLists, mockReports, mockTeams } from "../mockData" +import { mockPolicyLists, mockReports, mockTeams, PolicyList, Report, Team } from "../mockData" import TabNavigation from "../../../components/dashboard/tab-navigation"; -export default function OverviewPage() { - const searchParams = useSearchParams() - const teamIdParam = searchParams.get("team") - const [selectedTeam, setSelectedTeam] = useState(mockTeams.find((t) => t.id === teamIdParam) || mockTeams[0]) +interface OverviewPageProps { + selectedTeam: Team; + reports: Report[]; + policyLists: PolicyList[]; +} +async function fetchData(teamIdParam?: string): Promise<OverviewPageProps> { + const selectedTeam = mockTeams.find((t) => t.id === teamIdParam) || mockTeams[0]; + const reports = mockReports.filter((report) => report.teamId === selectedTeam.id); + const policyLists = mockPolicyLists.filter((list) => list.teamId === selectedTeam.id); - // Update selected team when URL param changes - useEffect(() => { - const team = mockTeams.find((t) => t.id === teamIdParam) - if (team) { - setSelectedTeam(team) - } - }, [teamIdParam]) + return { + selectedTeam, + reports, + policyLists, + }; +} - // Filter data based on selected team - const reports = mockReports.filter((report) => report.teamId === selectedTeam.id) - const policyLists = mockPolicyLists.filter((list) => list.teamId === selectedTeam.id) +export default async function OverviewPage({ searchParams }: { searchParams: Promise<{ team?: string }> }) { + const { team } = await searchParams; + const { selectedTeam, reports, policyLists } = await fetchData(team); return ( - <> <TabNavigation selectedTeam={selectedTeam} currentTab="overview" /> <div className="space-y-8"> @@ -140,4 +139,4 @@ export default function OverviewPage() { </div> </> ) -} +} +\ No newline at end of file diff --git a/src/app/dashboard/settings/page.tsx b/src/app/dashboard/settings/page.tsx @@ -1,7 +1,7 @@ "use client"; -import { useEffect, useState } from "react" -import { UserPlus, Settings, Crown, Trash2, LogOut } from "lucide-react" +import { useState } from "react" +import { UserPlus, Settings, Trash2, LogOut } from "lucide-react" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" @@ -22,8 +22,6 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { ScrollArea } from "@/components/ui/scroll-area" @@ -33,34 +31,15 @@ import { mockTeams } from "../mockData" import AddProtectedRoom from "../../../components/modals/add-protected-room" import ProtectedRomsList from "../../../components/dashboard/protected-rooms-list" import TabNavigation from "../../../components/dashboard/tab-navigation" -import CreateBotModal from "../../../components/modals/create-bot"; export default function TeamManagement() { const searchParams = useSearchParams() const teamIdParam = searchParams.get("team") - const [selectedTeam, setSelectedTeam] = useState(mockTeams.find((t) => t.id === teamIdParam) || mockTeams[0]) - const [activeTab, setActiveTab] = useState("members") - - // Update selected team when URL param changes - useEffect(() => { - const team = mockTeams.find((t) => t.id === teamIdParam) - if (team) { - setSelectedTeam(team) - } - }, [teamIdParam]) + const [selectedTeam] = useState(mockTeams.find((t) => t.id === teamIdParam) || mockTeams[0]) - const handleTeamChange = (teamId: string) => { - const team = mockTeams.find((t) => t.id === teamId) - if (team) { - setSelectedTeam(team) - - // Update URL with team parameter - const url = new URL(window.location.href) - url.searchParams.set("team", teamId) - window.history.pushState({}, "", url.toString()) - } - } + // TODO: Make this actual pages so we can use more ssr + const [activeTab, setActiveTab] = useState("members") return ( <> @@ -72,29 +51,6 @@ export default function TeamManagement() { <CardTitle>Team Management</CardTitle> <CardDescription className="text-gray-400">Manage your bots and team members</CardDescription> </div> - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button variant="outline" className="border-purple-500 text-purple-400 hover:bg-purple-950"> - <Crown className="mr-2 h-4 w-4" /> - {selectedTeam.name} - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent className="w-56 bg-gray-900 border-gray-800"> - <DropdownMenuLabel>Your Bots</DropdownMenuLabel> - <DropdownMenuSeparator className="bg-gray-800" /> - {mockTeams.map((team) => ( - <DropdownMenuItem - key={team.id} - className={selectedTeam.id === team.id ? "bg-gray-800" : ""} - onClick={() => handleTeamChange(team.id)} - > - {team.name} - </DropdownMenuItem> - ))} - <DropdownMenuSeparator className="bg-gray-800" /> - <CreateBotModal /> - </DropdownMenuContent> - </DropdownMenu> </div> </CardHeader> <CardContent> diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx @@ -22,6 +22,8 @@ export default function LoginPage() { setStep("login") } + // TODO: Fix me + // eslint-disable-next-line @typescript-eslint/no-unused-vars const handleLoginSuccess = async (token: string) => { try { // Use the login function from session context diff --git a/src/app/page.tsx b/src/app/page.tsx @@ -4,6 +4,8 @@ import { Shield, Users, BarChart, Settings, AlertTriangle, CheckCircle, Ban, Eye import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +export const experimental_ppr = true + export default function Home() { return ( <div className="flex min-h-screen flex-col bg-black text-white"> diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx @@ -22,6 +22,8 @@ export default function RegisterPage() { setStep("login") } + // TODO: Fix me + // eslint-disable-next-line @typescript-eslint/no-unused-vars const handleLoginSuccess = async (token: string) => { try { // Use the register function from session context diff --git a/src/components/analytics/info-card-with-trend.tsx b/src/components/analytics/info-card-with-trend.tsx @@ -0,0 +1,31 @@ +import { TrendingDown, TrendingUp } from "lucide-react"; +import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; + +interface InfoCardWithTrendProps { + title: string; + changePercent: number; + value: string | number; + trendColorOverride?: string; +} + +export default function InfoCardWithTrend({ title, changePercent, value, trendColorOverride }: InfoCardWithTrendProps) { + const trendColor = changePercent > 0 ? "text-green-400" : "text-red-400" + const trendIcon = changePercent > 0 ? <TrendingUp className="h-3.5 w-3.5 mr-1" /> : <TrendingDown className="h-3.5 w-3.5 mr-1" /> + const trendText = changePercent > 0 ? `+${changePercent}% from previous period` : `${changePercent}% from previous period` + const trendClass = trendColorOverride || trendColor + + return ( + <Card className="border-gray-800 bg-gray-950"> + <CardHeader className="pb-2"> + <CardTitle className="text-sm font-medium">{title}</CardTitle> + </CardHeader> + <CardContent> + <div className="text-2xl font-bold">{value}</div> + <div className={`flex items-center text-xs ${trendClass}`}> + {trendIcon} + <span>{trendText}</span> + </div> + </CardContent> + </Card> + ) +} +\ No newline at end of file diff --git a/src/components/dashboard/dashboard-header.tsx b/src/components/dashboard/dashboard-header.tsx @@ -22,7 +22,7 @@ import { useSession } from "@/contexts/session-context" import AddBan from "../modals/add-ban" import KickUserModal from "../modals/kick-user" import CreateBotModal from "../modals/create-bot" -import { mockPolicyLists } from "../../app/dashboard/mockData" +import { mockPolicyLists, Team } from "../../app/dashboard/mockData" interface Notification { id: string @@ -34,12 +34,6 @@ interface Notification { teamId?: string } -interface Team { - id: string - name: string - description: string -} - interface DashboardHeaderProps { teams: Team[] } diff --git a/src/components/matrix-login-form.tsx b/src/components/matrix-login-form.tsx @@ -31,6 +31,8 @@ export function MatrixLoginForm({ matrixId, onSuccess, onCancel }: MatrixLoginFo // Extract username and server from Matrix ID const serverName = matrixId.includes(":") ? matrixId.split(":").pop() || "matrix.org" : "matrix.org" + // TODO: Fix me + // eslint-disable-next-line @typescript-eslint/no-unused-vars const username = matrixId.startsWith("@") ? matrixId.includes(":") ? matrixId.split(":")[0].substring(1) @@ -111,6 +113,8 @@ export function MatrixLoginForm({ matrixId, onSuccess, onCancel }: MatrixLoginFo await new Promise((resolve) => setTimeout(resolve, 1000)) // Simulate access token + // TODO: Fix me + // eslint-disable-next-line @typescript-eslint/no-unused-vars const mockAccessToken = "syt_" + Math.random().toString(36).substring(2, 15) // Step 2: Request an OpenID token diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx @@ -1,7 +1,7 @@ -import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" +import { forwardRef, HTMLAttributes } from "react" const alertVariants = cva( "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", @@ -19,9 +19,9 @@ const alertVariants = cva( } ) -const Alert = React.forwardRef< +const Alert = forwardRef< HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> + HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> >(({ className, variant, ...props }, ref) => ( <div ref={ref} @@ -32,9 +32,9 @@ const Alert = React.forwardRef< )) Alert.displayName = "Alert" -const AlertTitle = React.forwardRef< +const AlertTitle = forwardRef< HTMLParagraphElement, - React.HTMLAttributes<HTMLHeadingElement> + HTMLAttributes<HTMLHeadingElement> >(({ className, ...props }, ref) => ( <h5 ref={ref} @@ -44,9 +44,9 @@ const AlertTitle = React.forwardRef< )) AlertTitle.displayName = "AlertTitle" -const AlertDescription = React.forwardRef< +const AlertDescription = forwardRef< HTMLParagraphElement, - React.HTMLAttributes<HTMLParagraphElement> + HTMLAttributes<HTMLParagraphElement> >(({ className, ...props }, ref) => ( <div ref={ref} @@ -56,4 +56,4 @@ const AlertDescription = React.forwardRef< )) AlertDescription.displayName = "AlertDescription" -export { Alert, AlertTitle, AlertDescription } +export { Alert, AlertTitle, AlertDescription } +\ No newline at end of file diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx @@ -1,15 +1,13 @@ -"use client" - -import * as React from "react" -import * as AvatarPrimitive from "@radix-ui/react-avatar" +import { Root, Image, Fallback } from "@radix-ui/react-avatar" import { cn } from "@/lib/utils" +import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react" -const Avatar = React.forwardRef< - React.ElementRef<typeof AvatarPrimitive.Root>, - React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> +const Avatar = forwardRef< + ComponentRef<typeof Root>, + ComponentPropsWithoutRef<typeof Root> >(({ className, ...props }, ref) => ( - <AvatarPrimitive.Root + <Root ref={ref} className={cn( "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", @@ -18,25 +16,26 @@ const Avatar = React.forwardRef< {...props} /> )) -Avatar.displayName = AvatarPrimitive.Root.displayName +Avatar.displayName = Root.displayName -const AvatarImage = React.forwardRef< - React.ElementRef<typeof AvatarPrimitive.Image>, - React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> +const AvatarImage = forwardRef< + ComponentRef<typeof Image>, + ComponentPropsWithoutRef<typeof Image> >(({ className, ...props }, ref) => ( - <AvatarPrimitive.Image + // eslint-disable-next-line jsx-a11y/alt-text + <Image ref={ref} className={cn("aspect-square h-full w-full", className)} {...props} /> )) -AvatarImage.displayName = AvatarPrimitive.Image.displayName +AvatarImage.displayName = Image.displayName -const AvatarFallback = React.forwardRef< - React.ElementRef<typeof AvatarPrimitive.Fallback>, - React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> +const AvatarFallback = forwardRef< + ComponentRef<typeof Fallback>, + ComponentPropsWithoutRef<typeof Fallback> >(({ className, ...props }, ref) => ( - <AvatarPrimitive.Fallback + <Fallback ref={ref} className={cn( "flex h-full w-full items-center justify-center rounded-full bg-muted", @@ -45,6 +44,6 @@ const AvatarFallback = React.forwardRef< {...props} /> )) -AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName +AvatarFallback.displayName = Fallback.displayName export { Avatar, AvatarImage, AvatarFallback } diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx @@ -1,7 +1,7 @@ -import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" +import { HTMLAttributes } from "react" const badgeVariants = cva( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", @@ -24,8 +24,8 @@ const badgeVariants = cva( ) export interface BadgeProps - extends React.HTMLAttributes<HTMLDivElement>, - VariantProps<typeof badgeVariants> {} + extends HTMLAttributes<HTMLDivElement>, + VariantProps<typeof badgeVariants> { } function Badge({ className, variant, ...props }: BadgeProps) { return ( diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx @@ -1,20 +1,20 @@ -import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { ChevronRight, MoreHorizontal } from "lucide-react" import { cn } from "@/lib/utils" +import { forwardRef, ComponentPropsWithoutRef, ReactNode, ComponentProps } from "react" -const Breadcrumb = React.forwardRef< +const Breadcrumb = forwardRef< HTMLElement, - React.ComponentPropsWithoutRef<"nav"> & { - separator?: React.ReactNode + ComponentPropsWithoutRef<"nav"> & { + separator?: ReactNode } >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />) Breadcrumb.displayName = "Breadcrumb" -const BreadcrumbList = React.forwardRef< +const BreadcrumbList = forwardRef< HTMLOListElement, - React.ComponentPropsWithoutRef<"ol"> + ComponentPropsWithoutRef<"ol"> >(({ className, ...props }, ref) => ( <ol ref={ref} @@ -27,9 +27,9 @@ const BreadcrumbList = React.forwardRef< )) BreadcrumbList.displayName = "BreadcrumbList" -const BreadcrumbItem = React.forwardRef< +const BreadcrumbItem = forwardRef< HTMLLIElement, - React.ComponentPropsWithoutRef<"li"> + ComponentPropsWithoutRef<"li"> >(({ className, ...props }, ref) => ( <li ref={ref} @@ -39,9 +39,9 @@ const BreadcrumbItem = React.forwardRef< )) BreadcrumbItem.displayName = "BreadcrumbItem" -const BreadcrumbLink = React.forwardRef< +const BreadcrumbLink = forwardRef< HTMLAnchorElement, - React.ComponentPropsWithoutRef<"a"> & { + ComponentPropsWithoutRef<"a"> & { asChild?: boolean } >(({ asChild, className, ...props }, ref) => { @@ -57,9 +57,9 @@ const BreadcrumbLink = React.forwardRef< }) BreadcrumbLink.displayName = "BreadcrumbLink" -const BreadcrumbPage = React.forwardRef< +const BreadcrumbPage = forwardRef< HTMLSpanElement, - React.ComponentPropsWithoutRef<"span"> + ComponentPropsWithoutRef<"span"> >(({ className, ...props }, ref) => ( <span ref={ref} @@ -76,7 +76,7 @@ const BreadcrumbSeparator = ({ children, className, ...props -}: React.ComponentProps<"li">) => ( +}: ComponentProps<"li">) => ( <li role="presentation" aria-hidden="true" @@ -91,7 +91,7 @@ BreadcrumbSeparator.displayName = "BreadcrumbSeparator" const BreadcrumbEllipsis = ({ className, ...props -}: React.ComponentProps<"span">) => ( +}: ComponentProps<"span">) => ( <span role="presentation" aria-hidden="true" diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx @@ -1,8 +1,8 @@ -import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" +import { ButtonHTMLAttributes, forwardRef } from "react" const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", @@ -34,12 +34,12 @@ const buttonVariants = cva( ) export interface ButtonProps - extends React.ButtonHTMLAttributes<HTMLButtonElement>, - VariantProps<typeof buttonVariants> { + extends ButtonHTMLAttributes<HTMLButtonElement>, + VariantProps<typeof buttonVariants> { asChild?: boolean } -const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( +const Button = forwardRef<HTMLButtonElement, ButtonProps>( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button" return ( diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx @@ -1,10 +1,9 @@ -import * as React from "react" - import { cn } from "@/lib/utils" +import { forwardRef, HTMLAttributes } from "react" -const Card = React.forwardRef< +const Card = forwardRef< HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> + HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} @@ -17,9 +16,9 @@ const Card = React.forwardRef< )) Card.displayName = "Card" -const CardHeader = React.forwardRef< +const CardHeader = forwardRef< HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> + HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} @@ -29,9 +28,9 @@ const CardHeader = React.forwardRef< )) CardHeader.displayName = "CardHeader" -const CardTitle = React.forwardRef< +const CardTitle = forwardRef< HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> + HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} @@ -44,9 +43,9 @@ const CardTitle = React.forwardRef< )) CardTitle.displayName = "CardTitle" -const CardDescription = React.forwardRef< +const CardDescription = forwardRef< HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> + HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} @@ -56,17 +55,17 @@ const CardDescription = React.forwardRef< )) CardDescription.displayName = "CardDescription" -const CardContent = React.forwardRef< +const CardContent = forwardRef< HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> + HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> )) CardContent.displayName = "CardContent" -const CardFooter = React.forwardRef< +const CardFooter = forwardRef< HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> + HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx @@ -1,16 +1,16 @@ "use client" -import * as React from "react" -import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Root, Indicator } from "@radix-ui/react-checkbox" import { Check } from "lucide-react" import { cn } from "@/lib/utils" +import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react" -const Checkbox = React.forwardRef< - React.ElementRef<typeof CheckboxPrimitive.Root>, - React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> +const Checkbox = forwardRef< + ComponentRef<typeof Root>, + ComponentPropsWithoutRef<typeof Root> >(({ className, ...props }, ref) => ( - <CheckboxPrimitive.Root + <Root ref={ref} className={cn( "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", @@ -18,13 +18,13 @@ const Checkbox = React.forwardRef< )} {...props} > - <CheckboxPrimitive.Indicator + <Indicator className={cn("flex items-center justify-center text-current")} > <Check className="h-4 w-4" /> - </CheckboxPrimitive.Indicator> - </CheckboxPrimitive.Root> + </Indicator> + </Root> )) -Checkbox.displayName = CheckboxPrimitive.Root.displayName +Checkbox.displayName = Root.displayName export { Checkbox } diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx @@ -1,24 +1,24 @@ "use client" -import * as React from "react" -import * as DialogPrimitive from "@radix-ui/react-dialog" +import { Root, Trigger, Portal, Close, Overlay, Content, Title, Description } from "@radix-ui/react-dialog" import { X } from "lucide-react" import { cn } from "@/lib/utils" +import { forwardRef, ComponentRef, ComponentPropsWithoutRef, HTMLAttributes } from "react" -const Dialog = DialogPrimitive.Root +const Dialog = Root -const DialogTrigger = DialogPrimitive.Trigger +const DialogTrigger = Trigger -const DialogPortal = DialogPrimitive.Portal +const DialogPortal = Portal -const DialogClose = DialogPrimitive.Close +const DialogClose = Close -const DialogOverlay = React.forwardRef< - React.ElementRef<typeof DialogPrimitive.Overlay>, - React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> +const DialogOverlay = forwardRef< + ComponentRef<typeof Overlay>, + ComponentPropsWithoutRef<typeof Overlay> >(({ className, ...props }, ref) => ( - <DialogPrimitive.Overlay + <Overlay ref={ref} className={cn( "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", @@ -27,15 +27,15 @@ const DialogOverlay = React.forwardRef< {...props} /> )) -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName +DialogOverlay.displayName = Overlay.displayName -const DialogContent = React.forwardRef< - React.ElementRef<typeof DialogPrimitive.Content>, - React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> +const DialogContent = forwardRef< + ComponentRef<typeof Content>, + ComponentPropsWithoutRef<typeof Content> >(({ className, children, ...props }, ref) => ( <DialogPortal> <DialogOverlay /> - <DialogPrimitive.Content + <Content ref={ref} className={cn( "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", @@ -44,19 +44,19 @@ const DialogContent = React.forwardRef< {...props} > {children} - <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> + <Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> <X className="h-4 w-4" /> <span className="sr-only">Close</span> - </DialogPrimitive.Close> - </DialogPrimitive.Content> + </Close> + </Content> </DialogPortal> )) -DialogContent.displayName = DialogPrimitive.Content.displayName +DialogContent.displayName = Content.displayName const DialogHeader = ({ className, ...props -}: React.HTMLAttributes<HTMLDivElement>) => ( +}: HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col space-y-1.5 text-center sm:text-left", @@ -70,7 +70,7 @@ DialogHeader.displayName = "DialogHeader" const DialogFooter = ({ className, ...props -}: React.HTMLAttributes<HTMLDivElement>) => ( +}: HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", @@ -81,11 +81,11 @@ const DialogFooter = ({ ) DialogFooter.displayName = "DialogFooter" -const DialogTitle = React.forwardRef< - React.ElementRef<typeof DialogPrimitive.Title>, - React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> +const DialogTitle = forwardRef< + ComponentRef<typeof Title>, + ComponentPropsWithoutRef<typeof Title> >(({ className, ...props }, ref) => ( - <DialogPrimitive.Title + <Title ref={ref} className={cn( "text-lg font-semibold leading-none tracking-tight", @@ -94,19 +94,19 @@ const DialogTitle = React.forwardRef< {...props} /> )) -DialogTitle.displayName = DialogPrimitive.Title.displayName +DialogTitle.displayName = Title.displayName -const DialogDescription = React.forwardRef< - React.ElementRef<typeof DialogPrimitive.Description>, - React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> +const DialogDescription = forwardRef< + ComponentRef<typeof Description>, + ComponentPropsWithoutRef<typeof Description> >(({ className, ...props }, ref) => ( - <DialogPrimitive.Description + <Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} /> )) -DialogDescription.displayName = DialogPrimitive.Description.displayName +DialogDescription.displayName = Description.displayName export { Dialog, diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx @@ -1,30 +1,30 @@ "use client" -import * as React from "react" -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Root, Trigger, Group, Portal, Sub, RadioGroup, SubTrigger, Content, Item, SubContent, CheckboxItem, ItemIndicator, RadioItem, Label, Separator } from "@radix-ui/react-dropdown-menu" import { Check, ChevronRight, Circle } from "lucide-react" import { cn } from "@/lib/utils" +import { forwardRef, ComponentRef, ComponentPropsWithoutRef, HTMLAttributes } from "react" -const DropdownMenu = DropdownMenuPrimitive.Root +const DropdownMenu = Root -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger +const DropdownMenuTrigger = Trigger -const DropdownMenuGroup = DropdownMenuPrimitive.Group +const DropdownMenuGroup = Group -const DropdownMenuPortal = DropdownMenuPrimitive.Portal +const DropdownMenuPortal = Portal -const DropdownMenuSub = DropdownMenuPrimitive.Sub +const DropdownMenuSub = Sub -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup +const DropdownMenuRadioGroup = RadioGroup -const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { +const DropdownMenuSubTrigger = forwardRef< + ComponentRef<typeof SubTrigger>, + ComponentPropsWithoutRef<typeof SubTrigger> & { inset?: boolean } >(({ className, inset, children, ...props }, ref) => ( - <DropdownMenuPrimitive.SubTrigger + <SubTrigger ref={ref} className={cn( "flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", @@ -35,16 +35,16 @@ const DropdownMenuSubTrigger = React.forwardRef< > {children} <ChevronRight className="ml-auto" /> - </DropdownMenuPrimitive.SubTrigger> + </SubTrigger> )) DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName + SubTrigger.displayName -const DropdownMenuSubContent = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> +const DropdownMenuSubContent = forwardRef< + ComponentRef<typeof SubContent>, + ComponentPropsWithoutRef<typeof SubContent> >(({ className, ...props }, ref) => ( - <DropdownMenuPrimitive.SubContent + <SubContent ref={ref} className={cn( "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", @@ -54,14 +54,14 @@ const DropdownMenuSubContent = React.forwardRef< /> )) DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName + SubContent.displayName -const DropdownMenuContent = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.Content>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> +const DropdownMenuContent = forwardRef< + ComponentRef<typeof Content>, + ComponentPropsWithoutRef<typeof Content> >(({ className, sideOffset = 4, ...props }, ref) => ( - <DropdownMenuPrimitive.Portal> - <DropdownMenuPrimitive.Content + <Portal> + <Content ref={ref} sideOffset={sideOffset} className={cn( @@ -70,17 +70,17 @@ const DropdownMenuContent = React.forwardRef< )} {...props} /> - </DropdownMenuPrimitive.Portal> + </Portal> )) -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName +DropdownMenuContent.displayName = Content.displayName -const DropdownMenuItem = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.Item>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { +const DropdownMenuItem = forwardRef< + ComponentRef<typeof Item>, + ComponentPropsWithoutRef<typeof Item> & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( - <DropdownMenuPrimitive.Item + <Item ref={ref} className={cn( "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", @@ -90,13 +90,13 @@ const DropdownMenuItem = React.forwardRef< {...props} /> )) -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName +DropdownMenuItem.displayName = Item.displayName -const DropdownMenuCheckboxItem = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> +const DropdownMenuCheckboxItem = forwardRef< + ComponentRef<typeof CheckboxItem>, + ComponentPropsWithoutRef<typeof CheckboxItem> >(({ className, children, checked, ...props }, ref) => ( - <DropdownMenuPrimitive.CheckboxItem + <CheckboxItem ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", @@ -106,21 +106,21 @@ const DropdownMenuCheckboxItem = React.forwardRef< {...props} > <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> - <DropdownMenuPrimitive.ItemIndicator> + <ItemIndicator> <Check className="h-4 w-4" /> - </DropdownMenuPrimitive.ItemIndicator> + </ItemIndicator> </span> {children} - </DropdownMenuPrimitive.CheckboxItem> + </CheckboxItem> )) DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName + CheckboxItem.displayName -const DropdownMenuRadioItem = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> +const DropdownMenuRadioItem = forwardRef< + ComponentRef<typeof RadioItem>, + ComponentPropsWithoutRef<typeof RadioItem> >(({ className, children, ...props }, ref) => ( - <DropdownMenuPrimitive.RadioItem + <RadioItem ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", @@ -129,22 +129,22 @@ const DropdownMenuRadioItem = React.forwardRef< {...props} > <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> - <DropdownMenuPrimitive.ItemIndicator> + <ItemIndicator> <Circle className="h-2 w-2 fill-current" /> - </DropdownMenuPrimitive.ItemIndicator> + </ItemIndicator> </span> {children} - </DropdownMenuPrimitive.RadioItem> + </RadioItem> )) -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName +DropdownMenuRadioItem.displayName = RadioItem.displayName -const DropdownMenuLabel = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.Label>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { +const DropdownMenuLabel = forwardRef< + ComponentRef<typeof Label>, + ComponentPropsWithoutRef<typeof Label> & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( - <DropdownMenuPrimitive.Label + <Label ref={ref} className={cn( "px-2 py-1.5 text-sm font-semibold", @@ -154,24 +154,24 @@ const DropdownMenuLabel = React.forwardRef< {...props} /> )) -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName +DropdownMenuLabel.displayName = Label.displayName -const DropdownMenuSeparator = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.Separator>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> +const DropdownMenuSeparator = forwardRef< + ComponentRef<typeof Separator>, + ComponentPropsWithoutRef<typeof Separator> >(({ className, ...props }, ref) => ( - <DropdownMenuPrimitive.Separator + <Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} /> )) -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName +DropdownMenuSeparator.displayName = Separator.displayName const DropdownMenuShortcut = ({ className, ...props -}: React.HTMLAttributes<HTMLSpanElement>) => { +}: HTMLAttributes<HTMLSpanElement>) => { return ( <span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx @@ -1,8 +1,7 @@ -import * as React from "react" - import { cn } from "@/lib/utils" +import { ComponentProps, forwardRef } from "react" -const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>( +const Input = forwardRef<HTMLInputElement, ComponentProps<"input">>( ({ className, type, ...props }, ref) => { return ( <input @@ -19,4 +18,4 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>( ) Input.displayName = "Input" -export { Input } +export { Input } +\ No newline at end of file diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx @@ -1,26 +1,26 @@ "use client" -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" +import { Root } from "@radix-ui/react-label" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" +import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react" const labelVariants = cva( "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" ) -const Label = React.forwardRef< - React.ElementRef<typeof LabelPrimitive.Root>, - React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & - VariantProps<typeof labelVariants> +const Label = forwardRef< + ComponentRef<typeof Root>, + ComponentPropsWithoutRef<typeof Root> & + VariantProps<typeof labelVariants> >(({ className, ...props }, ref) => ( - <LabelPrimitive.Root + <Root ref={ref} className={cn(labelVariants(), className)} {...props} /> )) -Label.displayName = LabelPrimitive.Root.displayName +Label.displayName = Root.displayName export { Label } diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx @@ -1,10 +1,10 @@ -import * as React from "react" import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" import { cn } from "@/lib/utils" import { ButtonProps, buttonVariants } from "@/components/ui/button" +import { ComponentProps, forwardRef } from "react" -const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( +const Pagination = ({ className, ...props }: ComponentProps<"nav">) => ( <nav role="navigation" aria-label="pagination" @@ -14,9 +14,9 @@ const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( ) Pagination.displayName = "Pagination" -const PaginationContent = React.forwardRef< +const PaginationContent = forwardRef< HTMLUListElement, - React.ComponentProps<"ul"> + ComponentProps<"ul"> >(({ className, ...props }, ref) => ( <ul ref={ref} @@ -26,9 +26,9 @@ const PaginationContent = React.forwardRef< )) PaginationContent.displayName = "PaginationContent" -const PaginationItem = React.forwardRef< +const PaginationItem = forwardRef< HTMLLIElement, - React.ComponentProps<"li"> + ComponentProps<"li"> >(({ className, ...props }, ref) => ( <li ref={ref} className={cn("", className)} {...props} /> )) @@ -37,7 +37,7 @@ PaginationItem.displayName = "PaginationItem" type PaginationLinkProps = { isActive?: boolean } & Pick<ButtonProps, "size"> & - React.ComponentProps<"a"> + ComponentProps<"a"> const PaginationLink = ({ className, @@ -62,7 +62,7 @@ PaginationLink.displayName = "PaginationLink" const PaginationPrevious = ({ className, ...props -}: React.ComponentProps<typeof PaginationLink>) => ( +}: ComponentProps<typeof PaginationLink>) => ( <PaginationLink aria-label="Go to previous page" size="default" @@ -78,7 +78,7 @@ PaginationPrevious.displayName = "PaginationPrevious" const PaginationNext = ({ className, ...props -}: React.ComponentProps<typeof PaginationLink>) => ( +}: ComponentProps<typeof PaginationLink>) => ( <PaginationLink aria-label="Go to next page" size="default" @@ -94,7 +94,7 @@ PaginationNext.displayName = "PaginationNext" const PaginationEllipsis = ({ className, ...props -}: React.ComponentProps<"span">) => ( +}: ComponentProps<"span">) => ( <span aria-hidden className={cn("flex h-9 w-9 items-center justify-center", className)} diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx @@ -1,20 +1,20 @@ "use client" -import * as React from "react" -import * as PopoverPrimitive from "@radix-ui/react-popover" +import { Root, Trigger, Content, Portal } from "@radix-ui/react-popover" import { cn } from "@/lib/utils" +import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react" -const Popover = PopoverPrimitive.Root +const Popover = Root -const PopoverTrigger = PopoverPrimitive.Trigger +const PopoverTrigger = Trigger -const PopoverContent = React.forwardRef< - React.ElementRef<typeof PopoverPrimitive.Content>, - React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> +const PopoverContent = forwardRef< + ComponentRef<typeof Content>, + ComponentPropsWithoutRef<typeof Content> >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( - <PopoverPrimitive.Portal> - <PopoverPrimitive.Content + <Portal> + <Content ref={ref} align={align} sideOffset={sideOffset} @@ -24,8 +24,8 @@ const PopoverContent = React.forwardRef< )} {...props} /> - </PopoverPrimitive.Portal> + </Portal> )) -PopoverContent.displayName = PopoverPrimitive.Content.displayName +PopoverContent.displayName = Content.displayName export { Popover, PopoverTrigger, PopoverContent } diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx @@ -1,48 +1,48 @@ "use client" -import * as React from "react" -import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" +import { Root, Viewport, Corner, ScrollAreaScrollbar, ScrollAreaThumb } from "@radix-ui/react-scroll-area" import { cn } from "@/lib/utils" +import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react" -const ScrollArea = React.forwardRef< - React.ElementRef<typeof ScrollAreaPrimitive.Root>, - React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> +const ScrollArea = forwardRef< + ComponentRef<typeof Root>, + ComponentPropsWithoutRef<typeof Root> >(({ className, children, ...props }, ref) => ( - <ScrollAreaPrimitive.Root + <Root ref={ref} className={cn("relative overflow-hidden", className)} {...props} > - <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> + <Viewport className="h-full w-full rounded-[inherit]"> {children} - </ScrollAreaPrimitive.Viewport> + </Viewport> <ScrollBar /> - <ScrollAreaPrimitive.Corner /> - </ScrollAreaPrimitive.Root> + <Corner /> + </Root> )) -ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName +ScrollArea.displayName = Root.displayName -const ScrollBar = React.forwardRef< - React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, - React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> +const ScrollBar = forwardRef< + ComponentRef<typeof ScrollAreaScrollbar>, + ComponentPropsWithoutRef<typeof ScrollAreaScrollbar> >(({ className, orientation = "vertical", ...props }, ref) => ( - <ScrollAreaPrimitive.ScrollAreaScrollbar + <ScrollAreaScrollbar ref={ref} orientation={orientation} className={cn( "flex touch-none select-none transition-colors", orientation === "vertical" && - "h-full w-2.5 border-l border-l-transparent p-[1px]", + "h-full w-2.5 border-l border-l-transparent p-[1px]", orientation === "horizontal" && - "h-2.5 flex-col border-t border-t-transparent p-[1px]", + "h-2.5 flex-col border-t border-t-transparent p-[1px]", className )} {...props} > - <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> - </ScrollAreaPrimitive.ScrollAreaScrollbar> + <ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> + </ScrollAreaScrollbar> )) -ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName +ScrollBar.displayName = ScrollAreaScrollbar.displayName export { ScrollArea, ScrollBar } diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx @@ -1,22 +1,22 @@ "use client" -import * as React from "react" -import * as SelectPrimitive from "@radix-ui/react-select" +import { Root, Group, Value, Trigger, Icon, ScrollUpButton, ScrollDownButton, Content, Portal, Viewport, Label, Item, ItemIndicator, ItemText, Separator } from "@radix-ui/react-select" import { Check, ChevronDown, ChevronUp } from "lucide-react" import { cn } from "@/lib/utils" +import React, { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react" -const Select = SelectPrimitive.Root +const Select = Root -const SelectGroup = SelectPrimitive.Group +const SelectGroup = Group -const SelectValue = SelectPrimitive.Value +const SelectValue = Value -const SelectTrigger = React.forwardRef< - React.ElementRef<typeof SelectPrimitive.Trigger>, - React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> +const SelectTrigger = forwardRef< + ComponentRef<typeof Trigger>, + ComponentPropsWithoutRef<typeof Trigger> >(({ className, children, ...props }, ref) => ( - <SelectPrimitive.Trigger + <Trigger ref={ref} className={cn( "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", @@ -25,18 +25,18 @@ const SelectTrigger = React.forwardRef< {...props} > {children} - <SelectPrimitive.Icon asChild> + <Icon asChild> <ChevronDown className="h-4 w-4 opacity-50" /> - </SelectPrimitive.Icon> - </SelectPrimitive.Trigger> + </Icon> + </Trigger> )) -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName +SelectTrigger.displayName = Trigger.displayName -const SelectScrollUpButton = React.forwardRef< - React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, - React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton> +const SelectScrollUpButton = forwardRef< + ComponentRef<typeof ScrollUpButton>, + ComponentPropsWithoutRef<typeof ScrollUpButton> >(({ className, ...props }, ref) => ( - <SelectPrimitive.ScrollUpButton + <ScrollUpButton ref={ref} className={cn( "flex cursor-default items-center justify-center py-1", @@ -45,15 +45,15 @@ const SelectScrollUpButton = React.forwardRef< {...props} > <ChevronUp className="h-4 w-4" /> - </SelectPrimitive.ScrollUpButton> + </ScrollUpButton> )) -SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName +SelectScrollUpButton.displayName = ScrollUpButton.displayName -const SelectScrollDownButton = React.forwardRef< - React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, - React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton> +const SelectScrollDownButton = forwardRef< + ComponentRef<typeof ScrollDownButton>, + ComponentPropsWithoutRef<typeof ScrollDownButton> >(({ className, ...props }, ref) => ( - <SelectPrimitive.ScrollDownButton + <ScrollDownButton ref={ref} className={cn( "flex cursor-default items-center justify-center py-1", @@ -62,60 +62,60 @@ const SelectScrollDownButton = React.forwardRef< {...props} > <ChevronDown className="h-4 w-4" /> - </SelectPrimitive.ScrollDownButton> + </ScrollDownButton> )) SelectScrollDownButton.displayName = - SelectPrimitive.ScrollDownButton.displayName + ScrollDownButton.displayName -const SelectContent = React.forwardRef< - React.ElementRef<typeof SelectPrimitive.Content>, - React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> +const SelectContent = forwardRef< + ComponentRef<typeof Content>, + ComponentPropsWithoutRef<typeof Content> >(({ className, children, position = "popper", ...props }, ref) => ( - <SelectPrimitive.Portal> - <SelectPrimitive.Content + <Portal> + <Content ref={ref} className={cn( "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", position === "popper" && - "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", + "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )} position={position} {...props} > <SelectScrollUpButton /> - <SelectPrimitive.Viewport + <Viewport className={cn( "p-1", position === "popper" && - "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" + "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" )} > {children} - </SelectPrimitive.Viewport> + </Viewport> <SelectScrollDownButton /> - </SelectPrimitive.Content> - </SelectPrimitive.Portal> + </Content> + </Portal> )) -SelectContent.displayName = SelectPrimitive.Content.displayName +SelectContent.displayName = Content.displayName -const SelectLabel = React.forwardRef< - React.ElementRef<typeof SelectPrimitive.Label>, - React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> +const SelectLabel = forwardRef< + ComponentRef<typeof Label>, + ComponentPropsWithoutRef<typeof Label> >(({ className, ...props }, ref) => ( - <SelectPrimitive.Label + <Label ref={ref} className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)} {...props} /> )) -SelectLabel.displayName = SelectPrimitive.Label.displayName +SelectLabel.displayName = Label.displayName -const SelectItem = React.forwardRef< - React.ElementRef<typeof SelectPrimitive.Item>, - React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> +const SelectItem = forwardRef< + ComponentRef<typeof Item>, + ComponentPropsWithoutRef<typeof Item> >(({ className, children, ...props }, ref) => ( - <SelectPrimitive.Item + <Item ref={ref} className={cn( "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", @@ -124,27 +124,27 @@ const SelectItem = React.forwardRef< {...props} > <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> - <SelectPrimitive.ItemIndicator> + <ItemIndicator> <Check className="h-4 w-4" /> - </SelectPrimitive.ItemIndicator> + </ItemIndicator> </span> - <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> - </SelectPrimitive.Item> + <ItemText>{children}</ItemText> + </Item> )) -SelectItem.displayName = SelectPrimitive.Item.displayName +SelectItem.displayName = Item.displayName -const SelectSeparator = React.forwardRef< - React.ElementRef<typeof SelectPrimitive.Separator>, - React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> +const SelectSeparator = forwardRef< + ComponentRef<typeof Separator>, + ComponentPropsWithoutRef<typeof Separator> >(({ className, ...props }, ref) => ( - <SelectPrimitive.Separator + <Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} /> )) -SelectSeparator.displayName = SelectPrimitive.Separator.displayName +SelectSeparator.displayName = Separator.displayName export { Select, diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx @@ -1,19 +1,19 @@ "use client" -import * as React from "react" -import * as SeparatorPrimitive from "@radix-ui/react-separator" +import { Root } from "@radix-ui/react-separator" import { cn } from "@/lib/utils" +import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react" -const Separator = React.forwardRef< - React.ElementRef<typeof SeparatorPrimitive.Root>, - React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> +const Separator = forwardRef< + ComponentRef<typeof Root>, + ComponentPropsWithoutRef<typeof Root> >( ( { className, orientation = "horizontal", decorative = true, ...props }, ref ) => ( - <SeparatorPrimitive.Root + <Root ref={ref} decorative={decorative} orientation={orientation} @@ -26,6 +26,6 @@ const Separator = React.forwardRef< /> ) ) -Separator.displayName = SeparatorPrimitive.Root.displayName +Separator.displayName = Root.displayName export { Separator } diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx @@ -1,25 +1,25 @@ "use client" -import * as React from "react" -import * as SheetPrimitive from "@radix-ui/react-dialog" +import { Root, Trigger, Close, Portal, Overlay, Content, Title, Description } from "@radix-ui/react-dialog" import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react" import { cn } from "@/lib/utils" +import { ComponentPropsWithoutRef, ComponentRef, forwardRef, HTMLAttributes } from "react" -const Sheet = SheetPrimitive.Root +const Sheet = Root -const SheetTrigger = SheetPrimitive.Trigger +const SheetTrigger = Trigger -const SheetClose = SheetPrimitive.Close +const SheetClose = Close -const SheetPortal = SheetPrimitive.Portal +const SheetPortal = Portal -const SheetOverlay = React.forwardRef< - React.ElementRef<typeof SheetPrimitive.Overlay>, - React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> +const SheetOverlay = forwardRef< + ComponentRef<typeof Overlay>, + ComponentPropsWithoutRef<typeof Overlay> >(({ className, ...props }, ref) => ( - <SheetPrimitive.Overlay + <Overlay className={cn( "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className @@ -28,7 +28,7 @@ const SheetOverlay = React.forwardRef< ref={ref} /> )) -SheetOverlay.displayName = SheetPrimitive.Overlay.displayName +SheetOverlay.displayName = Overlay.displayName const sheetVariants = cva( "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", @@ -50,34 +50,34 @@ const sheetVariants = cva( ) interface SheetContentProps - extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, - VariantProps<typeof sheetVariants> {} + extends ComponentPropsWithoutRef<typeof Content>, + VariantProps<typeof sheetVariants> { } -const SheetContent = React.forwardRef< - React.ElementRef<typeof SheetPrimitive.Content>, +const SheetContent = forwardRef< + ComponentRef<typeof Content>, SheetContentProps >(({ side = "right", className, children, ...props }, ref) => ( <SheetPortal> <SheetOverlay /> - <SheetPrimitive.Content + <Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props} > {children} - <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"> + <Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"> <X className="h-4 w-4" /> <span className="sr-only">Close</span> - </SheetPrimitive.Close> - </SheetPrimitive.Content> + </Close> + </Content> </SheetPortal> )) -SheetContent.displayName = SheetPrimitive.Content.displayName +SheetContent.displayName = Content.displayName const SheetHeader = ({ className, ...props -}: React.HTMLAttributes<HTMLDivElement>) => ( +}: HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col space-y-2 text-center sm:text-left", @@ -91,7 +91,7 @@ SheetHeader.displayName = "SheetHeader" const SheetFooter = ({ className, ...props -}: React.HTMLAttributes<HTMLDivElement>) => ( +}: HTMLAttributes<HTMLDivElement>) => ( <div className={cn( "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", @@ -102,29 +102,29 @@ const SheetFooter = ({ ) SheetFooter.displayName = "SheetFooter" -const SheetTitle = React.forwardRef< - React.ElementRef<typeof SheetPrimitive.Title>, - React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> +const SheetTitle = forwardRef< + ComponentRef<typeof Title>, + ComponentPropsWithoutRef<typeof Title> >(({ className, ...props }, ref) => ( - <SheetPrimitive.Title + <Title ref={ref} className={cn("text-lg font-semibold text-foreground", className)} {...props} /> )) -SheetTitle.displayName = SheetPrimitive.Title.displayName +SheetTitle.displayName = Title.displayName -const SheetDescription = React.forwardRef< - React.ElementRef<typeof SheetPrimitive.Description>, - React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> +const SheetDescription = forwardRef< + ComponentRef<typeof Description>, + ComponentPropsWithoutRef<typeof Description> >(({ className, ...props }, ref) => ( - <SheetPrimitive.Description + <Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} /> )) -SheetDescription.displayName = SheetPrimitive.Description.displayName +SheetDescription.displayName = Description.displayName export { Sheet, diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx @@ -1,9 +1,10 @@ import { cn } from "@/lib/utils" +import { HTMLAttributes } from "react" function Skeleton({ className, ...props -}: React.HTMLAttributes<HTMLDivElement>) { +}: HTMLAttributes<HTMLDivElement>) { return ( <div className={cn("animate-pulse rounded-md bg-muted", className)} diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx @@ -1,10 +1,10 @@ -import * as React from "react" import { cn } from "@/lib/utils" +import { forwardRef, HTMLAttributes, TdHTMLAttributes, ThHTMLAttributes } from "react" -const Table = React.forwardRef< +const Table = forwardRef< HTMLTableElement, - React.HTMLAttributes<HTMLTableElement> + HTMLAttributes<HTMLTableElement> >(({ className, ...props }, ref) => ( <div className="relative w-full overflow-auto"> <table @@ -16,17 +16,17 @@ const Table = React.forwardRef< )) Table.displayName = "Table" -const TableHeader = React.forwardRef< +const TableHeader = forwardRef< HTMLTableSectionElement, - React.HTMLAttributes<HTMLTableSectionElement> + HTMLAttributes<HTMLTableSectionElement> >(({ className, ...props }, ref) => ( <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} /> )) TableHeader.displayName = "TableHeader" -const TableBody = React.forwardRef< +const TableBody = forwardRef< HTMLTableSectionElement, - React.HTMLAttributes<HTMLTableSectionElement> + HTMLAttributes<HTMLTableSectionElement> >(({ className, ...props }, ref) => ( <tbody ref={ref} @@ -36,9 +36,9 @@ const TableBody = React.forwardRef< )) TableBody.displayName = "TableBody" -const TableFooter = React.forwardRef< +const TableFooter = forwardRef< HTMLTableSectionElement, - React.HTMLAttributes<HTMLTableSectionElement> + HTMLAttributes<HTMLTableSectionElement> >(({ className, ...props }, ref) => ( <tfoot ref={ref} @@ -51,9 +51,9 @@ const TableFooter = React.forwardRef< )) TableFooter.displayName = "TableFooter" -const TableRow = React.forwardRef< +const TableRow = forwardRef< HTMLTableRowElement, - React.HTMLAttributes<HTMLTableRowElement> + HTMLAttributes<HTMLTableRowElement> >(({ className, ...props }, ref) => ( <tr ref={ref} @@ -66,9 +66,9 @@ const TableRow = React.forwardRef< )) TableRow.displayName = "TableRow" -const TableHead = React.forwardRef< +const TableHead = forwardRef< HTMLTableCellElement, - React.ThHTMLAttributes<HTMLTableCellElement> + ThHTMLAttributes<HTMLTableCellElement> >(({ className, ...props }, ref) => ( <th ref={ref} @@ -81,9 +81,9 @@ const TableHead = React.forwardRef< )) TableHead.displayName = "TableHead" -const TableCell = React.forwardRef< +const TableCell = forwardRef< HTMLTableCellElement, - React.TdHTMLAttributes<HTMLTableCellElement> + TdHTMLAttributes<HTMLTableCellElement> >(({ className, ...props }, ref) => ( <td ref={ref} @@ -93,9 +93,9 @@ const TableCell = React.forwardRef< )) TableCell.displayName = "TableCell" -const TableCaption = React.forwardRef< +const TableCaption = forwardRef< HTMLTableCaptionElement, - React.HTMLAttributes<HTMLTableCaptionElement> + HTMLAttributes<HTMLTableCaptionElement> >(({ className, ...props }, ref) => ( <caption ref={ref} diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx @@ -1,17 +1,17 @@ "use client" -import * as React from "react" -import * as TabsPrimitive from "@radix-ui/react-tabs" +import { Root, List, Trigger, Content } from "@radix-ui/react-tabs" import { cn } from "@/lib/utils" +import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react" -const Tabs = TabsPrimitive.Root +const Tabs = Root -const TabsList = React.forwardRef< - React.ElementRef<typeof TabsPrimitive.List>, - React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> +const TabsList = forwardRef< + ComponentRef<typeof List>, + ComponentPropsWithoutRef<typeof List> >(({ className, ...props }, ref) => ( - <TabsPrimitive.List + <List ref={ref} className={cn( "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground", @@ -20,13 +20,13 @@ const TabsList = React.forwardRef< {...props} /> )) -TabsList.displayName = TabsPrimitive.List.displayName +TabsList.displayName = List.displayName -const TabsTrigger = React.forwardRef< - React.ElementRef<typeof TabsPrimitive.Trigger>, - React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> +const TabsTrigger = forwardRef< + ComponentRef<typeof Trigger>, + ComponentPropsWithoutRef<typeof Trigger> >(({ className, ...props }, ref) => ( - <TabsPrimitive.Trigger + <Trigger ref={ref} className={cn( "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm", @@ -35,13 +35,13 @@ const TabsTrigger = React.forwardRef< {...props} /> )) -TabsTrigger.displayName = TabsPrimitive.Trigger.displayName +TabsTrigger.displayName = Trigger.displayName -const TabsContent = React.forwardRef< - React.ElementRef<typeof TabsPrimitive.Content>, - React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content> +const TabsContent = forwardRef< + ComponentRef<typeof Content>, + ComponentPropsWithoutRef<typeof Content> >(({ className, ...props }, ref) => ( - <TabsPrimitive.Content + <Content ref={ref} className={cn( "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", @@ -50,6 +50,6 @@ const TabsContent = React.forwardRef< {...props} /> )) -TabsContent.displayName = TabsPrimitive.Content.displayName +TabsContent.displayName = Content.displayName export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx @@ -1,8 +1,8 @@ -import * as React from "react" import { cn } from "@/lib/utils" +import { forwardRef } from "react" -const Textarea = React.forwardRef< +const Textarea = forwardRef< HTMLTextAreaElement, React.ComponentProps<"textarea"> >(({ className, ...props }, ref) => { diff --git a/src/components/ui/use-mobile.tsx b/src/components/ui/use-mobile.tsx @@ -1,11 +1,11 @@ -import * as React from "react" +import { useEffect, useState } from "react" const MOBILE_BREAKPOINT = 768 export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined) + const [isMobile, setIsMobile] = useState<boolean | undefined>(undefined) - React.useEffect(() => { + useEffect(() => { const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) const onChange = () => { setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) diff --git a/src/components/ui/use-toast.ts b/src/components/ui/use-toast.ts @@ -1,195 +0,0 @@ -"use client" - -// Inspired by react-hot-toast library -import * as React from "react" - -import type { - ToastActionElement, - ToastProps, -} from "@/components/ui/toast" - -const TOAST_LIMIT = 1 -const TOAST_REMOVE_DELAY = 1000000 - -type ToasterToast = ToastProps & { - id: string - title?: React.ReactNode - description?: React.ReactNode - action?: ToastActionElement -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const - -let count = 0 - -function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER - return count.toString() -} - -type ActionType = typeof actionTypes - -type Action = - | { - type: ActionType["ADD_TOAST"] - toast: ToasterToast - } - | { - type: ActionType["UPDATE_TOAST"] - toast: Partial<ToasterToast> - } - | { - type: ActionType["DISMISS_TOAST"] - toastId?: ToasterToast["id"] - } - | { - type: ActionType["REMOVE_TOAST"] - toastId?: ToasterToast["id"] - } - -interface State { - toasts: ToasterToast[] -} - -const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>() - -const addToRemoveQueue = (toastId: string) => { - if (toastTimeouts.has(toastId)) { - return - } - - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId) - dispatch({ - type: "REMOVE_TOAST", - toastId: toastId, - }) - }, TOAST_REMOVE_DELAY) - - toastTimeouts.set(toastId, timeout) -} - -export const reducer = (state: State, action: Action): State => { - switch (action.type) { - case "ADD_TOAST": - return { - ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - } - - case "UPDATE_TOAST": - return { - ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t - ), - } - - case "DISMISS_TOAST": { - const { toastId } = action - - // ! Side effects ! - This could be extracted into a dismissToast() action, - // but I'll keep it here for simplicity - if (toastId) { - addToRemoveQueue(toastId) - } else { - state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id) - }) - } - - return { - ...state, - toasts: state.toasts.map((t) => - t.id === toastId || toastId === undefined - ? { - ...t, - open: false, - } - : t - ), - } - } - case "REMOVE_TOAST": - if (action.toastId === undefined) { - return { - ...state, - toasts: [], - } - } - return { - ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - } - } -} - -const listeners: Array<(state: State) => void> = [] - -let memoryState: State = { toasts: [] } - -function dispatch(action: Action) { - memoryState = reducer(memoryState, action) - listeners.forEach((listener) => { - listener(memoryState) - }) -} - -type Toast = Omit<ToasterToast, "id"> - -function toast({ ...props }: Toast) { - const id = genId() - - const update = (props: ToasterToast) => - dispatch({ - type: "UPDATE_TOAST", - toast: { ...props, id }, - }) - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) - - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open: boolean) => { - if (!open) dismiss() - }, - }, - }) - - return { - id: id, - dismiss, - update, - } -} - -function useToast() { - const [state, setState] = React.useState<State>(memoryState) - - React.useEffect(() => { - listeners.push(setState) - return () => { - const index = listeners.indexOf(setState) - if (index > -1) { - listeners.splice(index, 1) - } - } - }, [state]) - - return { - ...state, - toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - } -} - -export { useToast, toast } diff --git a/src/lib/auth.ts b/src/lib/auth.ts @@ -1,11 +1,8 @@ import { cookies } from "next/headers" -import type { NextRequest, NextResponse } from "next/server" import { jwtVerify, SignJWT } from "jose" -// Secret key for JWT signing - in production, use a proper secret management system -const JWT_SECRET = new TextEncoder().encode( - process.env.JWT_SECRET || "this_is_a_secret_key_that_should_be_in_env_variables", -) +const secretKey = process.env.SESSION_SECRET +const encodedKey = new TextEncoder().encode(secretKey) export type User = { id: string @@ -15,35 +12,68 @@ export type User = { isAdmin: boolean } -export async function createSession(user: User): Promise<string> { - // Create a JWT token with user data - const token = await new SignJWT({ user }) - .setProtectedHeader({ alg: "HS256" }) +export async function encrypt(user: User) { + return new SignJWT({ user }) + .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() - .setExpirationTime("7d") // Token expires in 7 days - .sign(JWT_SECRET) + .setExpirationTime('7d') + .sign(encodedKey) +} + +export async function decrypt(session: string | undefined = '') { + try { + const { payload } = await jwtVerify(session, encodedKey, { + algorithms: ['HS256'], + }) + return payload + } catch (error: unknown) { + console.log('Failed to verify session', error) + } +} - return token +export async function createSession(user: User) { + const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) + const session = await encrypt(user) + const cookieStore = await cookies() + + cookieStore.set('session', session, { + httpOnly: true, + secure: true, + expires: expiresAt, + sameSite: 'lax', + path: '/', + }) } -export async function setSessionCookie(token: string, response: NextResponse): Promise<void> { - // Set the session cookie with secure attributes - response.cookies.set({ - name: "matrix_session", - value: token, +export async function updateSession() { + const session = (await cookies()).get('session')?.value + const payload = await decrypt(session) + + if (!session || !payload) { + return null + } + + const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) + + const cookieStore = await cookies() + cookieStore.set('session', session, { httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "lax", - maxAge: 60 * 60 * 24 * 7, // 7 days - path: "/", + secure: true, + expires: expires, + sameSite: 'lax', + path: '/', }) } -export async function getSessionUser(request?: NextRequest): Promise<User | null> { +export async function deleteSession() { + const cookieStore = await cookies() + cookieStore.delete('session') +} + +export async function getSessionUser(): Promise<User | null> { try { // Get the session cookie - const cookieStore = request ? request.cookies : await cookies() - const sessionCookie = cookieStore.get("matrix_session") + const sessionCookie = (await cookies()).get('session') if (!sessionCookie?.value) { return null @@ -51,7 +81,7 @@ export async function getSessionUser(request?: NextRequest): Promise<User | null // Verify the JWT token try { - const { payload } = await jwtVerify(sessionCookie.value, JWT_SECRET) + const { payload } = await jwtVerify(sessionCookie.value, encodedKey) if (!payload.user) { return null @@ -66,17 +96,4 @@ export async function getSessionUser(request?: NextRequest): Promise<User | null console.error("Failed to verify session:", error) return null } -} - -export async function clearSession(response: NextResponse): Promise<void> { - // Clear the session cookie - response.cookies.set({ - name: "matrix_session", - value: "", - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "lax", - maxAge: 0, - path: "/", - }) -} +} +\ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts @@ -10,7 +10,7 @@ export async function middleware(request: NextRequest) { const isPublicPath = path === "/" || path === "/login" || path === "/register" // Check if the user is authenticated - const user = await getSessionUser(request) + const user = await getSessionUser() const isAuthenticated = !!user // If the path is dashboard and the user is not authenticated, redirect to login