-
+
What is this?
@@ -26,15 +26,15 @@ export function WhatIs() {
What actions you can perform depends on your role, but some remarkable ones are:
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
It's clear that you'll have access to different features depending on your role(s).
@@ -43,7 +43,7 @@ export function WhatIs() {
)
}
-export function FeatureBox({ icon: Icon, feature }: { icon: LucideIcon; feature: string }) {
+export function FeatureBox({ icon: Icon, feature }: { icon: IconType; feature: string }) {
return (
diff --git a/src/app/(auth)/onboarding/link/telegram.tsx b/src/app/(auth)/onboarding/link/telegram.tsx
index 86d5e0a..d6dd6bd 100644
--- a/src/app/(auth)/onboarding/link/telegram.tsx
+++ b/src/app/(auth)/onboarding/link/telegram.tsx
@@ -1,8 +1,8 @@
"use client"
import { APIError } from "better-auth/api"
-import { CircleCheckBig, ClockAlertIcon } from "lucide-react"
import { useRouter } from "next/navigation"
import { useCallback, useEffect, useState } from "react"
+import { FiCheckCircle, FiClock } from "react-icons/fi"
import { toast } from "sonner"
import { Code } from "@/components/code"
import { InputWithPrefix } from "@/components/input-prefix"
@@ -104,7 +104,7 @@ export function TelegramLink({ botUsername }: { botUsername: string }) {
if (success)
return (
-
+
Linked successfully!
Redirecting you in 5s...
@@ -113,7 +113,7 @@ export function TelegramLink({ botUsername }: { botUsername: string }) {
if (expired)
return (
-
+
Code Expired!
Regenerate
diff --git a/src/app/dashboard/(active)/account/delete-passkey.tsx b/src/app/dashboard/(active)/account/delete-passkey.tsx
index 0047e80..56a1435 100644
--- a/src/app/dashboard/(active)/account/delete-passkey.tsx
+++ b/src/app/dashboard/(active)/account/delete-passkey.tsx
@@ -1,7 +1,7 @@
"use client"
-import { Trash2Icon } from "lucide-react"
import { useRouter } from "next/navigation"
+import { FiTrash2 } from "react-icons/fi"
import { toast } from "sonner"
import {
AlertDialog,
@@ -34,7 +34,7 @@ export function DeletePasskey({ id }: { id: string }) {
-
+
Remove passkey
Are you sure you want to delete this passkey?
diff --git a/src/app/dashboard/(active)/account/page.tsx b/src/app/dashboard/(active)/account/page.tsx
index 0ac592a..8267448 100644
--- a/src/app/dashboard/(active)/account/page.tsx
+++ b/src/app/dashboard/(active)/account/page.tsx
@@ -1,5 +1,5 @@
-import { Calendar, CircleAlert, KeyIcon } from "lucide-react"
import { headers } from "next/headers"
+import { FiAlertCircle, FiCalendar, FiKey } from "react-icons/fi"
import { auth } from "@/lib/auth"
import { getServerSession } from "@/server/auth"
import { DeletePasskey } from "./delete-passkey"
@@ -28,7 +28,7 @@ export default async function Account() {
- {!user.name &&
}
+ {!user.name &&
}
Name:
{user.name ?
{user.name}
:
}
@@ -48,12 +48,12 @@ export default async function Account() {
{passkeys?.map((p) => (
-
+
{p.name}
-
+
Created on {p.createdAt.toLocaleDateString()}
diff --git a/src/app/dashboard/(active)/account/profile-pic.tsx b/src/app/dashboard/(active)/account/profile-pic.tsx
index 43d17dc..bfdf238 100644
--- a/src/app/dashboard/(active)/account/profile-pic.tsx
+++ b/src/app/dashboard/(active)/account/profile-pic.tsx
@@ -1,9 +1,9 @@
"use client"
import type { User } from "better-auth"
-import { Pencil, Upload, UserIcon, X } from "lucide-react"
import { useRouter } from "next/navigation"
import { forwardRef, useRef } from "react"
+import { FiEdit2, FiUpload, FiUser, FiX } from "react-icons/fi"
import { toast } from "sonner"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
@@ -42,15 +42,15 @@ export function ProfilePic({ user }: Props) {
uploadRef.current?.click()}>
- Upload
+ Upload
- Remove
+ Remove
@@ -59,7 +59,7 @@ export function ProfilePic({ user }: Props) {
- {user.name ? getInitials(user.name) : }
+ {user.name ? getInitials(user.name) : }
Max 1MB
diff --git a/src/app/dashboard/(active)/azure/groups/groups-list.tsx b/src/app/dashboard/(active)/azure/groups/groups-list.tsx
index 41909c7..2bf11d5 100644
--- a/src/app/dashboard/(active)/azure/groups/groups-list.tsx
+++ b/src/app/dashboard/(active)/azure/groups/groups-list.tsx
@@ -1,8 +1,8 @@
"use client"
-import { ChevronDown, Info, LoaderCircle, Mail, UserMinus, UserPlus } from "lucide-react"
import { useRouter } from "next/navigation"
import { useMemo, useState } from "react"
+import { FiChevronDown, FiInfo, FiLoader, FiMail, FiUserMinus, FiUserPlus } from "react-icons/fi"
import { toast } from "sonner"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount } from "@/components/ui/avatar"
@@ -63,7 +63,7 @@ export function GroupsList({ groups, directoryMembers }: { groups: AzureGroup[];
-
+
Looking for groups with 0-1 member(s)?
Groups with max one member are collected in a collapsed section at the bottom of the page.
@@ -86,7 +86,7 @@ export function GroupsList({ groups, directoryMembers }: { groups: AzureGroup[];
{multiMemberGroups.length} {singleMemberGroups.length === 1 ? "group" : "groups"}
-
+
}
/>
@@ -116,7 +116,7 @@ export function GroupsList({ groups, directoryMembers }: { groups: AzureGroup[];
{singleMemberGroups.length} {singleMemberGroups.length === 1 ? "group" : "groups"}
-
+
}
/>
@@ -148,7 +148,7 @@ function GroupRow({ group, directoryMembers }: { group: AzureGroup; directoryMem
{group.displayName}
{group.mailAddress && (
-
+
{group.mailAddress}
)}
@@ -263,7 +263,7 @@ function AddMemberDialog({ group, directoryMembers }: { group: AzureGroup; direc
/>
}
>
-
+
Add member
@@ -310,9 +310,9 @@ function AddMemberDialog({ group, directoryMembers }: { group: AzureGroup; direc
/>
{pending ? (
-
+
) : (
-
+
)}
{pending ? "Adding…" : "Add member"}
@@ -381,7 +381,7 @@ function RemoveMemberDialog({ group, directoryMembers }: { group: AzureGroup; di
/>
}
>
-
+
Remove member
@@ -430,9 +430,9 @@ function RemoveMemberDialog({ group, directoryMembers }: { group: AzureGroup; di
/>
{pending ? (
-
+
) : (
-
+
)}
{pending ? "Removing…" : "Remove member"}
diff --git a/src/app/dashboard/(active)/complete-profile.tsx b/src/app/dashboard/(active)/complete-profile.tsx
index 89fc4ac..10c0eb4 100644
--- a/src/app/dashboard/(active)/complete-profile.tsx
+++ b/src/app/dashboard/(active)/complete-profile.tsx
@@ -1,7 +1,7 @@
"use client"
-import { UserRoundPenIcon } from "lucide-react"
import Link from "next/link"
+import { FiEdit3 } from "react-icons/fi"
import { Button } from "@/components/ui/button"
import { useSession } from "@/lib/auth"
@@ -13,7 +13,7 @@ export function CompleteProfile() {
return (
!data.user.name && (
-
+
Your profile is incomplete, please enter the missing information.
Go complete
diff --git a/src/app/dashboard/(active)/page.tsx b/src/app/dashboard/(active)/page.tsx
index a0cb34b..b22e159 100644
--- a/src/app/dashboard/(active)/page.tsx
+++ b/src/app/dashboard/(active)/page.tsx
@@ -1,4 +1,4 @@
-import { InfoIcon } from "lucide-react"
+import { FiInfo } from "react-icons/fi"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { CompleteProfile } from "./complete-profile"
@@ -7,7 +7,7 @@ export default function AdminHome() {
-
+
Page under construction
Use the sidebar to access the sections
diff --git a/src/app/dashboard/(active)/telegram/grants/delete-grant.tsx b/src/app/dashboard/(active)/telegram/grants/delete-grant.tsx
index c12558b..2eb277b 100644
--- a/src/app/dashboard/(active)/telegram/grants/delete-grant.tsx
+++ b/src/app/dashboard/(active)/telegram/grants/delete-grant.tsx
@@ -1,8 +1,8 @@
"use client"
-import { OctagonX } from "lucide-react"
import { useRouter } from "next/navigation"
import { useState } from "react"
+import { FiAlertOctagon } from "react-icons/fi"
import { toast } from "sonner"
import {
AlertDialog,
@@ -58,7 +58,7 @@ export function DeleteGrant({
-
+
Interrupt
}
@@ -66,7 +66,7 @@ export function DeleteGrant({
-
+
Interrupt Grant
Are you sure you want to interrupt the grant?
diff --git a/src/app/dashboard/(active)/telegram/grants/new-grant.tsx b/src/app/dashboard/(active)/telegram/grants/new-grant.tsx
index 3f839aa..7c709c1 100644
--- a/src/app/dashboard/(active)/telegram/grants/new-grant.tsx
+++ b/src/app/dashboard/(active)/telegram/grants/new-grant.tsx
@@ -1,9 +1,9 @@
"use client"
import { format } from "date-fns"
-import { ChevronDownIcon, Plus } from "lucide-react"
import { useRouter } from "next/navigation"
import { useCallback, useEffect, useState } from "react"
+import { FiChevronDown, FiPlus } from "react-icons/fi"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
@@ -99,7 +99,7 @@ export function NewGrant() {
- New Grant
+ New Grant
}
/>
@@ -184,7 +184,7 @@ function DatePicker({ onSelect }: { onSelect: (date: Date, startTime: number, du
render={
{date ? format(date, "PPP") : "Select date"}
-
+
}
/>
diff --git a/src/app/dashboard/(active)/telegram/groups/group-row.tsx b/src/app/dashboard/(active)/telegram/groups/group-row.tsx
index bfb8006..e17328d 100644
--- a/src/app/dashboard/(active)/telegram/groups/group-row.tsx
+++ b/src/app/dashboard/(active)/telegram/groups/group-row.tsx
@@ -1,6 +1,6 @@
"use client"
-import { Copy } from "lucide-react"
import { useRouter } from "next/navigation"
+import { FiCopy } from "react-icons/fi"
import { toast } from "sonner"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
@@ -54,7 +54,7 @@ export function GroupRow({ row: r }: { row: TgGroup }) {
}
}}
>
-
+
diff --git a/src/app/dashboard/(active)/telegram/groups/leave-chat.tsx b/src/app/dashboard/(active)/telegram/groups/leave-chat.tsx
index 8a8a284..221564b 100644
--- a/src/app/dashboard/(active)/telegram/groups/leave-chat.tsx
+++ b/src/app/dashboard/(active)/telegram/groups/leave-chat.tsx
@@ -1,8 +1,8 @@
"use client"
-import { OctagonX, Trash2 } from "lucide-react"
import { useRouter } from "next/navigation"
import { useState } from "react"
+import { FiAlertOctagon, FiTrash2 } from "react-icons/fi"
import { toast } from "sonner"
import { Spinner } from "@/components/spinner"
import {
@@ -57,7 +57,7 @@ export function LeaveChat({ chatId }: { chatId: number }) {
-
+
Leave
}
@@ -65,7 +65,7 @@ export function LeaveChat({ chatId }: { chatId: number }) {
-
+
Leave chat
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/add-role.tsx b/src/app/dashboard/(active)/telegram/users/[id]/add-role.tsx
index 361ddc1..c702ab1 100644
--- a/src/app/dashboard/(active)/telegram/users/[id]/add-role.tsx
+++ b/src/app/dashboard/(active)/telegram/users/[id]/add-role.tsx
@@ -1,8 +1,8 @@
"use client"
import { USER_ROLE } from "@polinetwork/backend"
-import { Plus } from "lucide-react"
import { useRouter } from "next/navigation"
import { useState } from "react"
+import { FiPlus } from "react-icons/fi"
import { toast } from "sonner"
import { Spinner } from "@/components/spinner"
import { Badge } from "@/components/ui/badge"
@@ -89,7 +89,7 @@ export function AddRole({
- Add Role
+ Add Role
}
/>
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/card-message.tsx b/src/app/dashboard/(active)/telegram/users/[id]/card-message.tsx
index 46603cc..4f60cda 100644
--- a/src/app/dashboard/(active)/telegram/users/[id]/card-message.tsx
+++ b/src/app/dashboard/(active)/telegram/users/[id]/card-message.tsx
@@ -1,4 +1,4 @@
-import { ExternalLinkIcon } from "lucide-react"
+import { FiExternalLink } from "react-icons/fi"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardFooter } from "@/components/ui/card"
import { stripChatId } from "@/lib/utils/telegram"
@@ -30,7 +30,7 @@ export function MessageCard({ message: m }: { message: Message }) {
{m.group?.inviteLink && (
- Join Chat
+ Join Chat
)}
@@ -41,7 +41,7 @@ export function MessageCard({ message: m }: { message: Message }) {
aria-label="Open message in chat"
>
- Open
+ Open
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/card-user-grant.tsx b/src/app/dashboard/(active)/telegram/users/[id]/card-user-grant.tsx
index 7dbeb37..8e8de0e 100644
--- a/src/app/dashboard/(active)/telegram/users/[id]/card-user-grant.tsx
+++ b/src/app/dashboard/(active)/telegram/users/[id]/card-user-grant.tsx
@@ -1,6 +1,6 @@
"use client"
import { format } from "date-fns"
-import { Sparkle } from "lucide-react"
+import { FiZap } from "react-icons/fi"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import type { TgGrant, TgUser } from "@/server/trpc/types"
import { DeleteGrant } from "../../grants/delete-grant"
@@ -18,7 +18,7 @@ export function UserGrantCard({
- Ongoing Grant
+ Ongoing Grant
This user has an ongoing grant
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/card-user-info.tsx b/src/app/dashboard/(active)/telegram/users/[id]/card-user-info.tsx
index 52741df..8a44e6b 100644
--- a/src/app/dashboard/(active)/telegram/users/[id]/card-user-info.tsx
+++ b/src/app/dashboard/(active)/telegram/users/[id]/card-user-info.tsx
@@ -1,5 +1,5 @@
"use client"
-import { Star } from "lucide-react"
+import { FiStar } from "react-icons/fi"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { useSession } from "@/lib/auth"
@@ -30,7 +30,7 @@ export function UserInfoCard({
# User ID: {user.id}{" "}
{isSelf && (
- You
+ You
)}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/delete-group-admin.tsx b/src/app/dashboard/(active)/telegram/users/[id]/delete-group-admin.tsx
index 25b0b36..cfcd50b 100644
--- a/src/app/dashboard/(active)/telegram/users/[id]/delete-group-admin.tsx
+++ b/src/app/dashboard/(active)/telegram/users/[id]/delete-group-admin.tsx
@@ -1,8 +1,8 @@
"use client"
-import { Trash2, Trash2Icon } from "lucide-react"
import { useRouter } from "next/navigation"
import { useState } from "react"
+import { FiTrash2 } from "react-icons/fi"
import { toast } from "sonner"
import { Spinner } from "@/components/spinner"
import {
@@ -63,7 +63,7 @@ export function DeleteGroupAdmin({
-
+
Delete
}
@@ -71,7 +71,7 @@ export function DeleteGroupAdmin({
-
+
Remove Group Admin
Are you sure you want to remove the group admin?
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/new-group-admin.tsx b/src/app/dashboard/(active)/telegram/users/[id]/new-group-admin.tsx
index 840e20d..fc7f5e5 100644
--- a/src/app/dashboard/(active)/telegram/users/[id]/new-group-admin.tsx
+++ b/src/app/dashboard/(active)/telegram/users/[id]/new-group-admin.tsx
@@ -1,7 +1,7 @@
"use client"
-import { Plus, Search, X } from "lucide-react"
import { useRouter } from "next/navigation"
import { useState } from "react"
+import { FiPlus, FiSearch, FiX } from "react-icons/fi"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import {
@@ -84,7 +84,7 @@ export function NewGroupAdmin({
- New
+ New
}
/>
@@ -119,12 +119,12 @@ export function NewGroupAdmin({
value={groupQuery}
/>
-
+
{groups.length > 0 && (
-
+
Reset
)}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/page.tsx b/src/app/dashboard/(active)/telegram/users/[id]/page.tsx
index bc83ede..ec6649e 100644
--- a/src/app/dashboard/(active)/telegram/users/[id]/page.tsx
+++ b/src/app/dashboard/(active)/telegram/users/[id]/page.tsx
@@ -10,8 +10,8 @@ import { NewGroupAdmin } from "./new-group-admin"
export default async function TgUserDetails({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
- const parsedInt = parseInt(id, 10)
- if (isNaN(parsedInt)) notFound()
+ const parsedInt = Number(id)
+ if (!Number.isInteger(parsedInt) || !Number.isSafeInteger(parsedInt)) notFound()
const data = await getUserDetails(parsedInt)
if (!data) notFound()
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/remove-role.tsx b/src/app/dashboard/(active)/telegram/users/[id]/remove-role.tsx
index 82ddde0..261a6b4 100644
--- a/src/app/dashboard/(active)/telegram/users/[id]/remove-role.tsx
+++ b/src/app/dashboard/(active)/telegram/users/[id]/remove-role.tsx
@@ -1,8 +1,8 @@
"use client"
import { USER_ROLE } from "@polinetwork/backend"
-import { Minus } from "lucide-react"
import { useRouter } from "next/navigation"
import { useState } from "react"
+import { FiMinus } from "react-icons/fi"
import { toast } from "sonner"
import { Spinner } from "@/components/spinner"
import { Badge } from "@/components/ui/badge"
@@ -93,7 +93,7 @@ export function RemoveRole({
- Remove Role
+ Remove Role
}
/>
diff --git a/src/app/dashboard/(active)/telegram/users/page.tsx b/src/app/dashboard/(active)/telegram/users/page.tsx
index 5a52f6d..79d7a71 100644
--- a/src/app/dashboard/(active)/telegram/users/page.tsx
+++ b/src/app/dashboard/(active)/telegram/users/page.tsx
@@ -1,5 +1,5 @@
-import { Eye } from "lucide-react"
import Link from "next/link"
+import { FiEye } from "react-icons/fi"
import { SearchInput } from "@/components/search-input"
import { Button } from "@/components/ui/button"
import { trpc } from "@/server/trpc"
@@ -47,7 +47,7 @@ function UserRow({ row: r }: { row: Users[number] }) {
-
+
diff --git a/src/app/dashboard/(active)/web/guide/create-guide.tsx b/src/app/dashboard/(active)/web/guide/create-guide.tsx
index 6799ad0..5845298 100644
--- a/src/app/dashboard/(active)/web/guide/create-guide.tsx
+++ b/src/app/dashboard/(active)/web/guide/create-guide.tsx
@@ -1,8 +1,8 @@
"use client"
import { format } from "date-fns"
-import { ChevronDownIcon, Plus, Upload } from "lucide-react"
import { useState } from "react"
+import { FiChevronDown, FiPlus, FiUpload } from "react-icons/fi"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
@@ -84,7 +84,7 @@ export function CreateGuide({
- Add Guide
+ Add Guide
}
/>
@@ -116,7 +116,7 @@ export function CreateGuide({
render={
{date ? format(date, "dd/MM/yyyy") : "Select date"}
-
+
}
/>
@@ -142,7 +142,7 @@ export function CreateGuide({
htmlFor="file"
className="flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg border border-input bg-transparent px-2.5 text-sm transition-colors hover:bg-accent/50"
>
-
+
{file ? file.name : "Choose a PDF file..."}
diff --git a/src/app/dashboard/(active)/web/guide/delete-guide.tsx b/src/app/dashboard/(active)/web/guide/delete-guide.tsx
index 9d88482..9597629 100644
--- a/src/app/dashboard/(active)/web/guide/delete-guide.tsx
+++ b/src/app/dashboard/(active)/web/guide/delete-guide.tsx
@@ -1,7 +1,7 @@
"use client"
-import { OctagonX, Trash } from "lucide-react"
import { useState } from "react"
+import { FiAlertOctagon, FiTrash } from "react-icons/fi"
import { toast } from "sonner"
import {
AlertDialog,
@@ -52,14 +52,14 @@ export function DeleteGuide({
-
+
}
/>
-
+
Delete Guide
diff --git a/src/components/dashboard-sidebar/data.tsx b/src/components/dashboard-sidebar/data.tsx
index a50c731..0fa52ae 100644
--- a/src/components/dashboard-sidebar/data.tsx
+++ b/src/components/dashboard-sidebar/data.tsx
@@ -1,5 +1,5 @@
-import { BookOpen, Globe, MessageCircleMoreIcon, Sparkle, Users, UsersRound } from "lucide-react"
import Image from "next/image"
+import { FiBookOpen, FiGlobe, FiMessageSquare, FiUsers, FiZap } from "react-icons/fi"
import azureSvg from "@/assets/svg/azure.svg"
import telegramSvg from "@/assets/svg/telegram.svg"
@@ -9,23 +9,23 @@ export const DSData = {
title: "Telegram",
icon: ,
items: [
- { title: "Grants", url: "/dashboard/telegram/grants", icon: },
- { title: "Groups", url: "/dashboard/telegram/groups", icon: },
- { title: "Users", url: "/dashboard/telegram/users", icon: },
+ { title: "Grants", url: "/dashboard/telegram/grants", icon: },
+ { title: "Groups", url: "/dashboard/telegram/groups", icon: },
+ { title: "Users", url: "/dashboard/telegram/users", icon: },
],
},
{
title: "Azure",
icon: ,
items: [
- { title: "Groups", url: "/dashboard/azure/groups", icon: },
- { title: "Members", url: "/dashboard/azure/members", icon: },
+ { title: "Groups", url: "/dashboard/azure/groups", icon: },
+ { title: "Members", url: "/dashboard/azure/members", icon: },
],
},
{
title: "Web",
- icon: ,
- items: [{ title: "Guide", url: "/dashboard/web/guide", icon: }],
+ icon: ,
+ items: [{ title: "Guide", url: "/dashboard/web/guide", icon: }],
},
],
}
diff --git a/src/components/dashboard-sidebar/main-nav.tsx b/src/components/dashboard-sidebar/main-nav.tsx
index 7b7acf5..b61f719 100644
--- a/src/components/dashboard-sidebar/main-nav.tsx
+++ b/src/components/dashboard-sidebar/main-nav.tsx
@@ -1,8 +1,8 @@
"use client"
-import { ChevronRight } from "lucide-react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { useState } from "react"
+import { FiChevronRight } from "react-icons/fi"
import { COOKIES } from "@/constants"
import { useCookieStorage } from "@/hooks/use-cookie-storage"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible"
@@ -67,7 +67,7 @@ function DSMenuCategory({
{category.icon}
{category.title}
-
+
}
/>
diff --git a/src/components/dashboard-sidebar/user-nav.tsx b/src/components/dashboard-sidebar/user-nav.tsx
index 40ceabe..ca07cdc 100644
--- a/src/components/dashboard-sidebar/user-nav.tsx
+++ b/src/components/dashboard-sidebar/user-nav.tsx
@@ -1,8 +1,8 @@
"use client"
-import { Bell, ChevronsUpDown, LogOut, UserIcon } from "lucide-react"
import Link from "next/link"
import { useRouter } from "next/navigation"
+import { FiBell, FiChevronsDown, FiLogOut, FiUser } from "react-icons/fi"
import { toast } from "sonner"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import {
@@ -40,13 +40,13 @@ export function DSUserNav() {
>
- {user.name ? getInitials(user.name) : }
+ {user.name ? getInitials(user.name) : }
{user.name}
{user.email}
-
+
}
/>
@@ -61,7 +61,7 @@ export function DSUserNav() {
- {user.name ? getInitials(user.name) : }
+ {user.name ? getInitials(user.name) : }
{user.name}
@@ -74,12 +74,12 @@ export function DSUserNav() {
-
+
Account
-
+
Notifications
@@ -97,7 +97,7 @@ export function DSUserNav() {
})
}
>
-
+
Log out
diff --git a/src/components/header-login-button.tsx b/src/components/header-login-button.tsx
index ed3dd14..62057da 100644
--- a/src/components/header-login-button.tsx
+++ b/src/components/header-login-button.tsx
@@ -1,7 +1,7 @@
"use client"
-import { LogIn, LogOut } from "lucide-react"
import Link from "next/link"
import { usePathname, useRouter } from "next/navigation"
+import { FiLogIn, FiLogOut } from "react-icons/fi"
import { useIsMobile } from "@/hooks/use-mobile"
import { auth, useSession } from "@/lib/auth"
import { Button } from "./ui/button"
@@ -24,7 +24,7 @@ export function HeaderLoginButton() {
router.refresh()
}}
>
-
Logout
+
Logout
)
@@ -36,7 +36,7 @@ export function HeaderLoginButton() {
className="rounded-lg"
size={isMobile ? "icon" : "default"}
>
-
+
Login
diff --git a/src/components/header.tsx b/src/components/header.tsx
index 55c341e..d007f74 100644
--- a/src/components/header.tsx
+++ b/src/components/header.tsx
@@ -1,5 +1,5 @@
-import { GlobeIcon } from "lucide-react"
import Link from "next/link"
+import { FiGlobe } from "react-icons/fi"
import { ThemeButton } from "@/components/theme-button"
import { HeaderLoginButton } from "./header-login-button"
import { Logo } from "./logo"
@@ -22,7 +22,7 @@ export async function Header() {
-
+
diff --git a/src/components/logout-button.tsx b/src/components/logout-button.tsx
index 1a6a958..de99534 100644
--- a/src/components/logout-button.tsx
+++ b/src/components/logout-button.tsx
@@ -1,5 +1,5 @@
-import { LogOut } from "lucide-react"
import { redirect } from "next/navigation"
+import { FiLogOut } from "react-icons/fi"
import { Button } from "@/components/ui/button"
import { signOut } from "@/lib/auth"
@@ -13,7 +13,7 @@ export function LogoutButton() {
variant="destructive"
type="submit"
>
-
+
Logout
)
diff --git a/src/components/navigate-back.tsx b/src/components/navigate-back.tsx
index 463f442..febb551 100644
--- a/src/components/navigate-back.tsx
+++ b/src/components/navigate-back.tsx
@@ -1,7 +1,7 @@
"use client"
-import { ArrowLeft } from "lucide-react"
import { useRouter } from "next/navigation"
+import { FiArrowLeft } from "react-icons/fi"
import { Button } from "./ui/button"
export function NavigateBack() {
@@ -15,7 +15,7 @@ export function NavigateBack() {
router.back()
}}
>
-
+
)
}
diff --git a/src/components/search-input.tsx b/src/components/search-input.tsx
index e0e5a9e..43b9009 100644
--- a/src/components/search-input.tsx
+++ b/src/components/search-input.tsx
@@ -1,8 +1,8 @@
"use client"
-import { RefreshCcw, Search } from "lucide-react"
import { useRouter, useSearchParams } from "next/navigation"
import { useEffect, useState } from "react"
+import { FiRefreshCcw, FiSearch } from "react-icons/fi"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
@@ -35,11 +35,11 @@ export function SearchInput({ disabled }: { disabled?: boolean }) {
placeholder="Search..."
/>
-
+
Search
router.refresh()} variant="outline" disabled={disabled}>
-
+
Refresh
diff --git a/src/components/theme-button.tsx b/src/components/theme-button.tsx
index cbaa0ad..2ddc817 100644
--- a/src/components/theme-button.tsx
+++ b/src/components/theme-button.tsx
@@ -1,6 +1,6 @@
"use client"
-import { MoonIcon, SunIcon } from "lucide-react"
import { useTheme } from "next-themes"
+import { FiMoon, FiSun } from "react-icons/fi"
import { Button } from "./ui/button"
export function ThemeButton() {
@@ -9,8 +9,8 @@ export function ThemeButton() {
// TODO: remove disabled when light theme is ready
return (
setTheme(resolvedTheme === "light" ? "dark" : "light")}>
-
-
+
+
)
}
diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx
index e0deed4..28b736e 100644
--- a/src/components/ui/breadcrumb.tsx
+++ b/src/components/ui/breadcrumb.tsx
@@ -3,7 +3,7 @@ import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import { cn } from "@/lib/utils/shadcn"
-import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
+import { FiChevronRight, FiMoreHorizontal } from "react-icons/fi"
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
return (
@@ -86,7 +86,7 @@ function BreadcrumbSeparator({
{...props}
>
{children ?? (
-
+
)}
)
@@ -107,8 +107,7 @@ function BreadcrumbEllipsis({
)}
{...props}
>
-
+
More
)
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
index 686626b..a39b65e 100644
--- a/src/components/ui/calendar.tsx
+++ b/src/components/ui/calendar.tsx
@@ -10,7 +10,7 @@ import {
import { cn } from "@/lib/utils/shadcn"
import { Button, buttonVariants } from "@/components/ui/button"
-import { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon } from "lucide-react"
+import { FiChevronLeft, FiChevronRight, FiChevronDown } from "react-icons/fi"
function Calendar({
className,
@@ -147,18 +147,18 @@ function Calendar({
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return (
-
+
)
}
if (orientation === "right") {
return (
-
+
)
}
return (
-
+
)
},
DayButton: ({ ...props }) => (
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
index b57a702..7b8b0ef 100644
--- a/src/components/ui/command.tsx
+++ b/src/components/ui/command.tsx
@@ -15,7 +15,7 @@ import {
InputGroup,
InputGroupAddon,
} from "@/components/ui/input-group"
-import { SearchIcon, CheckIcon } from "lucide-react"
+import { FiSearch, FiCheck } from "react-icons/fi"
function Command({
className,
@@ -82,7 +82,7 @@ function CommandInput({
{...props}
/>
-
+
@@ -162,7 +162,7 @@ function CommandItem({
{...props}
>
{children}
-
+
)
}
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
index 9bb1f25..c3f389e 100644
--- a/src/components/ui/dialog.tsx
+++ b/src/components/ui/dialog.tsx
@@ -5,7 +5,7 @@ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
import { cn } from "@/lib/utils/shadcn"
import { Button } from "@/components/ui/button"
-import { XIcon } from "lucide-react"
+import { FiX } from "react-icons/fi"
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
return
@@ -61,7 +61,6 @@ function DialogContent({
{children}
{showCloseButton && (
}
>
-
+
Close
)}
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
index d1c45d7..385ff9f 100644
--- a/src/components/ui/dropdown-menu.tsx
+++ b/src/components/ui/dropdown-menu.tsx
@@ -4,7 +4,7 @@ import * as React from "react"
import { Menu as MenuPrimitive } from "@base-ui/react/menu"
import { cn } from "@/lib/utils/shadcn"
-import { ChevronRightIcon, CheckIcon } from "lucide-react"
+import { FiChevronRight, FiCheck } from "react-icons/fi"
function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
return
@@ -119,7 +119,7 @@ function DropdownMenuSubTrigger({
{...props}
>
{children}
-
+
)
}
@@ -170,8 +170,7 @@ function DropdownMenuCheckboxItem({
data-slot="dropdown-menu-checkbox-item-indicator"
>
-
+
{children}
@@ -211,8 +210,7 @@ function DropdownMenuRadioItem({
data-slot="dropdown-menu-radio-item-indicator"
>
-
+
{children}
diff --git a/src/components/ui/input-otp.tsx b/src/components/ui/input-otp.tsx
index dcbc966..baca16a 100644
--- a/src/components/ui/input-otp.tsx
+++ b/src/components/ui/input-otp.tsx
@@ -4,7 +4,7 @@ import * as React from "react"
import { OTPInput, OTPInputContext } from "input-otp"
import { cn } from "@/lib/utils/shadcn"
-import { MinusIcon } from "lucide-react"
+import { FiMinus } from "react-icons/fi"
function InputOTP({
className,
@@ -78,8 +78,7 @@ function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
role="separator"
{...props}
>
-
+
)
}
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
index bef549b..efd2cb8 100644
--- a/src/components/ui/select.tsx
+++ b/src/components/ui/select.tsx
@@ -4,7 +4,7 @@ import * as React from "react"
import { Select as SelectPrimitive } from "@base-ui/react/select"
import { cn } from "@/lib/utils/shadcn"
-import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
+import { FiChevronDown, FiCheck, FiChevronUp } from "react-icons/fi"
const Select = SelectPrimitive.Root
@@ -49,7 +49,7 @@ function SelectTrigger({
{children}
+
}
/>
@@ -130,7 +130,7 @@ function SelectItem({
}
>
-
+
)
@@ -162,8 +162,7 @@ function SelectScrollUpButton({
)}
{...props}
>
-
+
)
}
@@ -181,8 +180,7 @@ function SelectScrollDownButton({
)}
{...props}
>
-
+
)
}
diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx
index 308776c..a3bf29e 100644
--- a/src/components/ui/sheet.tsx
+++ b/src/components/ui/sheet.tsx
@@ -5,7 +5,7 @@ import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
import { cn } from "@/lib/utils/shadcn"
import { Button } from "@/components/ui/button"
-import { XIcon } from "lucide-react"
+import { FiX } from "react-icons/fi"
function Sheet({ ...props }: SheetPrimitive.Root.Props) {
return
@@ -70,8 +70,7 @@ function SheetContent({
/>
}
>
-
+
Close
)}
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx
index 3205f8a..a984eb5 100644
--- a/src/components/ui/sidebar.tsx
+++ b/src/components/ui/sidebar.tsx
@@ -23,7 +23,7 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
-import { PanelLeftIcon } from "lucide-react"
+import { FiSidebar } from "react-icons/fi"
import { useCookieStorage } from "@/hooks/use-cookie-storage"
import { COOKIES } from "@/constants"
@@ -275,7 +275,7 @@ function SidebarTrigger({
}}
{...props}
>
-
+
Toggle Sidebar
)
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
index 9280ee5..ddee39b 100644
--- a/src/components/ui/sonner.tsx
+++ b/src/components/ui/sonner.tsx
@@ -2,7 +2,7 @@
import { useTheme } from "next-themes"
import { Toaster as Sonner, type ToasterProps } from "sonner"
-import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
+import { FiCheckCircle, FiInfo, FiAlertTriangle, FiAlertOctagon, FiLoader } from "react-icons/fi"
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
@@ -13,19 +13,19 @@ const Toaster = ({ ...props }: ToasterProps) => {
className="toaster group"
icons={{
success: (
-
+
),
info: (
-
+
),
warning: (
-
+
),
error: (
-
+
),
loading: (
-
+
),
}}
style={
diff --git a/src/components/user-select.tsx b/src/components/user-select.tsx
index 68b2a14..bcb03bd 100644
--- a/src/components/user-select.tsx
+++ b/src/components/user-select.tsx
@@ -1,7 +1,7 @@
"use client"
-import { Search, X } from "lucide-react"
import { useState } from "react"
+import { FiSearch, FiX } from "react-icons/fi"
import { toast } from "sonner"
import { fmtUser } from "@/lib/utils/telegram"
import { getUserInfo, searchUserInfo } from "@/server/actions/users"
@@ -98,11 +98,11 @@ export function UserSelect({ onUser, onReset }: Props) {
{user ? (
-
+
) : (
-
+
)}
From d5fb641e960f5ead3a337f593286d33aafcab218 Mon Sep 17 00:00:00 2001
From: Lorenzo Corallo <66379281+lorenzocorallo@users.noreply.github.com>
Date: Tue, 11 Aug 2026 18:39:55 +0200
Subject: [PATCH 07/10] ci: publish arm64 image (#55)
* ci: publish multi-arch container images
* ci: build admin on native architecture runners
* ci: publish images only after main updates
* ci: serialize container publication
---
.github/workflows/docker.yml | 59 ++++++++++++++++++++++++++++--------
1 file changed, 46 insertions(+), 13 deletions(-)
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index b1c7c89..b109860 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -1,32 +1,65 @@
name: Docker Image CI
on:
- workflow_dispatch:
- workflow_run:
- workflows: [Test]
+ push:
branches: [main]
- types:
- - completed
+
+concurrency:
+ group: docker-publish-${{ github.ref }}
+ cancel-in-progress: true
jobs:
build:
- name: Build
- if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
- runs-on: ubuntu-latest
+ name: Build ${{ matrix.platform }}
+ permissions:
+ contents: read
+ packages: write
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - platform: linux/amd64
+ runner: ubuntu-latest
+ suffix: amd64
+ - platform: linux/arm64
+ runner: ubuntu-24.04-arm
+ suffix: arm64
+ runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- - uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- - uses: docker/login-action@v2
+ - uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build the Docker image
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v6
with:
context: .
- platforms: linux/amd64
+ platforms: ${{ matrix.platform }}
push: true
- tags: ghcr.io/polinetworkorg/admin:latest
+ tags: ghcr.io/polinetworkorg/admin:build-${{ github.sha }}-${{ matrix.suffix }}
+
+ manifest:
+ name: Publish multi-arch manifest
+ needs: build
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - uses: docker/setup-buildx-action@v3
+ - uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Publish latest manifest
+ run: |
+ docker buildx imagetools create \
+ --tag ghcr.io/polinetworkorg/admin:latest \
+ ghcr.io/polinetworkorg/admin:build-${{ github.sha }}-amd64 \
+ ghcr.io/polinetworkorg/admin:build-${{ github.sha }}-arm64
+ docker buildx imagetools inspect ghcr.io/polinetworkorg/admin:latest
From 5f3c8238e774037faaedefb800bbea426e9e62b5 Mon Sep 17 00:00:00 2001
From: Lorenzo Corallo
Date: Tue, 18 Aug 2026 21:28:21 +0200
Subject: [PATCH 08/10] refactor: migrate admin dashboard to TanStack Start
Replace the Next.js application with the audited TanStack Start rewrite. Preserve the dashboard workflows and stacked UI work while fixing authorization, request isolation, auth proxying, production safeguards, validation, and feature colocation.
---
.dockerignore | 3 +-
.env.example | 4 +-
.gitignore | 10 +-
AGENTS.md | 33 +
CLAUDE.md | 1 +
Dockerfile | 57 +-
README.md | 30 +-
biome.jsonc | 24 +-
components.json | 17 +-
next-env.d.ts | 6 -
next.config.ts | 30 -
package.json | 75 +-
pnpm-lock.yaml | 4935 ++++++++---------
postcss.config.js | 5 -
src/app/(auth)/layout.tsx | 15 -
src/app/(auth)/login/can-i-access.tsx | 77 -
src/app/(auth)/login/login-button.tsx | 18 -
src/app/(auth)/login/login-form.tsx | 196 -
src/app/(auth)/login/page.tsx | 25 -
src/app/(auth)/login/what-is.tsx | 55 -
src/app/(auth)/onboarding/_layout.tsx | 7 -
src/app/(auth)/onboarding/link/logout.tsx | 32 -
src/app/(auth)/onboarding/link/page.tsx | 38 -
src/app/(auth)/onboarding/link/telegram.tsx | 225 -
src/app/(auth)/onboarding/no-role/page.tsx | 38 -
.../(active)/account/delete-passkey.tsx | 51 -
.../dashboard/(active)/account/loading.tsx | 34 -
src/app/dashboard/(active)/account/page.tsx | 67 -
.../(active)/account/passkey-button.tsx | 35 -
.../(active)/account/profile-pic.tsx | 108 -
.../dashboard/(active)/account/set-name.tsx | 64 -
.../dashboard/(active)/account/telegram.tsx | 17 -
.../(active)/azure/groups/groups-list.tsx | 443 --
.../(active)/azure/groups/loading.tsx | 44 -
.../dashboard/(active)/azure/groups/page.tsx | 17 -
.../(active)/azure/members/columns.tsx | 50 -
.../azure/members/create-assoc-member.tsx | 140 -
.../(active)/azure/members/loading.tsx | 9 -
.../dashboard/(active)/azure/members/page.tsx | 19 -
.../azure/members/set-assoc-number-dialog.tsx | 97 -
.../(active)/azure/members/table.tsx | 72 -
src/app/dashboard/(active)/breadcrumb.tsx | 74 -
.../dashboard/(active)/complete-profile.tsx | 24 -
src/app/dashboard/(active)/layout.tsx | 50 -
src/app/dashboard/(active)/not-found.tsx | 19 -
src/app/dashboard/(active)/page.tsx | 16 -
.../(active)/telegram/grants/delete-grant.tsx | 83 -
.../(active)/telegram/grants/grant-list.tsx | 42 -
.../(active)/telegram/grants/loading.tsx | 51 -
.../(active)/telegram/grants/new-grant.tsx | 254 -
.../(active)/telegram/grants/page.tsx | 37 -
.../(active)/telegram/groups/group-row.tsx | 76 -
.../(active)/telegram/groups/leave-chat.tsx | 84 -
.../(active)/telegram/groups/loading.tsx | 26 -
.../(active)/telegram/groups/page.tsx | 34 -
.../(active)/telegram/users/[id]/add-role.tsx | 148 -
.../telegram/users/[id]/card-audit-log.tsx | 40 -
.../telegram/users/[id]/card-group-admin.tsx | 39 -
.../telegram/users/[id]/card-message.tsx | 50 -
.../telegram/users/[id]/card-user-grant.tsx | 34 -
.../telegram/users/[id]/card-user-info.tsx | 58 -
.../users/[id]/delete-group-admin.tsx | 88 -
.../(active)/telegram/users/[id]/loading.tsx | 37 -
.../telegram/users/[id]/new-group-admin.tsx | 163 -
.../(active)/telegram/users/[id]/page.tsx | 83 -
.../telegram/users/[id]/remove-role.tsx | 152 -
.../(active)/telegram/users/loading.tsx | 25 -
.../(active)/telegram/users/page.tsx | 56 -
.../dashboard/(active)/web/guide/columns.tsx | 35 -
.../(active)/web/guide/create-guide.tsx | 169 -
.../(active)/web/guide/delete-guide.tsx | 79 -
src/app/dashboard/(active)/web/guide/page.tsx | 12 -
.../dashboard/(active)/web/guide/table.tsx | 39 -
src/app/dashboard/(active)/web/guide/types.ts | 6 -
src/app/dashboard/disabled/page.tsx | 44 -
src/app/dashboard/inactive/logout.tsx | 0
src/app/dashboard/inactive/page.tsx | 53 -
src/app/dashboard/layout.tsx | 24 -
src/app/layout.tsx | 53 -
src/app/page.tsx | 20 -
src/app/testtrpc/page.tsx | 19 -
src/assets/logo.svg | 9 -
src/assets/svg/account_disabled.svg | 52 -
src/assets/svg/account_inactive.svg | 1 -
src/assets/svg/login-1.svg | 1 -
src/assets/svg/login-2.svg | 1 -
src/assets/svg/login-3.svg | 1 -
src/assets/svg/logo.svg | 1 -
src/assets/svg/shapes/big-blue.svg | 1 -
src/assets/svg/shapes/big-teal.svg | 1 -
src/assets/svg/shapes/looper.svg | 1 -
src/assets/svg/shapes/small-blue.svg | 1 -
src/components/app-mark.tsx | 44 +
src/components/code.tsx | 77 -
src/components/dashboard-breadcrumb.tsx | 78 +
src/components/dashboard-frame.tsx | 61 +
src/components/dashboard-navigation.ts | 84 +
src/components/dashboard-sidebar.tsx | 264 +
src/components/dashboard-sidebar/data.tsx | 49 -
src/components/dashboard-sidebar/index.tsx | 45 -
src/components/dashboard-sidebar/main-nav.tsx | 103 -
src/components/dashboard-sidebar/user-nav.tsx | 108 -
src/components/data-table.tsx | 64 -
src/components/data-toolbar.tsx | 86 +
src/components/empty-state.tsx | 28 +
src/components/header-login-button.tsx | 44 -
src/components/header.tsx | 31 -
src/components/input-prefix.tsx | 24 -
src/components/loading-skeleton.tsx | 93 +
src/components/logo.tsx | 6 -
src/components/logout-button.tsx | 20 -
src/components/navigate-back.tsx | 21 -
src/components/page-header.tsx | 26 +
src/components/pagination.tsx | 131 +
src/components/reui/stepper.tsx | 421 ++
src/components/route-error.tsx | 49 +
src/components/search-input.tsx | 47 -
src/components/shapes.tsx | 32 -
src/components/spinner.tsx | 26 -
.../telegram/create-grant-dialog.tsx | 462 ++
.../telegram/grant-date-time-fields.tsx | 373 ++
src/components/theme-button.tsx | 16 -
src/components/theme-provider.tsx | 8 -
src/components/theme-toggle.tsx | 41 +
src/components/ui/alert-dialog.tsx | 56 +-
src/components/ui/alert.tsx | 35 +-
src/components/ui/avatar.tsx | 30 +-
src/components/ui/badge.tsx | 13 +-
src/components/ui/breadcrumb.tsx | 59 +-
src/components/ui/button.tsx | 35 +-
src/components/ui/calendar.tsx | 93 +-
src/components/ui/card.tsx | 55 +-
src/components/ui/collapsible.tsx | 10 +-
src/components/ui/combobox.tsx | 111 +
src/components/ui/command.tsx | 81 +-
src/components/ui/dialog.tsx | 59 +-
src/components/ui/dropdown-menu.tsx | 55 +-
src/components/ui/empty.tsx | 87 +
src/components/ui/field.tsx | 222 +-
src/components/ui/form.tsx | 179 -
src/components/ui/input-group.tsx | 72 +-
src/components/ui/input-otp.tsx | 86 -
src/components/ui/input.tsx | 17 +-
src/components/ui/label.tsx | 10 +-
src/components/ui/pagination.tsx | 92 +
src/components/ui/popover.tsx | 41 +-
src/components/ui/progress.tsx | 80 -
src/components/ui/select.tsx | 199 -
src/components/ui/separator.tsx | 10 +-
src/components/ui/sheet.tsx | 58 +-
src/components/ui/sidebar.tsx | 109 +-
src/components/ui/skeleton.tsx | 10 +-
src/components/ui/sonner.tsx | 44 +-
src/components/ui/table.tsx | 77 +-
src/components/ui/tabs.tsx | 82 -
src/components/ui/textarea.tsx | 4 +-
src/components/ui/toggle-group.tsx | 17 +-
src/components/ui/toggle.tsx | 17 +-
src/components/ui/tooltip.tsx | 23 +-
src/components/ui/wheel-picker.tsx | 678 +++
src/components/user-select.tsx | 112 -
src/constants.ts | 4 -
src/env.ts | 48 +-
src/features/account/account-page.tsx | 85 +
src/features/account/account.functions.ts | 15 +
src/features/account/account.validation.ts | 7 +
src/features/account/confirmation-dialog.tsx | 52 +
src/features/account/profile-sections.tsx | 179 +
src/features/account/security-sections.tsx | 211 +
src/features/account/types.ts | 16 +
src/features/account/use-account.ts | 208 +
src/features/auth/auth.functions.ts | 21 +
src/features/auth/login-page.tsx | 191 +
src/features/azure/azure.functions.ts | 58 +
src/features/azure/group-membership.tsx | 275 +
src/features/azure/groups-page.tsx | 190 +
src/features/azure/member-dialog.tsx | 162 +
src/features/azure/members-page.tsx | 282 +
src/features/dashboard/overview-page.tsx | 119 +
src/features/guides/guide-dialogs.tsx | 251 +
src/features/guides/guides-page.tsx | 145 +
src/features/guides/guides.functions.ts | 32 +
src/features/guides/guides.validation.ts | 13 +
src/features/guides/types.ts | 3 +
.../onboarding/telegram-link-page.tsx | 131 +
src/features/onboarding/use-telegram-link.ts | 231 +
src/features/telegram/grants-page.tsx | 253 +
src/features/telegram/grants.functions.ts | 44 +
src/features/telegram/groups-page.tsx | 275 +
src/features/telegram/groups.functions.ts | 23 +
src/features/telegram/leave-group-dialog.tsx | 74 +
.../telegram/user-detail/grant-dialogs.tsx | 83 +
.../user-detail/group-admin-dialog.tsx | 206 +
src/features/telegram/user-detail/profile.tsx | 283 +
.../telegram/user-detail/role-dialog.tsx | 178 +
.../telegram/user-detail/sections.tsx | 88 +
src/features/telegram/user-detail/types.ts | 13 +
src/features/telegram/users-page.tsx | 166 +
src/features/telegram/users.functions.ts | 112 +
src/hooks/use-cookie-storage.tsx | 49 -
src/hooks/use-local-storage.tsx | 31 -
src/hooks/use-mobile.tsx | 19 -
src/hooks/use-session-storage.tsx | 39 -
src/index.css | 181 -
.../trpc/types.tsx => lib/api/types.ts} | 2 +-
src/lib/auth-plugins.ts | 11 +
src/lib/auth.ts | 22 +-
src/lib/table.ts | 31 +
src/lib/{utils/shadcn.ts => utils.ts} | 0
src/lib/utils/index.ts | 17 -
src/lib/utils/telegram.ts | 18 -
src/middleware.ts | 14 -
src/routeTree.gen.ts | 376 ++
src/router.tsx | 17 +
src/routes/__root.tsx | 40 +
src/routes/api/auth/$.ts | 14 +
src/routes/dashboard.tsx | 21 +
src/routes/dashboard/account.tsx | 11 +
src/routes/dashboard/azure/groups.tsx | 15 +
src/routes/dashboard/azure/members.tsx | 15 +
src/routes/dashboard/index.tsx | 4 +
src/routes/dashboard/telegram/grants.tsx | 15 +
src/routes/dashboard/telegram/groups.tsx | 15 +
.../dashboard/telegram/users/$userId.tsx | 25 +
src/routes/dashboard/telegram/users/index.tsx | 14 +
src/routes/dashboard/web/guides.tsx | 14 +
src/routes/index.tsx | 7 +
src/routes/login.tsx | 11 +
src/routes/onboarding/link.tsx | 19 +
src/routes/onboarding/unauthorized.tsx | 74 +
src/server/actions/azure.ts | 43 -
src/server/actions/grants.ts | 27 -
src/server/actions/groups.ts | 22 -
src/server/actions/guides.ts | 27 -
src/server/actions/test.ts | 6 -
src/server/actions/users.ts | 77 -
src/server/auth-proxy-core.ts | 14 +
src/server/auth-proxy.server.ts | 13 +
src/server/auth.middleware.ts | 66 +
src/server/auth.server.ts | 82 +
src/server/auth/index.ts | 23 -
src/server/authorization.ts | 10 +
src/server/backend.server.ts | 29 +
src/server/request-headers.ts | 4 +
src/server/runtime-env.ts | 13 +
src/server/trpc/index.tsx | 28 -
src/styles.css | 315 ++
src/utils/cookies.ts | 56 -
src/vite-env.d.ts | 1 +
tailwind.config.ts | 5 +
tests/server-security.test.mjs | 160 +
tsconfig.json | 49 +-
tsr.config.json | 3 +
vite.config.ts | 11 +
254 files changed, 12076 insertions(+), 10545 deletions(-)
create mode 100644 AGENTS.md
create mode 120000 CLAUDE.md
delete mode 100644 next-env.d.ts
delete mode 100644 next.config.ts
delete mode 100644 postcss.config.js
delete mode 100644 src/app/(auth)/layout.tsx
delete mode 100644 src/app/(auth)/login/can-i-access.tsx
delete mode 100644 src/app/(auth)/login/login-button.tsx
delete mode 100644 src/app/(auth)/login/login-form.tsx
delete mode 100644 src/app/(auth)/login/page.tsx
delete mode 100644 src/app/(auth)/login/what-is.tsx
delete mode 100644 src/app/(auth)/onboarding/_layout.tsx
delete mode 100644 src/app/(auth)/onboarding/link/logout.tsx
delete mode 100644 src/app/(auth)/onboarding/link/page.tsx
delete mode 100644 src/app/(auth)/onboarding/link/telegram.tsx
delete mode 100644 src/app/(auth)/onboarding/no-role/page.tsx
delete mode 100644 src/app/dashboard/(active)/account/delete-passkey.tsx
delete mode 100644 src/app/dashboard/(active)/account/loading.tsx
delete mode 100644 src/app/dashboard/(active)/account/page.tsx
delete mode 100644 src/app/dashboard/(active)/account/passkey-button.tsx
delete mode 100644 src/app/dashboard/(active)/account/profile-pic.tsx
delete mode 100644 src/app/dashboard/(active)/account/set-name.tsx
delete mode 100644 src/app/dashboard/(active)/account/telegram.tsx
delete mode 100644 src/app/dashboard/(active)/azure/groups/groups-list.tsx
delete mode 100644 src/app/dashboard/(active)/azure/groups/loading.tsx
delete mode 100644 src/app/dashboard/(active)/azure/groups/page.tsx
delete mode 100644 src/app/dashboard/(active)/azure/members/columns.tsx
delete mode 100644 src/app/dashboard/(active)/azure/members/create-assoc-member.tsx
delete mode 100644 src/app/dashboard/(active)/azure/members/loading.tsx
delete mode 100644 src/app/dashboard/(active)/azure/members/page.tsx
delete mode 100644 src/app/dashboard/(active)/azure/members/set-assoc-number-dialog.tsx
delete mode 100644 src/app/dashboard/(active)/azure/members/table.tsx
delete mode 100644 src/app/dashboard/(active)/breadcrumb.tsx
delete mode 100644 src/app/dashboard/(active)/complete-profile.tsx
delete mode 100644 src/app/dashboard/(active)/layout.tsx
delete mode 100644 src/app/dashboard/(active)/not-found.tsx
delete mode 100644 src/app/dashboard/(active)/page.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/grants/delete-grant.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/grants/grant-list.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/grants/loading.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/grants/new-grant.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/grants/page.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/groups/group-row.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/groups/leave-chat.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/groups/loading.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/groups/page.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/[id]/add-role.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/[id]/card-audit-log.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/[id]/card-group-admin.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/[id]/card-message.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/[id]/card-user-grant.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/[id]/card-user-info.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/[id]/delete-group-admin.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/[id]/loading.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/[id]/new-group-admin.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/[id]/page.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/[id]/remove-role.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/loading.tsx
delete mode 100644 src/app/dashboard/(active)/telegram/users/page.tsx
delete mode 100644 src/app/dashboard/(active)/web/guide/columns.tsx
delete mode 100644 src/app/dashboard/(active)/web/guide/create-guide.tsx
delete mode 100644 src/app/dashboard/(active)/web/guide/delete-guide.tsx
delete mode 100644 src/app/dashboard/(active)/web/guide/page.tsx
delete mode 100644 src/app/dashboard/(active)/web/guide/table.tsx
delete mode 100644 src/app/dashboard/(active)/web/guide/types.ts
delete mode 100644 src/app/dashboard/disabled/page.tsx
delete mode 100644 src/app/dashboard/inactive/logout.tsx
delete mode 100644 src/app/dashboard/inactive/page.tsx
delete mode 100644 src/app/dashboard/layout.tsx
delete mode 100644 src/app/layout.tsx
delete mode 100644 src/app/page.tsx
delete mode 100644 src/app/testtrpc/page.tsx
delete mode 100644 src/assets/logo.svg
delete mode 100644 src/assets/svg/account_disabled.svg
delete mode 100644 src/assets/svg/account_inactive.svg
delete mode 100644 src/assets/svg/login-1.svg
delete mode 100644 src/assets/svg/login-2.svg
delete mode 100644 src/assets/svg/login-3.svg
delete mode 100644 src/assets/svg/logo.svg
delete mode 100644 src/assets/svg/shapes/big-blue.svg
delete mode 100644 src/assets/svg/shapes/big-teal.svg
delete mode 100644 src/assets/svg/shapes/looper.svg
delete mode 100644 src/assets/svg/shapes/small-blue.svg
create mode 100644 src/components/app-mark.tsx
delete mode 100644 src/components/code.tsx
create mode 100644 src/components/dashboard-breadcrumb.tsx
create mode 100644 src/components/dashboard-frame.tsx
create mode 100644 src/components/dashboard-navigation.ts
create mode 100644 src/components/dashboard-sidebar.tsx
delete mode 100644 src/components/dashboard-sidebar/data.tsx
delete mode 100644 src/components/dashboard-sidebar/index.tsx
delete mode 100644 src/components/dashboard-sidebar/main-nav.tsx
delete mode 100644 src/components/dashboard-sidebar/user-nav.tsx
delete mode 100644 src/components/data-table.tsx
create mode 100644 src/components/data-toolbar.tsx
create mode 100644 src/components/empty-state.tsx
delete mode 100644 src/components/header-login-button.tsx
delete mode 100644 src/components/header.tsx
delete mode 100644 src/components/input-prefix.tsx
create mode 100644 src/components/loading-skeleton.tsx
delete mode 100644 src/components/logo.tsx
delete mode 100644 src/components/logout-button.tsx
delete mode 100644 src/components/navigate-back.tsx
create mode 100644 src/components/page-header.tsx
create mode 100644 src/components/pagination.tsx
create mode 100644 src/components/reui/stepper.tsx
create mode 100644 src/components/route-error.tsx
delete mode 100644 src/components/search-input.tsx
delete mode 100644 src/components/shapes.tsx
delete mode 100644 src/components/spinner.tsx
create mode 100644 src/components/telegram/create-grant-dialog.tsx
create mode 100644 src/components/telegram/grant-date-time-fields.tsx
delete mode 100644 src/components/theme-button.tsx
delete mode 100644 src/components/theme-provider.tsx
create mode 100644 src/components/theme-toggle.tsx
create mode 100644 src/components/ui/combobox.tsx
create mode 100644 src/components/ui/empty.tsx
delete mode 100644 src/components/ui/form.tsx
delete mode 100644 src/components/ui/input-otp.tsx
create mode 100644 src/components/ui/pagination.tsx
delete mode 100644 src/components/ui/progress.tsx
delete mode 100644 src/components/ui/select.tsx
delete mode 100644 src/components/ui/tabs.tsx
create mode 100644 src/components/ui/wheel-picker.tsx
delete mode 100644 src/components/user-select.tsx
delete mode 100644 src/constants.ts
create mode 100644 src/features/account/account-page.tsx
create mode 100644 src/features/account/account.functions.ts
create mode 100644 src/features/account/account.validation.ts
create mode 100644 src/features/account/confirmation-dialog.tsx
create mode 100644 src/features/account/profile-sections.tsx
create mode 100644 src/features/account/security-sections.tsx
create mode 100644 src/features/account/types.ts
create mode 100644 src/features/account/use-account.ts
create mode 100644 src/features/auth/auth.functions.ts
create mode 100644 src/features/auth/login-page.tsx
create mode 100644 src/features/azure/azure.functions.ts
create mode 100644 src/features/azure/group-membership.tsx
create mode 100644 src/features/azure/groups-page.tsx
create mode 100644 src/features/azure/member-dialog.tsx
create mode 100644 src/features/azure/members-page.tsx
create mode 100644 src/features/dashboard/overview-page.tsx
create mode 100644 src/features/guides/guide-dialogs.tsx
create mode 100644 src/features/guides/guides-page.tsx
create mode 100644 src/features/guides/guides.functions.ts
create mode 100644 src/features/guides/guides.validation.ts
create mode 100644 src/features/guides/types.ts
create mode 100644 src/features/onboarding/telegram-link-page.tsx
create mode 100644 src/features/onboarding/use-telegram-link.ts
create mode 100644 src/features/telegram/grants-page.tsx
create mode 100644 src/features/telegram/grants.functions.ts
create mode 100644 src/features/telegram/groups-page.tsx
create mode 100644 src/features/telegram/groups.functions.ts
create mode 100644 src/features/telegram/leave-group-dialog.tsx
create mode 100644 src/features/telegram/user-detail/grant-dialogs.tsx
create mode 100644 src/features/telegram/user-detail/group-admin-dialog.tsx
create mode 100644 src/features/telegram/user-detail/profile.tsx
create mode 100644 src/features/telegram/user-detail/role-dialog.tsx
create mode 100644 src/features/telegram/user-detail/sections.tsx
create mode 100644 src/features/telegram/user-detail/types.ts
create mode 100644 src/features/telegram/users-page.tsx
create mode 100644 src/features/telegram/users.functions.ts
delete mode 100644 src/hooks/use-cookie-storage.tsx
delete mode 100644 src/hooks/use-local-storage.tsx
delete mode 100644 src/hooks/use-mobile.tsx
delete mode 100644 src/hooks/use-session-storage.tsx
delete mode 100644 src/index.css
rename src/{server/trpc/types.tsx => lib/api/types.ts} (88%)
create mode 100644 src/lib/auth-plugins.ts
create mode 100644 src/lib/table.ts
rename src/lib/{utils/shadcn.ts => utils.ts} (100%)
delete mode 100644 src/lib/utils/index.ts
delete mode 100644 src/lib/utils/telegram.ts
delete mode 100644 src/middleware.ts
create mode 100644 src/routeTree.gen.ts
create mode 100644 src/router.tsx
create mode 100644 src/routes/__root.tsx
create mode 100644 src/routes/api/auth/$.ts
create mode 100644 src/routes/dashboard.tsx
create mode 100644 src/routes/dashboard/account.tsx
create mode 100644 src/routes/dashboard/azure/groups.tsx
create mode 100644 src/routes/dashboard/azure/members.tsx
create mode 100644 src/routes/dashboard/index.tsx
create mode 100644 src/routes/dashboard/telegram/grants.tsx
create mode 100644 src/routes/dashboard/telegram/groups.tsx
create mode 100644 src/routes/dashboard/telegram/users/$userId.tsx
create mode 100644 src/routes/dashboard/telegram/users/index.tsx
create mode 100644 src/routes/dashboard/web/guides.tsx
create mode 100644 src/routes/index.tsx
create mode 100644 src/routes/login.tsx
create mode 100644 src/routes/onboarding/link.tsx
create mode 100644 src/routes/onboarding/unauthorized.tsx
delete mode 100644 src/server/actions/azure.ts
delete mode 100644 src/server/actions/grants.ts
delete mode 100644 src/server/actions/groups.ts
delete mode 100644 src/server/actions/guides.ts
delete mode 100644 src/server/actions/test.ts
delete mode 100644 src/server/actions/users.ts
create mode 100644 src/server/auth-proxy-core.ts
create mode 100644 src/server/auth-proxy.server.ts
create mode 100644 src/server/auth.middleware.ts
create mode 100644 src/server/auth.server.ts
delete mode 100644 src/server/auth/index.ts
create mode 100644 src/server/authorization.ts
create mode 100644 src/server/backend.server.ts
create mode 100644 src/server/request-headers.ts
create mode 100644 src/server/runtime-env.ts
delete mode 100644 src/server/trpc/index.tsx
create mode 100644 src/styles.css
delete mode 100644 src/utils/cookies.ts
create mode 100644 src/vite-env.d.ts
create mode 100644 tailwind.config.ts
create mode 100644 tests/server-security.test.mjs
create mode 100644 tsr.config.json
create mode 100644 vite.config.ts
diff --git a/.dockerignore b/.dockerignore
index c550055..d6d1d07 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -3,5 +3,6 @@ Dockerfile
node_modules
npm-debug.log
README.md
-.next
+.output
+.tanstack
.git
diff --git a/.env.example b/.env.example
index 877a2fc..06d7976 100644
--- a/.env.example
+++ b/.env.example
@@ -1,3 +1,3 @@
-# this file might be outdated, please check /src/env.js for required env
BACKEND_URL="http://localhost:3000"
-ADMIN_ORG_EMAIL="## Email address of admin Azure account ##"),
+# Local preview bypass. This is ignored unless NODE_ENV is development.
+AGENT_MODE="false"
diff --git a/.gitignore b/.gitignore
index b2de018..4ac50e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,12 +13,11 @@
/prisma/db.sqlite-journal
db.sqlite
-# next.js
-/.next/
-/out/
-
# production
/build
+/dist
+/.output
+/.tanstack
# misc
.DS_Store
@@ -31,7 +30,6 @@ yarn-error.log*
.pnpm-debug.log*
# local env files
-# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
.env
.env*.local
@@ -44,4 +42,4 @@ yarn-error.log*
# idea files
.idea
-certificates
\ No newline at end of file
+certificates
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..17a8a7d
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,33 @@
+# Agent instructions
+
+## Previewing the authenticated dashboard
+
+When checking UI changes through a preview or browser, start the app with agent mode enabled:
+
+```sh
+PORT=xxxxx AGENT_MODE=true pnpm dev
+```
+
+Setting the `PORT=xxxxx` to an high enough port that is unlikely to hit
+another service or conflict with another workflow in preview (min 10000).
+
+Then open `/dashboard` directly (if not working on a page outside dashboard).
+`AGENT_MODE` bypasses session and role authorization and disables the auth-based
+redirects between `/login` and `/dashboard`, so no real account or login flow is needed.
+You can indipendently check /dashboard and /login for modifying those pages
+
+Use this flag only for local agent-driven development and previews.
+Never enable it in a deployed environment.
+
+When modifing auth-related code or redirects between authed and non-authed contexts,
+always verify that normal behavior still works with `AGENT_MODE=false`.
+
+> [!IMPORTANT]
+> Do not run destructive actions across multiple rows, unless specific prompt indication or
+> ask for user confirmation ALWAYS.
+
+## Git
+
+Always do commits with conventional commits.
+Set description when a commit includes lot of changes that cannot
+be divided into multiple commits, to specify the most important changes of the commit.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 120000
index 0000000..47dc3e3
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+AGENTS.md
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 7d3afaa..90268fa 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,52 +1,21 @@
-FROM node:22-alpine AS base
-
-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
+FROM node:22-alpine AS deps
WORKDIR /app
+COPY package.json pnpm-lock.yaml ./
+RUN corepack enable && pnpm install --frozen-lockfile
-# Install dependencies based on the preferred package manager
-COPY package.json pnpm-lock.yaml* .npmrc* ./
-RUN corepack enable pnpm && pnpm i --frozen-lockfile
-
-# Rebuild the source code only when needed
-FROM base AS builder
+FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
+RUN corepack enable && pnpm build
-# Next.js collects completely anonymous telemetry data about general usage.
-# Learn more here: https://nextjs.org/telemetry
-# Uncomment the following line in case you want to disable telemetry during the build.
-# ENV NEXT_TELEMETRY_DISABLED=1
-ENV SKIP_ENV_VALIDATION=true
-
-RUN corepack enable pnpm && pnpm run build
-
-# Production image, copy all the files and run next
-FROM base AS runner
+FROM node:22-alpine AS runner
WORKDIR /app
-
ENV NODE_ENV=production
-# Uncomment the following line in case you want to disable telemetry during runtime.
-# 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 3001
-ENV PORT=3001
-
-# 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"]
+ENV PORT=3000
+ENV HOST=0.0.0.0
+COPY --from=builder /app/.output ./.output
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/package.json ./package.json
+EXPOSE 3000
+CMD ["node", ".output/server/index.mjs"]
diff --git a/README.md b/README.md
index 2d0a5d7..39fc7c7 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,29 @@
-# Admin
+# PoliNetwork Admin
-[WIP] Dashboard for admin and members of PoliNetwork APS
+The PoliNetwork operations console, rebuilt with TanStack Start, React 19, Vite, Nitro, Tailwind CSS v4, and shadcn/ui.
+
+## Development
+
+```bash
+pnpm install
+pnpm dev
+```
+
+Open `http://localhost:3001`. The production server is generated with `pnpm build` and starts with `pnpm start`.
+
+The UI uses shadcn's base components with a custom semantic theme in `src/styles.css`. The theme preserves the console's paper canvas, dark navy shell, cobalt `#1156ae` primary, DM Sans body copy, Libre Baskerville headings, and DM Mono metadata while keeping page layout in Tailwind utilities.
+
+## Environment
+
+Set `BACKEND_URL` to the PoliNetwork backend origin. TanStack Start proxies Better Auth at `/api/auth/*`, and each server-function request creates its own tRPC client with that request's session cookie. Private dashboard functions also verify the linked Telegram identity and an administrator role before contacting the backend.
+
+`AGENT_MODE=true` provides a fake administrator only while the app runs in development. Production ignores the flag.
+
+## Checks
+
+```bash
+pnpm typecheck
+pnpm test
+pnpm check
+pnpm build
+```
diff --git a/biome.jsonc b/biome.jsonc
index 9e1c810..18a9891 100644
--- a/biome.jsonc
+++ b/biome.jsonc
@@ -7,15 +7,7 @@
},
"files": {
"ignoreUnknown": true,
- "includes": [
- "**",
- "!node_modules",
- "!.next",
- "!dist",
- "!build",
- "!src/app/globals.css", // modified by shadcn
- "!src/components/ui/*" // modified by shadcn
- ]
+ "includes": ["**", "!node_modules", "!dist", "!build", "!.output", "!.tanstack", "!src/routeTree.gen.ts"]
},
"formatter": {
"enabled": true,
@@ -36,29 +28,21 @@
"recommended": true,
"suspicious": {
"noVar": "error"
- // "noUnknownAtRules": "off" // until fix for tailwind rules is released
+ // Project-specific suspicious rules live here.
},
"nursery": {
"noFloatingPromises": "error",
"noMisusedPromises": "error",
"noUnnecessaryConditions": "error",
"noImportCycles": "warn",
- "useSortedClasses": {
- "options": {
- "attributes": ["classList"],
- "functions": ["cn"]
- }
- }
+ "useSortedClasses": "off"
},
"correctness": {
"useJsonImportAttributes": "warn",
"noPrivateImports": "error"
}
},
- "domains": {
- "project": "recommended",
- "next": "recommended"
- }
+ "domains": { "project": "recommended" }
},
"javascript": {
"formatter": {
diff --git a/components.json b/components.json
index 4a9d7a4..e02f8e2 100644
--- a/components.json
+++ b/components.json
@@ -1,25 +1,28 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "base-nova",
- "rsc": true,
+ "rsc": false,
"tsx": true,
"tailwind": {
- "config": "",
- "css": "src/index.css",
+ "config": "tailwind.config.ts",
+ "css": "src/styles.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
+ "menuColor": "default",
+ "menuAccent": "subtle",
"aliases": {
"components": "@/components",
- "utils": "@/lib/utils/shadcn",
+ "utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
- "menuColor": "default",
- "menuAccent": "subtle",
- "registries": {}
+ "registries": {
+ "@iconiq": "https://iconiqui.com/r/{name}.json",
+ "@reui": "https://reui.io/r/{style}/{name}.json"
+ }
}
diff --git a/next-env.d.ts b/next-env.d.ts
deleted file mode 100644
index 830fb59..0000000
--- a/next-env.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-///
-///
-///
-
-// NOTE: This file should not be edited
-// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/next.config.ts b/next.config.ts
deleted file mode 100644
index 3fa77a8..0000000
--- a/next.config.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import type { NextConfig } from "next"
-/**
- * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
- * for Docker builds.
- */
-import "./src/env.ts"
-
-const config: NextConfig = {
- typescript: {
- ignoreBuildErrors: true,
- },
- output: "standalone",
- transpilePackages: ["@t3-oss/env-core", "@t3-oss/env-nextjs"],
- images: {
- remotePatterns: [
- {
- protocol: "https",
- hostname: "**.blob.core.windows.net",
- },
- ],
- },
- experimental: {
- reactCompiler: true,
- serverActions: {
- bodySizeLimit: "2mb",
- },
- },
-}
-
-export default config
diff --git a/package.json b/package.json
index 78b8ec7..e56a047 100644
--- a/package.json
+++ b/package.json
@@ -4,65 +4,54 @@
"private": true,
"type": "module",
"scripts": {
- "build": "next build",
- "dev": "PORT=3001 next dev --turbo",
+ "dev": "vite dev",
+ "build": "vite build",
+ "start": "NODE_ENV=production node .output/server/index.mjs",
+ "preview": "vite preview",
+ "test": "node --experimental-strip-types --test tests/*.test.mjs",
+ "typecheck": "tsc --noEmit",
"check": "biome check",
- "check:fix": "biome check --fix --unsafe",
- "preview": "next build && next start",
- "start": "next start",
- "typecheck": "tsc --noEmit"
+ "check:fix": "biome check --write --unsafe"
},
"dependencies": {
- "@base-ui/react": "^1.3.0",
+ "@base-ui/react": "^1.6.0",
"@better-auth/passkey": "^1.5.5",
- "@hookform/resolvers": "^3.9.1",
"@polinetwork/backend": "^0.17.0",
- "@radix-ui/react-dialog": "^1.1.15",
- "@t3-oss/env-nextjs": "^0.13.10",
- "@tanstack/react-table": "^8.21.2",
+ "@t3-oss/env-core": "^0.13.10",
+ "@tanstack/react-router": "1.170.17",
+ "@tanstack/react-start": "1.168.27",
+ "@tanstack/react-store": "^0.11.0",
+ "@tanstack/react-table": "9.0.0-beta.38",
"@trpc/client": "11.5.1",
- "@trpc/next": "11.5.1",
- "babel-plugin-react-compiler": "1.0.0",
"better-auth": "^1.5.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
- "date-fns": "^4.1.0",
- "geist": "^1.3.0",
- "input-otp": "^1.4.2",
- "next": "^15.5.18",
- "next-themes": "^0.4.4",
- "postgres": "^3.4.4",
- "radix-ui": "^1.4.3",
- "react": "^18.3.1",
- "react-day-picker": "^9.14.0",
- "react-dom": "^18.3.1",
- "react-error-boundary": "^6.1.0",
- "react-hook-form": "^7.55.0",
- "react-icons": "^5.7.0",
- "server-only": "^0.0.1",
- "shadcn": "^4.2.0",
- "sonner": "^2.0.3",
+ "date-fns": "^4.4.0",
+ "lucide-react": "^0.545.0",
+ "motion": "^12.42.2",
+ "nitro": "3.0.260610-beta",
+ "react": "^19.2.0",
+ "react-day-picker": "^10.0.1",
+ "react-dom": "^19.2.0",
+ "shadcn": "^4.13.0",
+ "sonner": "^2.0.7",
"superjson": "^2.2.1",
- "tailwind-merge": "^3.0.1",
- "tailwind-scrollbar": "^4.0.2",
- "tailwindcss-animate": "^1.0.7",
+ "tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0",
"zod": "^4.3.5"
},
"devDependencies": {
"@biomejs/biome": "2.3.10",
- "@tailwindcss/postcss": "^4.1.4",
+ "@tailwindcss/vite": "^4.3.2",
"@trpc/server": "11.5.1",
- "@types/node": "^20.14.10",
- "@types/react": "^18.3.3",
- "@types/react-dom": "^18.3.0",
- "postcss": "^8.4.39",
- "tailwindcss": "^4.1.4",
- "typescript": "^5.5.3"
+ "@types/node": "^22.10.2",
+ "@types/react": "^19.2.0",
+ "@types/react-dom": "^19.2.0",
+ "@vitejs/plugin-react": "^6.0.1",
+ "tailwindcss": "^4.3.2",
+ "typescript": "^6.0.2",
+ "vite": "^8.0.0"
},
- "ct3aMetadata": {
- "initVersion": "7.38.1"
- },
- "packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677"
+ "packageManager": "pnpm@10.8.1"
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 11dc93d..5a6d4e5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,38 +9,35 @@ importers:
.:
dependencies:
'@base-ui/react':
- specifier: ^1.3.0
- version: 1.3.0(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ specifier: ^1.6.0
+ version: 1.6.0(@date-fns/tz@1.5.0)(@types/react@19.2.17)(date-fns@4.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
'@better-auth/passkey':
specifier: ^1.5.5
- version: 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(mongodb@7.1.0)(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(better-call@1.3.2(zod@4.3.5))(nanostores@1.2.0)
- '@hookform/resolvers':
- specifier: ^3.9.1
- version: 3.10.0(react-hook-form@7.55.0(react@18.3.1))
+ version: 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(@tanstack/react-start@1.168.27(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0)))(mongodb@7.1.0)(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(solid-js@1.9.14))(better-call@1.3.2(zod@4.3.5))(nanostores@1.2.0)
'@polinetwork/backend':
specifier: ^0.17.0
version: 0.17.0
- '@radix-ui/react-dialog':
- specifier: ^1.1.15
- version: 1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@t3-oss/env-nextjs':
+ '@t3-oss/env-core':
specifier: ^0.13.10
- version: 0.13.10(typescript@5.7.3)(zod@4.3.5)
+ version: 0.13.11(typescript@6.0.3)(zod@4.3.5)
+ '@tanstack/react-router':
+ specifier: 1.170.17
+ version: 1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@tanstack/react-start':
+ specifier: 1.168.27
+ version: 1.168.27(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))
+ '@tanstack/react-store':
+ specifier: ^0.11.0
+ version: 0.11.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
'@tanstack/react-table':
- specifier: ^8.21.2
- version: 8.21.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ specifier: 9.0.0-beta.38
+ version: 9.0.0-beta.38(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
'@trpc/client':
specifier: 11.5.1
- version: 11.5.1(@trpc/server@11.5.1(typescript@5.7.3))(typescript@5.7.3)
- '@trpc/next':
- specifier: 11.5.1
- version: 11.5.1(@tanstack/react-query@5.90.19(react@18.3.1))(@trpc/client@11.5.1(@trpc/server@11.5.1(typescript@5.7.3))(typescript@5.7.3))(@trpc/react-query@11.5.1(@tanstack/react-query@5.90.19(react@18.3.1))(@trpc/client@11.5.1(@trpc/server@11.5.1(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.5.1(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3))(@trpc/server@11.5.1(typescript@5.7.3))(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)
- babel-plugin-react-compiler:
- specifier: 1.0.0
- version: 1.0.0
+ version: 11.5.1(@trpc/server@11.5.1(typescript@6.0.3))(typescript@6.0.3)
better-auth:
specifier: ^1.5.5
- version: 1.5.5(mongodb@7.1.0)(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 1.5.5(@tanstack/react-start@1.168.27(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0)))(mongodb@7.1.0)(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(solid-js@1.9.14)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -49,67 +46,40 @@ importers:
version: 2.1.1
cmdk:
specifier: ^1.1.1
- version: 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
date-fns:
- specifier: ^4.1.0
- version: 4.1.0
- geist:
- specifier: ^1.3.0
- version: 1.3.1(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
- input-otp:
- specifier: ^1.4.2
- version: 1.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- next:
- specifier: ^15.5.18
- version: 15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- next-themes:
- specifier: ^0.4.4
- version: 0.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- postgres:
- specifier: ^3.4.4
- version: 3.4.5
- radix-ui:
- specifier: ^1.4.3
- version: 1.4.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ specifier: ^4.4.0
+ version: 4.4.0
+ lucide-react:
+ specifier: ^0.545.0
+ version: 0.545.0(react@19.2.7)
+ motion:
+ specifier: ^12.42.2
+ version: 12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ nitro:
+ specifier: 3.0.260610-beta
+ version: 3.0.260610-beta(chokidar@5.0.0)(dotenv@17.4.2)(jiti@2.7.0)(mongodb@7.1.0)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))
react:
- specifier: ^18.3.1
- version: 18.3.1
+ specifier: ^19.2.0
+ version: 19.2.7
react-day-picker:
- specifier: ^9.14.0
- version: 9.14.0(react@18.3.1)
+ specifier: ^10.0.1
+ version: 10.0.1(@types/react@19.2.17)(react@19.2.7)
react-dom:
- specifier: ^18.3.1
- version: 18.3.1(react@18.3.1)
- react-error-boundary:
- specifier: ^6.1.0
- version: 6.1.0(react@18.3.1)
- react-hook-form:
- specifier: ^7.55.0
- version: 7.55.0(react@18.3.1)
- react-icons:
- specifier: ^5.7.0
- version: 5.7.0(react@18.3.1)
- server-only:
- specifier: ^0.0.1
- version: 0.0.1
+ specifier: ^19.2.0
+ version: 19.2.7(react@19.2.7)
shadcn:
- specifier: ^4.2.0
- version: 4.2.0(@types/node@20.17.19)(typescript@5.7.3)
+ specifier: ^4.13.0
+ version: 4.13.0(typescript@6.0.3)
sonner:
- specifier: ^2.0.3
- version: 2.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ specifier: ^2.0.7
+ version: 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
superjson:
specifier: ^2.2.1
version: 2.2.2
tailwind-merge:
- specifier: ^3.0.1
- version: 3.0.1
- tailwind-scrollbar:
- specifier: ^4.0.2
- version: 4.0.2(react@18.3.1)(tailwindcss@4.1.4)
- tailwindcss-animate:
- specifier: ^1.0.7
- version: 1.0.7(tailwindcss@4.1.4)
+ specifier: ^3.6.0
+ version: 3.6.0
tw-animate-css:
specifier: ^1.4.0
version: 1.4.0
@@ -120,41 +90,48 @@ importers:
'@biomejs/biome':
specifier: 2.3.10
version: 2.3.10
- '@tailwindcss/postcss':
- specifier: ^4.1.4
- version: 4.1.4
+ '@tailwindcss/vite':
+ specifier: ^4.3.2
+ version: 4.3.2(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))
'@trpc/server':
specifier: 11.5.1
- version: 11.5.1(typescript@5.7.3)
+ version: 11.5.1(typescript@6.0.3)
'@types/node':
- specifier: ^20.14.10
- version: 20.17.19
+ specifier: ^22.10.2
+ version: 22.20.1
'@types/react':
- specifier: ^18.3.3
- version: 18.3.18
+ specifier: ^19.2.0
+ version: 19.2.17
'@types/react-dom':
- specifier: ^18.3.0
- version: 18.3.5(@types/react@18.3.18)
- postcss:
- specifier: ^8.4.39
- version: 8.5.2
+ specifier: ^19.2.0
+ version: 19.2.3(@types/react@19.2.17)
+ '@vitejs/plugin-react':
+ specifier: ^6.0.1
+ version: 6.0.3(babel-plugin-react-compiler@1.0.0)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))
tailwindcss:
- specifier: ^4.1.4
- version: 4.1.4
+ specifier: ^4.3.2
+ version: 4.3.2
typescript:
- specifier: ^5.5.3
- version: 5.7.3
+ specifier: ^6.0.2
+ version: 6.0.3
+ vite:
+ specifier: ^8.0.0
+ version: 8.1.4(@types/node@22.20.1)(jiti@2.7.0)
packages:
- '@alloc/quick-lru@5.2.0':
- resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
- engines: {node: '>=10'}
+ '@babel/code-frame@7.27.1':
+ resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+ engines: {node: '>=6.9.0'}
'@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
+ '@babel/code-frame@7.29.7':
+ resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/compat-data@7.29.0':
resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
engines: {node: '>=6.9.0'}
@@ -167,16 +144,20 @@ packages:
resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
engines: {node: '>=6.9.0'}
- '@babel/helper-annotate-as-pure@7.27.3':
- resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==}
+ '@babel/generator@7.29.7':
+ resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-annotate-as-pure@7.29.7':
+ resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==}
engines: {node: '>=6.9.0'}
'@babel/helper-compilation-targets@7.28.6':
resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
engines: {node: '>=6.9.0'}
- '@babel/helper-create-class-features-plugin@7.28.6':
- resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==}
+ '@babel/helper-create-class-features-plugin@7.29.7':
+ resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
@@ -185,54 +166,76 @@ packages:
resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
engines: {node: '>=6.9.0'}
- '@babel/helper-member-expression-to-functions@7.28.5':
- resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==}
+ '@babel/helper-globals@7.29.7':
+ resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-member-expression-to-functions@7.29.7':
+ resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==}
engines: {node: '>=6.9.0'}
'@babel/helper-module-imports@7.28.6':
resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-module-imports@7.29.7':
+ resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-module-transforms@7.28.6':
resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
- '@babel/helper-optimise-call-expression@7.27.1':
- resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==}
+ '@babel/helper-module-transforms@7.29.7':
+ resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-optimise-call-expression@7.29.7':
+ resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==}
engines: {node: '>=6.9.0'}
- '@babel/helper-plugin-utils@7.28.6':
- resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
+ '@babel/helper-plugin-utils@7.29.7':
+ resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==}
engines: {node: '>=6.9.0'}
- '@babel/helper-replace-supers@7.28.6':
- resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==}
+ '@babel/helper-replace-supers@7.29.7':
+ resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
- '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
- resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==}
+ '@babel/helper-skip-transparent-expression-wrappers@7.29.7':
+ resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
- '@babel/helper-validator-identifier@7.27.1':
- resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
+ '@babel/helper-string-parser@7.29.7':
+ resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.28.5':
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.29.7':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.27.1':
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-option@7.29.7':
+ resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helpers@7.29.2':
resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==}
engines: {node: '>=6.9.0'}
@@ -242,69 +245,88 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
- '@babel/plugin-syntax-jsx@7.28.6':
- resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==}
+ '@babel/parser@7.29.7':
+ resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-syntax-jsx@7.29.7':
+ resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-syntax-typescript@7.28.6':
- resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==}
+ '@babel/plugin-syntax-typescript@7.29.7':
+ resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-modules-commonjs@7.28.6':
- resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==}
+ '@babel/plugin-transform-modules-commonjs@7.29.7':
+ resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-typescript@7.28.6':
- resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==}
+ '@babel/plugin-transform-typescript@7.29.7':
+ resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/preset-typescript@7.28.5':
- resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==}
+ '@babel/preset-typescript@7.29.7':
+ resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/runtime@7.29.2':
- resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
+ '@babel/runtime@7.29.7':
+ resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
engines: {node: '>=6.9.0'}
'@babel/template@7.28.6':
resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
engines: {node: '>=6.9.0'}
+ '@babel/template@7.29.7':
+ resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/traverse@7.29.0':
resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
engines: {node: '>=6.9.0'}
- '@babel/types@7.28.2':
- resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
+ '@babel/traverse@7.29.7':
+ resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==}
engines: {node: '>=6.9.0'}
'@babel/types@7.29.0':
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
- '@base-ui/react@1.3.0':
- resolution: {integrity: sha512-FwpKqZbPz14AITp1CVgf4AjhKPe1OeeVKSBMdgD10zbFlj3QSWelmtCMLi2+/PFZZcIm3l87G7rwtCZJwHyXWA==}
+ '@babel/types@7.29.7':
+ resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
+ engines: {node: '>=6.9.0'}
+
+ '@base-ui/react@1.6.0':
+ resolution: {integrity: sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw==}
engines: {node: '>=14.0.0'}
peerDependencies:
+ '@date-fns/tz': ^1.2.0
'@types/react': ^17 || ^18 || ^19
+ date-fns: ^4.0.0
react: ^17 || ^18 || ^19
react-dom: ^17 || ^18 || ^19
peerDependenciesMeta:
+ '@date-fns/tz':
+ optional: true
'@types/react':
optional: true
+ date-fns:
+ optional: true
- '@base-ui/utils@0.2.6':
- resolution: {integrity: sha512-yQ+qeuqohwhsNpoYDqqXaLllYAkPCP4vYdDrVo8FQXaAPfHWm1pG/Vm+jmGTA5JFS0BAIjookyapuJFY8F9PIw==}
+ '@base-ui/utils@0.3.1':
+ resolution: {integrity: sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg==}
peerDependencies:
'@types/react': ^17 || ^18 || ^19
react: ^17 || ^18 || ^19
@@ -444,40 +466,31 @@ packages:
cpu: [x64]
os: [win32]
- '@date-fns/tz@1.4.1':
- resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==}
+ '@date-fns/tz@1.5.0':
+ resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==}
- '@dotenvx/dotenvx@1.61.0':
- resolution: {integrity: sha512-utL3cpZoFzflyqUkjYbxYujI6STBTmO5LFn4bbin/NZnRWN6wQ7eErhr3/Vpa5h/jicPFC6kTa42r940mQftJQ==}
+ '@dotenvx/dotenvx@1.75.1':
+ resolution: {integrity: sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==}
hasBin: true
- '@ecies/ciphers@0.2.6':
- resolution: {integrity: sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==}
- engines: {bun: '>=1', deno: '>=2.7.10', node: '>=16'}
- peerDependencies:
- '@noble/ciphers': ^1.0.0
+ '@dotenvx/primitives@0.8.0':
+ resolution: {integrity: sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==}
+
+ '@emnapi/core@1.11.1':
+ resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==}
- '@emnapi/runtime@1.10.0':
- resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
+ '@emnapi/runtime@1.11.1':
+ resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==}
- '@floating-ui/core@1.6.9':
- resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==}
+ '@emnapi/wasi-threads@1.2.2':
+ resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==}
'@floating-ui/core@1.7.5':
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
- '@floating-ui/dom@1.6.13':
- resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
-
'@floating-ui/dom@1.7.6':
resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
- '@floating-ui/react-dom@2.1.2':
- resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==}
- peerDependencies:
- react: '>=16.8.0'
- react-dom: '>=16.8.0'
-
'@floating-ui/react-dom@2.1.8':
resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==}
peerDependencies:
@@ -487,23 +500,15 @@ packages:
'@floating-ui/utils@0.2.11':
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
- '@floating-ui/utils@0.2.9':
- resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
-
'@hexagon/base64@1.1.28':
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
- '@hono/node-server@1.19.13':
- resolution: {integrity: sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==}
+ '@hono/node-server@1.19.14':
+ resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==}
engines: {node: '>=18.14.1'}
peerDependencies:
hono: ^4
- '@hookform/resolvers@3.10.0':
- resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
- peerDependencies:
- react-hook-form: ^7.0.0
-
'@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
@@ -641,41 +646,6 @@ packages:
cpu: [x64]
os: [win32]
- '@inquirer/ansi@1.0.2':
- resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==}
- engines: {node: '>=18'}
-
- '@inquirer/confirm@5.1.21':
- resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==}
- engines: {node: '>=18'}
- peerDependencies:
- '@types/node': '>=18'
- peerDependenciesMeta:
- '@types/node':
- optional: true
-
- '@inquirer/core@10.3.2':
- resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==}
- engines: {node: '>=18'}
- peerDependencies:
- '@types/node': '>=18'
- peerDependenciesMeta:
- '@types/node':
- optional: true
-
- '@inquirer/figures@1.0.15':
- resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==}
- engines: {node: '>=18'}
-
- '@inquirer/type@3.0.10':
- resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==}
- engines: {node: '>=18'}
- peerDependencies:
- '@types/node': '>=18'
- peerDependenciesMeta:
- '@types/node':
- optional: true
-
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@@ -708,9 +678,11 @@ packages:
'@mongodb-js/saslprep@1.4.11':
resolution: {integrity: sha512-o9rAHc0IpIjuPSxRutWpE1F62x7n+4mVS4rCNHkzhIUMQcc18bb6xEq5wd2NdN0WjepIyXIppRshYI2kQDOZVA==}
- '@mswjs/interceptors@0.41.3':
- resolution: {integrity: sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==}
- engines: {node: '>=18'}
+ '@napi-rs/wasm-runtime@1.1.6':
+ resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==}
+ peerDependencies:
+ '@emnapi/core': ^1.7.1
+ '@emnapi/runtime': ^1.7.1
'@next/env@15.5.18':
resolution: {integrity: sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g==}
@@ -763,22 +735,10 @@ packages:
cpu: [x64]
os: [win32]
- '@noble/ciphers@1.3.0':
- resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==}
- engines: {node: ^14.21.3 || >=16}
-
'@noble/ciphers@2.1.1':
resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==}
engines: {node: '>= 20.19.0'}
- '@noble/curves@1.9.7':
- resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==}
- engines: {node: ^14.21.3 || >=16}
-
- '@noble/hashes@1.8.0':
- resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
- engines: {node: ^14.21.3 || >=16}
-
'@noble/hashes@2.0.1':
resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==}
engines: {node: '>= 20.19.0'}
@@ -795,14 +755,24 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
- '@open-draft/deferred-promise@2.2.0':
- resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
+ '@oozcitak/dom@2.0.2':
+ resolution: {integrity: sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==}
+ engines: {node: '>=20.0'}
+
+ '@oozcitak/infra@2.0.2':
+ resolution: {integrity: sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA==}
+ engines: {node: '>=20.0'}
- '@open-draft/logger@0.3.0':
- resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==}
+ '@oozcitak/url@3.0.0':
+ resolution: {integrity: sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ==}
+ engines: {node: '>=20.0'}
- '@open-draft/until@2.1.0':
- resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
+ '@oozcitak/util@10.0.0':
+ resolution: {integrity: sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==}
+ engines: {node: '>=20.0'}
+
+ '@oxc-project/types@0.139.0':
+ resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==}
'@peculiar/asn1-android@2.6.0':
resolution: {integrity: sha512-cBRCKtYPF7vJGN76/yG8VbxRcHLPF3HnkoHhKOZeHpoVtbMYfY9ROKtH3DtYUY9m8uI1Mh47PRhHf2hSK3xcSQ==}
@@ -844,40 +814,29 @@ packages:
'@polinetwork/backend@0.17.0':
resolution: {integrity: sha512-NfU0i4It86LOMsZ5xyOX8ccOcbazwsC8j/D/B4sBb9PaJjENAdyFS27l9I4i0ihM2MrcFBJixxDdmnPO7/D/Rg==}
- '@radix-ui/number@1.1.1':
- resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
-
- '@radix-ui/primitive@1.1.3':
- resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
+ '@radix-ui/primitive@1.1.6':
+ resolution: {integrity: sha512-w9hl+724uYEgCGR3bhuRepjBtrNB/6gkhCnAf58Ke+SLbHPPQqVZZB59z60roB+5H+nh3nWTcdJhQdFMEydWmw==}
- '@radix-ui/react-accessible-icon@1.1.7':
- resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==}
+ '@radix-ui/react-compose-refs@1.1.3':
+ resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==}
peerDependencies:
'@types/react': '*'
- '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
- '@types/react-dom':
- optional: true
- '@radix-ui/react-accordion@1.2.12':
- resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==}
+ '@radix-ui/react-context@1.2.0':
+ resolution: {integrity: sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==}
peerDependencies:
'@types/react': '*'
- '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
- '@types/react-dom':
- optional: true
- '@radix-ui/react-alert-dialog@1.1.15':
- resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==}
+ '@radix-ui/react-dialog@1.1.20':
+ resolution: {integrity: sha512-cngVJcvK0yMvR7wICJpv+1uW3Qw4T7QM5sdbb+oE/lxOdTdvF00oaRpWUjVgmjyXe3J+xh7eZyXZlVF3g2g59g==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -889,8 +848,8 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-arrow@1.1.7':
- resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
+ '@radix-ui/react-dismissable-layer@1.1.16':
+ resolution: {integrity: sha512-t45h68IjFx0ccBnPJqk0X6ecv69LkCFWd6DNCFQX56mUnVEXZbNOLCH/u9fHlAjFZ1RrFdl8/m4zev7B7NyhXQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -902,21 +861,17 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-aspect-ratio@1.1.7':
- resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==}
+ '@radix-ui/react-focus-guards@1.1.4':
+ resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==}
peerDependencies:
'@types/react': '*'
- '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
- '@types/react-dom':
- optional: true
- '@radix-ui/react-avatar@1.1.10':
- resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==}
+ '@radix-ui/react-focus-scope@1.1.13':
+ resolution: {integrity: sha512-dE04aPEuP9rvKKT0d0KjSOtTEYNg6bmCYFsoSJpfC+y91Hic28ZfDCGgv6aJ+2Kw/LBXYipMZpyqVj/OD3Z8Gg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -928,21 +883,17 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-checkbox@1.3.3':
- resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
+ '@radix-ui/react-id@1.1.2':
+ resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==}
peerDependencies:
'@types/react': '*'
- '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
- '@types/react-dom':
- optional: true
- '@radix-ui/react-collapsible@1.1.12':
- resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==}
+ '@radix-ui/react-portal@1.1.14':
+ resolution: {integrity: sha512-REwjAGPMa3J9oyDE4cuWkZbwnCbbyky66NurquQklXMSDn67cl6oGFx2gO7KZhPtFNbNw9xTWNrti3VIhgluYw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -954,8 +905,8 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-collection@1.1.7':
- resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
+ '@radix-ui/react-presence@1.1.8':
+ resolution: {integrity: sha512-0hhyrQdXMaATgq4ammLG9+iPqsXxzZkgTSIxdrJHdfLnXO4Uo5L7BoO3/Xf0AEaettadGZWGGJMw6ujzQvIpGA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -967,26 +918,8 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-compose-refs@1.1.1':
- resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-compose-refs@1.1.2':
- resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@radix-ui/react-context-menu@2.2.16':
- resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==}
+ '@radix-ui/react-primitive@2.1.7':
+ resolution: {integrity: sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -998,8 +931,8 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-context@1.1.2':
- resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
+ '@radix-ui/react-slot@1.3.0':
+ resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
@@ -1007,21 +940,17 @@ packages:
'@types/react':
optional: true
- '@radix-ui/react-dialog@1.1.15':
- resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
+ '@radix-ui/react-use-callback-ref@1.1.2':
+ resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==}
peerDependencies:
'@types/react': '*'
- '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
- '@types/react-dom':
- optional: true
- '@radix-ui/react-direction@1.1.1':
- resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
+ '@radix-ui/react-use-controllable-state@1.2.4':
+ resolution: {integrity: sha512-cx2DixxmSfjCcEoRvDvy1NLd6SWK94XFcEEOZUcharUlXbmahFQGKCfwdKZL2ub34iIwOPOEFVF80xb+yfLYiA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
@@ -1029,756 +958,399 @@ packages:
'@types/react':
optional: true
- '@radix-ui/react-dismissable-layer@1.1.11':
- resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
+ '@radix-ui/react-use-effect-event@0.0.3':
+ resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==}
peerDependencies:
'@types/react': '*'
- '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
- '@types/react-dom':
- optional: true
- '@radix-ui/react-dropdown-menu@2.1.16':
- resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==}
+ '@radix-ui/react-use-layout-effect@1.1.2':
+ resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==}
peerDependencies:
'@types/react': '*'
- '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
- '@types/react-dom':
- optional: true
- '@radix-ui/react-focus-guards@1.1.3':
- resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
+ '@rolldown/binding-android-arm64@1.1.5':
+ resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
- '@radix-ui/react-focus-scope@1.1.7':
- resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@rolldown/binding-darwin-arm64@1.1.5':
+ resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
- '@radix-ui/react-form@0.1.8':
- resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@rolldown/binding-darwin-x64@1.1.5':
+ resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
- '@radix-ui/react-hover-card@1.1.15':
- resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@rolldown/binding-freebsd-x64@1.1.5':
+ resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
- '@radix-ui/react-id@1.1.0':
- resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
+ '@rolldown/binding-linux-arm-gnueabihf@1.1.5':
+ resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
- '@radix-ui/react-id@1.1.1':
- resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
+ '@rolldown/binding-linux-arm64-gnu@1.1.5':
+ resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
- '@radix-ui/react-label@2.1.7':
- resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@rolldown/binding-linux-arm64-musl@1.1.5':
+ resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rolldown/binding-linux-ppc64-gnu@1.1.5':
+ resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rolldown/binding-linux-s390x-gnu@1.1.5':
+ resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
- '@radix-ui/react-menu@2.1.16':
- resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==}
+ '@rolldown/binding-linux-x64-gnu@1.1.5':
+ resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@rolldown/binding-linux-x64-musl@1.1.5':
+ resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@rolldown/binding-openharmony-arm64@1.1.5':
+ resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rolldown/binding-wasm32-wasi@1.1.5':
+ resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [wasm32]
+
+ '@rolldown/binding-win32-arm64-msvc@1.1.5':
+ resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rolldown/binding-win32-x64-msvc@1.1.5':
+ resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@rolldown/pluginutils@1.0.1':
+ resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
+
+ '@sec-ant/readable-stream@0.4.1':
+ resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
+
+ '@simplewebauthn/browser@13.2.2':
+ resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==}
+
+ '@simplewebauthn/server@13.3.0':
+ resolution: {integrity: sha512-MLHYFrYG8/wK2i+86XMhiecK72nMaHKKt4bo+7Q1TbuG9iGjlSdfkPWKO5ZFE/BX+ygCJ7pr8H/AJeyAj1EaTQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@sindresorhus/merge-streams@4.0.0':
+ resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
+ engines: {node: '>=18'}
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@t3-oss/env-core@0.13.11':
+ resolution: {integrity: sha512-sM7GYY+KL7H/Hl0BE0inWfk3nRHZOLhmVn7sHGxaZt9FAR6KqREXAE+6TqKfiavfXmpRxO/OZ2QgKRd+oiBYRQ==}
peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ arktype: ^2.1.0
+ typescript: '>=5.0.0'
+ valibot: ^1.0.0-beta.7 || ^1.0.0
+ zod: ^3.24.0 || ^4.0.0
peerDependenciesMeta:
- '@types/react':
+ arktype:
optional: true
- '@types/react-dom':
+ typescript:
optional: true
-
- '@radix-ui/react-menubar@1.1.16':
- resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
+ valibot:
optional: true
- '@types/react-dom':
+ zod:
optional: true
- '@radix-ui/react-navigation-menu@1.2.14':
- resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-one-time-password-field@0.1.8':
- resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-password-toggle-field@0.1.3':
- resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/node@4.3.2':
+ resolution: {integrity: sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==}
- '@radix-ui/react-popover@1.1.15':
- resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-popper@1.2.8':
- resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-portal@1.1.9':
- resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-presence@1.1.5':
- resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-primitive@2.0.2':
- resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-primitive@2.1.3':
- resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/react-progress@1.1.7':
- resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/oxide-android-arm64@4.3.2':
+ resolution: {integrity: sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [android]
- '@radix-ui/react-radio-group@1.3.8':
- resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/oxide-darwin-arm64@4.3.2':
+ resolution: {integrity: sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [darwin]
- '@radix-ui/react-roving-focus@1.1.11':
- resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/oxide-darwin-x64@4.3.2':
+ resolution: {integrity: sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [darwin]
- '@radix-ui/react-scroll-area@1.2.10':
- resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/oxide-freebsd-x64@4.3.2':
+ resolution: {integrity: sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [freebsd]
- '@radix-ui/react-select@2.2.6':
- resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.2':
+ resolution: {integrity: sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==}
+ engines: {node: '>= 20'}
+ cpu: [arm]
+ os: [linux]
- '@radix-ui/react-separator@1.1.7':
- resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/oxide-linux-arm64-gnu@4.3.2':
+ resolution: {integrity: sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [linux]
- '@radix-ui/react-slider@1.3.6':
- resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/oxide-linux-arm64-musl@4.3.2':
+ resolution: {integrity: sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [linux]
- '@radix-ui/react-slot@1.1.2':
- resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
+ '@tailwindcss/oxide-linux-x64-gnu@4.3.2':
+ resolution: {integrity: sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [linux]
- '@radix-ui/react-slot@1.2.3':
- resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
+ '@tailwindcss/oxide-linux-x64-musl@4.3.2':
+ resolution: {integrity: sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [linux]
- '@radix-ui/react-switch@1.2.6':
- resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/oxide-wasm32-wasi@4.3.2':
+ resolution: {integrity: sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
- '@radix-ui/react-tabs@1.1.13':
- resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/oxide-win32-arm64-msvc@4.3.2':
+ resolution: {integrity: sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [win32]
- '@radix-ui/react-toast@1.2.15':
- resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/oxide-win32-x64-msvc@4.3.2':
+ resolution: {integrity: sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [win32]
- '@radix-ui/react-toggle-group@1.1.11':
- resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tailwindcss/oxide@4.3.2':
+ resolution: {integrity: sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==}
+ engines: {node: '>= 20'}
- '@radix-ui/react-toggle@1.1.10':
- resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==}
+ '@tailwindcss/vite@4.3.2':
+ resolution: {integrity: sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==}
peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ vite: ^5.2.0 || ^6 || ^7 || ^8
- '@radix-ui/react-toolbar@1.1.11':
- resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ '@tanstack/history@1.162.0':
+ resolution: {integrity: sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==}
+ engines: {node: '>=20.19'}
- '@radix-ui/react-tooltip@1.2.8':
- resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
+ '@tanstack/react-router@1.170.17':
+ resolution: {integrity: sha512-ppLkjCfSMaeug9rmFRYzOd4TIqWV+yTE7tzIny7alJsSnM7w4lzEZm6eqCehG0SPetpZ0R3K+UnanSmBgOAVcQ==}
+ engines: {node: '>=20.19'}
peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
+ react: '>=18.0.0 || >=19.0.0'
+ react-dom: '>=18.0.0 || >=19.0.0'
- '@radix-ui/react-use-callback-ref@1.1.1':
- resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
+ '@tanstack/react-start-client@1.168.15':
+ resolution: {integrity: sha512-pW50PHvadgi50iNCw6deUOvqc9rzs30SstyFZY2tcS9z1XlqTlELSvGowjxdu2m0ymtqm1emj1jau1iP7+3+PQ==}
+ engines: {node: '>=22.12.0'}
peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
+ react: '>=18.0.0 || >=19.0.0'
+ react-dom: '>=18.0.0 || >=19.0.0'
- '@radix-ui/react-use-controllable-state@1.2.2':
- resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
+ '@tanstack/react-start-rsc@0.1.26':
+ resolution: {integrity: sha512-+FMm3qtT1gWsl0i5sG/Q70mh1k7tzZUsPBaqbg4v34zVJZ+XGn1mJb34x2w7z1M0/e7co6GkZfV8BRpczrs8UA==}
+ engines: {node: '>=22.12.0'}
peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ '@rspack/core': '>=2.0.0-0'
+ '@vitejs/plugin-rsc': '>=0.5.20'
+ react: '>=18.0.0 || >=19.0.0'
+ react-dom: '>=18.0.0 || >=19.0.0'
+ react-server-dom-rspack: '>=0.0.2'
peerDependenciesMeta:
- '@types/react':
+ '@rspack/core':
optional: true
-
- '@radix-ui/react-use-effect-event@0.0.2':
- resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
+ '@vitejs/plugin-rsc':
optional: true
-
- '@radix-ui/react-use-escape-keydown@1.1.1':
- resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
+ react-server-dom-rspack:
optional: true
- '@radix-ui/react-use-is-hydrated@0.1.0':
- resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==}
+ '@tanstack/react-start-server@1.167.21':
+ resolution: {integrity: sha512-puJ7eFxaLuDzeM/tiLDJaXCA4uK+PZnEOoIC73zipsFqx865MGzrRS/GSZxeVxjavC5iHU+ZwC+rgI0qYSol1A==}
+ engines: {node: '>=22.12.0'}
peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
+ react: '>=18.0.0 || >=19.0.0'
+ react-dom: '>=18.0.0 || >=19.0.0'
- '@radix-ui/react-use-layout-effect@1.1.0':
- resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
+ '@tanstack/react-start@1.168.27':
+ resolution: {integrity: sha512-rdGFDqfCW71gyofyAxaYxhelNKmeVjpmbpm0uFYbNHORCa///4aBxi7B7ecShibKv9O4GfJ66MPX5F0ozbm+ig==}
+ engines: {node: '>=22.12.0'}
peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ '@rsbuild/core': ^2.0.0
+ '@vitejs/plugin-rsc': '*'
+ react: '>=18.0.0 || >=19.0.0'
+ react-dom: '>=18.0.0 || >=19.0.0'
+ vite: '>=7.0.0'
peerDependenciesMeta:
- '@types/react':
+ '@rsbuild/core':
optional: true
-
- '@radix-ui/react-use-layout-effect@1.1.1':
- resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
+ '@vitejs/plugin-rsc':
optional: true
-
- '@radix-ui/react-use-previous@1.1.1':
- resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==}
- peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
+ vite:
optional: true
- '@radix-ui/react-use-rect@1.1.1':
- resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
+ '@tanstack/react-store@0.11.0':
+ resolution: {integrity: sha512-tX4YXh3PDkmpvGQWkWqKpzs/MSqbtuwY9dWdWhtV9Q50PmO+jOkUKIWIX4G85dwt7lxdHLXsiaEKPdKmC8F41w==}
peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- '@radix-ui/react-use-size@1.1.1':
- resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
+ '@tanstack/react-store@0.9.3':
+ resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==}
peerDependencies:
- '@types/react': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- '@radix-ui/react-visually-hidden@1.2.3':
- resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
+ '@tanstack/react-table@9.0.0-beta.38':
+ resolution: {integrity: sha512-FKBVI9SpHqj+Jf8bdxLUNajnfbl3iYt0bhUPAN93kHH2L/XxHF41JPQDKbpqQC40WejbfJEHj4RIUirf3OAmVA==}
+ engines: {node: '>=20'}
peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- '@radix-ui/rect@1.1.1':
- resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
-
- '@sec-ant/readable-stream@0.4.1':
- resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
+ react: '>=18'
- '@simplewebauthn/browser@13.2.2':
- resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==}
-
- '@simplewebauthn/server@13.3.0':
- resolution: {integrity: sha512-MLHYFrYG8/wK2i+86XMhiecK72nMaHKKt4bo+7Q1TbuG9iGjlSdfkPWKO5ZFE/BX+ygCJ7pr8H/AJeyAj1EaTQ==}
- engines: {node: '>=20.0.0'}
-
- '@sindresorhus/merge-streams@4.0.0':
- resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
- engines: {node: '>=18'}
-
- '@standard-schema/spec@1.1.0':
- resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+ '@tanstack/router-core@1.171.14':
+ resolution: {integrity: sha512-Mo3hwx0qB0cJsVYGDjG0+Ouf7VV74h/vsoDMGztdlyzDanp4gBA2s7IVvm6hFrmQM6GpD9F0Z7SqD7OldfLE7g==}
+ engines: {node: '>=20.19'}
- '@swc/helpers@0.5.15':
- resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+ '@tanstack/router-generator@1.167.18':
+ resolution: {integrity: sha512-kFvM4caRds9Q3EXg64bZubJ6rbDxyV0YDSBSGvOGzmKspQPdz5Xrh0uj5T1Ov8avUUg+c761u04VQAaEzSBXRw==}
+ engines: {node: '>=20.19'}
- '@t3-oss/env-core@0.13.10':
- resolution: {integrity: sha512-NNFfdlJ+HmPHkLi2HKy7nwuat9SIYOxei9K10lO2YlcSObDILY7mHZNSHsieIM3A0/5OOzw/P/b+yLvPdaG52g==}
+ '@tanstack/router-plugin@1.168.19':
+ resolution: {integrity: sha512-aFglwLc+bbPTgZlkXn3PvOwpjJAfgUyPGSuql4MP3XrqTTh6WkBiy2RYb6oaG5h0s7EKwivEuq85K3Y4V0Mt1g==}
+ engines: {node: '>=20.19'}
peerDependencies:
- arktype: ^2.1.0
- typescript: '>=5.0.0'
- valibot: ^1.0.0-beta.7 || ^1.0.0
- zod: ^3.24.0 || ^4.0.0
+ '@rsbuild/core': '>=1.0.2 || ^2.0.0'
+ '@tanstack/react-router': ^1.170.17
+ vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0'
+ vite-plugin-solid: ^2.11.10 || ^3.0.0-0
+ webpack: '>=5.92.0'
peerDependenciesMeta:
- arktype:
+ '@rsbuild/core':
optional: true
- typescript:
+ '@tanstack/react-router':
optional: true
- valibot:
+ vite:
optional: true
- zod:
+ vite-plugin-solid:
optional: true
-
- '@t3-oss/env-nextjs@0.13.10':
- resolution: {integrity: sha512-JfSA2WXOnvcc/uMdp31paMsfbYhhdvLLRxlwvrnlPE9bwM/n0Z+Qb9xRv48nPpvfMhOrkrTYw1I5Yc06WIKBJQ==}
- peerDependencies:
- arktype: ^2.1.0
- typescript: '>=5.0.0'
- valibot: ^1.0.0-beta.7 || ^1.0.0
- zod: ^3.24.0 || ^4.0.0
- peerDependenciesMeta:
- arktype:
+ webpack:
optional: true
- typescript:
- optional: true
- valibot:
- optional: true
- zod:
- optional: true
-
- '@tabby_ai/hijri-converter@1.0.5':
- resolution: {integrity: sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ==}
- engines: {node: '>=16.0.0'}
-
- '@tailwindcss/node@4.1.4':
- resolution: {integrity: sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw==}
-
- '@tailwindcss/oxide-android-arm64@4.1.4':
- resolution: {integrity: sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [android]
-
- '@tailwindcss/oxide-darwin-arm64@4.1.4':
- resolution: {integrity: sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [darwin]
-
- '@tailwindcss/oxide-darwin-x64@4.1.4':
- resolution: {integrity: sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [darwin]
-
- '@tailwindcss/oxide-freebsd-x64@4.1.4':
- resolution: {integrity: sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [freebsd]
-
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.4':
- resolution: {integrity: sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg==}
- engines: {node: '>= 10'}
- cpu: [arm]
- os: [linux]
-
- '@tailwindcss/oxide-linux-arm64-gnu@4.1.4':
- resolution: {integrity: sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [linux]
-
- '@tailwindcss/oxide-linux-arm64-musl@4.1.4':
- resolution: {integrity: sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [linux]
-
- '@tailwindcss/oxide-linux-x64-gnu@4.1.4':
- resolution: {integrity: sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [linux]
-
- '@tailwindcss/oxide-linux-x64-musl@4.1.4':
- resolution: {integrity: sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [linux]
-
- '@tailwindcss/oxide-wasm32-wasi@4.1.4':
- resolution: {integrity: sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==}
- engines: {node: '>=14.0.0'}
- cpu: [wasm32]
- bundledDependencies:
- - '@napi-rs/wasm-runtime'
- - '@emnapi/core'
- - '@emnapi/runtime'
- - '@tybys/wasm-util'
- - '@emnapi/wasi-threads'
- - tslib
-
- '@tailwindcss/oxide-win32-arm64-msvc@4.1.4':
- resolution: {integrity: sha512-VlnhfilPlO0ltxW9/BgfLI5547PYzqBMPIzRrk4W7uupgCt8z6Trw/tAj6QUtF2om+1MH281Pg+HHUJoLesmng==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [win32]
-
- '@tailwindcss/oxide-win32-x64-msvc@4.1.4':
- resolution: {integrity: sha512-+7S63t5zhYjslUGb8NcgLpFXD+Kq1F/zt5Xv5qTv7HaFTG/DHyHD9GA6ieNAxhgyA4IcKa/zy7Xx4Oad2/wuhw==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [win32]
- '@tailwindcss/oxide@4.1.4':
- resolution: {integrity: sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ==}
- engines: {node: '>= 10'}
+ '@tanstack/router-utils@1.162.2':
+ resolution: {integrity: sha512-hTWqJtqIFFdvuCl8WXNyrodp2L9zo2G37xKRrcVmVRWpAB2h+U1LuRAfS4tsFTiWOIoE/B+WDVFB8JpoEdw6jQ==}
+ engines: {node: '>=20.19'}
- '@tailwindcss/postcss@4.1.4':
- resolution: {integrity: sha512-bjV6sqycCEa+AQSt2Kr7wpGF1bOZJ5wsqnLEkqSbM/JEHxx/yhMH8wHmdkPyApF9xhHeMSwnnkDUUMMM/hYnXw==}
+ '@tanstack/start-client-core@1.170.13':
+ resolution: {integrity: sha512-o37M3msIK5ec87kPrIYJWXb1XPnjIe5/jrkGLXiXpFuVL99z7mhoBCzftKtVPtzqI8EElnRE/VGFYT9BHNnWcw==}
+ engines: {node: '>=22.12.0'}
- '@tanstack/query-core@5.90.19':
- resolution: {integrity: sha512-GLW5sjPVIvH491VV1ufddnfldyVB+teCnpPIvweEfkpRx7CfUmUGhoh9cdcUKBh/KwVxk22aNEDxeTsvmyB/WA==}
+ '@tanstack/start-fn-stubs@1.162.0':
+ resolution: {integrity: sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==}
+ engines: {node: '>=22.12.0'}
- '@tanstack/react-query@5.90.19':
- resolution: {integrity: sha512-qTZRZ4QyTzQc+M0IzrbKHxSeISUmRB3RPGmao5bT+sI6ayxSRhn0FXEnT5Hg3as8SBFcRosrXXRFB+yAcxVxJQ==}
+ '@tanstack/start-plugin-core@1.171.19':
+ resolution: {integrity: sha512-+fpW3Z/2vPT8HDV1c5p2WC6/g2k/AV/ujdJVDcn/VFd+gXRtzSX1D/LfozlaDbhDoEsqOnAk/mGwjg60JkUA2Q==}
+ engines: {node: '>=22.12.0'}
peerDependencies:
- react: ^18 || ^19
+ '@rsbuild/core': ^2.0.0
+ vite: '>=7.0.0'
+ peerDependenciesMeta:
+ '@rsbuild/core':
+ optional: true
+ vite:
+ optional: true
- '@tanstack/react-table@8.21.2':
- resolution: {integrity: sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==}
- engines: {node: '>=12'}
- peerDependencies:
- react: '>=16.8'
- react-dom: '>=16.8'
+ '@tanstack/start-server-core@1.169.16':
+ resolution: {integrity: sha512-lvAjQpH3nHJtd4xy0iHIaWbsTbyN9EBxuYCxbtXH0EpeBQPg+TCPhu9GQC9WbbA1rE//s82CpE55oYDQMqkU5A==}
+ engines: {node: '>=22.12.0'}
- '@tanstack/table-core@8.21.2':
- resolution: {integrity: sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==}
- engines: {node: '>=12'}
+ '@tanstack/start-storage-context@1.167.16':
+ resolution: {integrity: sha512-zTegxlij4BC1DbCrC6rsVlMOQVMzOuG5IllacZEkrUdhiFwMIMYpk0VWGH+d0ucx5RBkmv8e8GNX3AOVBWclfg==}
+ engines: {node: '>=22.12.0'}
- '@trpc/client@11.5.1':
- resolution: {integrity: sha512-7I6JJ1I1lxv3S87ht3FAIZi0XxQa7hnQ9K+Oo5BH7cGO8ZtWe9Ftq6ItdkuDfpsnsRPcR2h158AMWbNs/iptqg==}
- peerDependencies:
- '@trpc/server': 11.5.1
- typescript: '>=5.7.2'
+ '@tanstack/store@0.11.0':
+ resolution: {integrity: sha512-WlzzCt3xi0G6pCAJu1U+2jiECwabETDpQDi3hfkFZvJii9AuZqEKbOiVarX1/bWhTNjU486yQtJCCasi/0q+Cw==}
- '@trpc/next@11.5.1':
- resolution: {integrity: sha512-MWb7Jqa0XYx0z2he7Eq7HSILc2z9oDqUtsbWz+S6LWCemewRSRzmG8UuoiZm+ICEoRa452RmqeQH1EYiPCWTUg==}
- peerDependencies:
- '@tanstack/react-query': ^5.59.15
- '@trpc/client': 11.5.1
- '@trpc/react-query': 11.5.1
- '@trpc/server': 11.5.1
- next: '*'
- react: '>=16.8.0'
- react-dom: '>=16.8.0'
- typescript: '>=5.7.2'
- peerDependenciesMeta:
- '@tanstack/react-query':
- optional: true
- '@trpc/react-query':
- optional: true
+ '@tanstack/store@0.9.3':
+ resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==}
- '@trpc/react-query@11.5.1':
- resolution: {integrity: sha512-Y+e0Lp7na1LAjoYqtHVjxpfayOuvvnuchS723b4dsI/V0h3irBuI+bx38S3m7RgaIh/L1av1SldFrKIauk+r/A==}
+ '@tanstack/table-core@9.0.0-beta.38':
+ resolution: {integrity: sha512-Sr+jaFelc5CfkyYeVrPYYPL6PQJyP17p5WQHt+ip2vfKzxmhNLXD2mrI66wC8sNYfrhuWt6a5fMiSr4W3dXMDg==}
+ engines: {node: '>=20'}
+
+ '@tanstack/virtual-file-routes@1.162.0':
+ resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==}
+ engines: {node: '>=20.19'}
+
+ '@trpc/client@11.5.1':
+ resolution: {integrity: sha512-7I6JJ1I1lxv3S87ht3FAIZi0XxQa7hnQ9K+Oo5BH7cGO8ZtWe9Ftq6ItdkuDfpsnsRPcR2h158AMWbNs/iptqg==}
peerDependencies:
- '@tanstack/react-query': ^5.80.3
- '@trpc/client': 11.5.1
'@trpc/server': 11.5.1
- react: '>=18.2.0'
- react-dom: '>=18.2.0'
typescript: '>=5.7.2'
'@trpc/server@11.5.1':
@@ -1789,25 +1361,19 @@ packages:
'@ts-morph/common@0.27.0':
resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==}
- '@types/node@20.17.19':
- resolution: {integrity: sha512-LEwC7o1ifqg/6r2gn9Dns0f1rhK+fPFDoMiceTJ6kWmVk6bgXBI/9IOWfVan4WiAavK9pIVWdX0/e3J+eEUh5A==}
-
- '@types/prismjs@1.26.5':
- resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
+ '@tybys/wasm-util@0.10.3':
+ resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
- '@types/prop-types@15.7.14':
- resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
+ '@types/node@22.20.1':
+ resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==}
- '@types/react-dom@18.3.5':
- resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==}
+ '@types/react-dom@19.2.3':
+ resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
peerDependencies:
- '@types/react': ^18.0.0
+ '@types/react': ^19.2.0
- '@types/react@18.3.18':
- resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==}
-
- '@types/statuses@2.0.6':
- resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}
+ '@types/react@19.2.17':
+ resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==}
'@types/validate-npm-package-name@4.0.2':
resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==}
@@ -1818,13 +1384,30 @@ packages:
'@types/whatwg-url@13.0.0':
resolution: {integrity: sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==}
+ '@vitejs/plugin-react@6.0.3':
+ resolution: {integrity: sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0
+ babel-plugin-react-compiler: ^1.0.0
+ vite: ^8.0.0
+ peerDependenciesMeta:
+ '@rolldown/plugin-babel':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
- agent-base@7.1.4:
- resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
- engines: {node: '>= 14'}
+ ajv-formats@2.1.1:
+ resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
ajv-formats@3.0.1:
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
@@ -1834,8 +1417,12 @@ packages:
ajv:
optional: true
- ajv@8.18.0:
- resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
+ ajv@8.20.0:
+ resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==}
+
+ ansi-colors@4.1.3:
+ resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
+ engines: {node: '>=6'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
@@ -1845,15 +1432,15 @@ packages:
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
engines: {node: '>=12'}
- ansi-styles@4.3.0:
- resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
- engines: {node: '>=8'}
+ ansis@4.3.1:
+ resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==}
+ engines: {node: '>=14'}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
- aria-hidden@1.2.4:
- resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
+ aria-hidden@1.2.6:
+ resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
engines: {node: '>=10'}
asn1js@3.0.7:
@@ -1864,6 +1451,13 @@ packages:
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
engines: {node: '>=4'}
+ atomically@1.7.0:
+ resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==}
+ engines: {node: '>=10.12.0'}
+
+ babel-dead-code-elimination@1.0.12:
+ resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==}
+
babel-plugin-react-compiler@1.0.0:
resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==}
@@ -1946,12 +1540,12 @@ packages:
zod:
optional: true
- body-parser@2.2.2:
- resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
+ body-parser@2.3.0:
+ resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
engines: {node: '>=18'}
- brace-expansion@5.0.5:
- resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
+ brace-expansion@5.0.7:
+ resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==}
engines: {node: 18 || 20 || >=22}
braces@3.0.3:
@@ -1994,6 +1588,10 @@ packages:
resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+ chokidar@5.0.0:
+ resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
+ engines: {node: '>= 20.19.0'}
+
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
@@ -2005,17 +1603,9 @@ packages:
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
engines: {node: '>=6'}
- cli-width@4.1.0:
- resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
- engines: {node: '>= 12'}
-
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
- cliui@8.0.1:
- resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
- engines: {node: '>=12'}
-
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
@@ -2029,13 +1619,6 @@ packages:
code-block-writer@13.0.3:
resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==}
- color-convert@2.0.1:
- resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
- engines: {node: '>=7.0.0'}
-
- color-name@1.1.4:
- resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
-
commander@11.1.0:
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
engines: {node: '>=16'}
@@ -2044,6 +1627,14 @@ packages:
resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
engines: {node: '>=20'}
+ conf@10.2.0:
+ resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==}
+ engines: {node: '>=12'}
+
+ consola@3.4.2:
+ resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
+ engines: {node: ^14.18.0 || >=16.10.0}
+
content-disposition@1.1.0:
resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
engines: {node: '>=18'}
@@ -2052,9 +1643,16 @@ packages:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
+ content-type@2.0.0:
+ resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==}
+ engines: {node: '>=18'}
+
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ cookie-es@3.1.1:
+ resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==}
+
cookie-signature@1.2.2:
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
engines: {node: '>=6.6.0'}
@@ -2063,10 +1661,6 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
- cookie@1.1.1:
- resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
- engines: {node: '>=18'}
-
copy-anything@3.0.5:
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
engines: {node: '>=12.13'}
@@ -2075,8 +1669,8 @@ packages:
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
engines: {node: '>= 0.10'}
- cosmiconfig@9.0.1:
- resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==}
+ cosmiconfig@9.0.2:
+ resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==}
engines: {node: '>=14'}
peerDependencies:
typescript: '>=4.9.5'
@@ -2088,23 +1682,51 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ crossws@0.4.9:
+ resolution: {integrity: sha512-iWx+1OMSG2aOHpjyf9AESOzkwsVdS49cXM9dVrI2PDhxU5l2RIWE/KG56gk4BbAnsMoycvniJ9OnOxO9LRzHVA==}
+ peerDependencies:
+ srvx: '>=0.11.5'
+ peerDependenciesMeta:
+ srvx:
+ optional: true
+
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
hasBin: true
- csstype@3.1.3:
- resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
- data-uri-to-buffer@4.0.1:
- resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
- engines: {node: '>= 12'}
+ date-fns@4.4.0:
+ resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==}
- date-fns-jalali@4.1.0-0:
- resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==}
+ db0@0.3.4:
+ resolution: {integrity: sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw==}
+ peerDependencies:
+ '@electric-sql/pglite': '*'
+ '@libsql/client': '*'
+ better-sqlite3: '*'
+ drizzle-orm: '*'
+ mysql2: '*'
+ sqlite3: '*'
+ peerDependenciesMeta:
+ '@electric-sql/pglite':
+ optional: true
+ '@libsql/client':
+ optional: true
+ better-sqlite3:
+ optional: true
+ drizzle-orm:
+ optional: true
+ mysql2:
+ optional: true
+ sqlite3:
+ optional: true
- date-fns@4.1.0:
- resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+ debounce-fn@4.0.0:
+ resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==}
+ engines: {node: '>=10'}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
@@ -2135,6 +1757,10 @@ packages:
resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==}
engines: {node: '>=18'}
+ define-lazy-prop@2.0.0:
+ resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
+ engines: {node: '>=8'}
+
define-lazy-prop@3.0.0:
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
engines: {node: '>=12'}
@@ -2157,18 +1783,18 @@ packages:
resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==}
engines: {node: '>=0.3.1'}
- dotenv@17.4.1:
- resolution: {integrity: sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==}
+ dot-prop@6.0.1:
+ resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
+ engines: {node: '>=10'}
+
+ dotenv@17.4.2:
+ resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==}
engines: {node: '>=12'}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
- eciesjs@0.4.18:
- resolution: {integrity: sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==}
- engines: {bun: '>=1', deno: '>=2', node: '>=16'}
-
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
@@ -2178,21 +1804,40 @@ packages:
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
- emoji-regex@8.0.0:
- resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
-
encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
- enhanced-resolve@5.18.1:
- resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
+ enhanced-resolve@5.21.6:
+ resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==}
engines: {node: '>=10.13.0'}
+ enquirer@2.4.1:
+ resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
+ engines: {node: '>=8.6'}
+
env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
+ env-runner@0.1.16:
+ resolution: {integrity: sha512-2LRJM4P2KLX6J83QZZrMqvgCDt/D5ea7wPcI3yYiy5cG/9rX5QwdwZFx0D7ktWnjdRyZxYjttGGorb5nFqb1CA==}
+ hasBin: true
+ peerDependencies:
+ '@netlify/runtime': ^4.1.23
+ '@vercel/queue': '>=0.2.0'
+ miniflare: ^4.20260515.0
+ wrangler: ^4.0.0
+ peerDependenciesMeta:
+ '@netlify/runtime':
+ optional: true
+ '@vercel/queue':
+ optional: true
+ miniflare:
+ optional: true
+ wrangler:
+ optional: true
+
error-ex@1.3.4:
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
@@ -2204,8 +1849,8 @@ packages:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
- es-object-atoms@1.1.1:
- resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ es-object-atoms@1.1.2:
+ resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
engines: {node: '>= 0.4'}
escalade@3.2.0:
@@ -2224,8 +1869,8 @@ packages:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
- eventsource-parser@3.0.6:
- resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
+ eventsource-parser@3.1.0:
+ resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==}
engines: {node: '>=18.0.0'}
eventsource@3.0.7:
@@ -2240,8 +1885,8 @@ packages:
resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
engines: {node: ^18.19.0 || >=20.5.0}
- express-rate-limit@8.3.2:
- resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==}
+ express-rate-limit@8.5.2:
+ resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==}
engines: {node: '>= 16'}
peerDependencies:
express: '>= 4.11'
@@ -2250,6 +1895,9 @@ packages:
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
engines: {node: '>= 18'}
+ exsolve@1.1.0:
+ resolution: {integrity: sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==}
+
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -2257,8 +1905,8 @@ packages:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
- fast-uri@3.1.0:
- resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
+ fast-uri@3.1.3:
+ resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==}
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
@@ -2272,9 +1920,8 @@ packages:
picomatch:
optional: true
- fetch-blob@3.2.0:
- resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
- engines: {node: ^12.20 || >= 14.13}
+ fetchdts@0.1.7:
+ resolution: {integrity: sha512-YoZjBdafyLIop9lSxXVI33oLD5kN31q4Td+CasofLLYeLXRFeOsuOw0Uo+XNRi9PZlbfdlN2GmRtm4tCEQ9/KA==}
figures@6.1.0:
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
@@ -2288,43 +1935,53 @@ packages:
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
engines: {node: '>= 18.0.0'}
- formdata-polyfill@4.0.10:
- resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
- engines: {node: '>=12.20.0'}
+ find-up@3.0.0:
+ resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
+ engines: {node: '>=6'}
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
+ framer-motion@12.42.2:
+ resolution: {integrity: sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==}
+ peerDependencies:
+ '@emotion/is-prop-valid': '*'
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/is-prop-valid':
+ optional: true
+ react:
+ optional: true
+ react-dom:
+ optional: true
+
fresh@2.0.0:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
- fs-extra@11.3.4:
- resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
+ fs-extra@11.3.6:
+ resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==}
engines: {node: '>=14.14'}
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
fuzzysort@3.1.0:
resolution: {integrity: sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==}
- geist@1.3.1:
- resolution: {integrity: sha512-Q4gC1pBVPN+D579pBaz0TRRnGA4p9UK6elDY/xizXdFk/g4EKR5g0I+4p/Kj6gM0SajDBZ/0FvDV9ey9ud7BWw==}
- peerDependencies:
- next: '>=13.2.0'
-
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
- get-caller-file@2.0.5:
- resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
- engines: {node: 6.* || 8.* || >= 10.*}
-
- get-east-asian-width@1.5.0:
- resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
+ get-east-asian-width@1.6.0:
+ resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
engines: {node: '>=18'}
get-intrinsic@1.3.0:
@@ -2362,32 +2019,47 @@ packages:
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
- graphql@16.13.2:
- resolution: {integrity: sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==}
- engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
+ h3@2.0.1-rc.20:
+ resolution: {integrity: sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==}
+ engines: {node: '>=20.11.1'}
+ hasBin: true
+ peerDependencies:
+ crossws: ^0.4.1
+ peerDependenciesMeta:
+ crossws:
+ optional: true
+
+ h3@2.0.1-rc.22:
+ resolution: {integrity: sha512-Esv0DMIuPkCTSWCA0vO73vcTqwzH1wjSrAO1TXNu/K3up1sZHa9EKMapbmxCDYBeymC3fVTk4qxp7ogQWQ+KgA==}
+ engines: {node: '>=20.11.1'}
+ hasBin: true
+ peerDependencies:
+ crossws: ^0.4.1
+ peerDependenciesMeta:
+ crossws:
+ optional: true
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
- hasown@2.0.2:
- resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ hasown@2.0.4:
+ resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'}
- headers-polyfill@4.0.3:
- resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==}
-
- hono@4.12.12:
- resolution: {integrity: sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==}
+ hono@4.12.29:
+ resolution: {integrity: sha512-1hNiRjawYrLq/4m3DQQjPGFg0VZkk4RjQJDff/excI6Dm9BiL75qxGrd7/c6YOxPdq6AscP3LiXhQ6fKFC1Waw==}
engines: {node: '>=16.9.0'}
+ hookable@6.1.1:
+ resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==}
+
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
- https-proxy-agent@7.0.6:
- resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
- engines: {node: '>= 14'}
+ httpxy@0.5.5:
+ resolution: {integrity: sha512-uDjmnPyp1q4Sgzf3w+J/Fc6UqcCEj0x4Wjp7OqK5dGhNeDgpyrAmnS6ey8QWrX3SWDon2DMKf9sBa5X9+CVyMA==}
human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
@@ -2397,8 +2069,8 @@ packages:
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
engines: {node: '>=18.18.0'}
- iconv-lite@0.7.2:
- resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
+ iconv-lite@0.7.3:
+ resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==}
engines: {node: '>=0.10.0'}
ignore@5.3.2:
@@ -2412,14 +2084,8 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
- input-otp@1.4.2:
- resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==}
- peerDependencies:
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
-
- ip-address@10.1.0:
- resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
+ ip-address@10.2.0:
+ resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
engines: {node: '>= 12'}
ipaddr.js@1.9.1:
@@ -2429,6 +2095,11 @@ packages:
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+ is-docker@2.2.1:
+ resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
+ engines: {node: '>=8'}
+ hasBin: true
+
is-docker@3.0.0:
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -2438,10 +2109,6 @@ packages:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
- is-fullwidth-code-point@3.0.0:
- resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
- engines: {node: '>=8'}
-
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@@ -2459,13 +2126,14 @@ packages:
resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
engines: {node: '>=12'}
- is-node-process@1.2.0:
- resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==}
-
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
+ is-obj@2.0.0:
+ resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
+ engines: {node: '>=8'}
+
is-obj@3.0.0:
resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==}
engines: {node: '>=12'}
@@ -2501,10 +2169,18 @@ packages:
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
engines: {node: '>=12.13'}
+ is-wsl@2.2.0:
+ resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
+ engines: {node: '>=8'}
+
is-wsl@3.1.1:
resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
engines: {node: '>=16'}
+ isbot@5.2.0:
+ resolution: {integrity: sha512-gbZiGCb4B5xaoxg9mS7koAyRdvJnArk10VLSHOgz6rtBG93/pi1xOFaVvXMKZ7JXgyZ8zAbNRK5uIBdIUTFSqw==}
+ engines: {node: '>=18'}
+
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -2512,8 +2188,8 @@ packages:
resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==}
engines: {node: '>=18'}
- jiti@2.4.2:
- resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
+ jiti@2.7.0:
+ resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
hasBin: true
jose@6.1.3:
@@ -2537,6 +2213,9 @@ packages:
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+ json-schema-typed@7.0.3:
+ resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==}
+
json-schema-typed@8.0.2:
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
@@ -2545,8 +2224,8 @@ packages:
engines: {node: '>=6'}
hasBin: true
- jsonfile@6.2.0:
- resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
+ jsonfile@6.2.1:
+ resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==}
kleur@3.0.3:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
@@ -2560,84 +2239,98 @@ packages:
resolution: {integrity: sha512-kWiueDWXhbCchgiotwXkwdxZE/6h56IHAeFWg4euUfW0YsmO9sxbAxzx1KLLv2lox15EfuuxHQvgJ1qIfZuHGw==}
engines: {node: '>=20.0.0'}
- lightningcss-darwin-arm64@1.29.2:
- resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
+ lightningcss-android-arm64@1.32.0:
+ resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.32.0:
+ resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
- lightningcss-darwin-x64@1.29.2:
- resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==}
+ lightningcss-darwin-x64@1.32.0:
+ resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
- lightningcss-freebsd-x64@1.29.2:
- resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==}
+ lightningcss-freebsd-x64@1.32.0:
+ resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
- lightningcss-linux-arm-gnueabihf@1.29.2:
- resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==}
+ lightningcss-linux-arm-gnueabihf@1.32.0:
+ resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
- lightningcss-linux-arm64-gnu@1.29.2:
- resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==}
+ lightningcss-linux-arm64-gnu@1.32.0:
+ resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
- lightningcss-linux-arm64-musl@1.29.2:
- resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==}
+ lightningcss-linux-arm64-musl@1.32.0:
+ resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
- lightningcss-linux-x64-gnu@1.29.2:
- resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==}
+ lightningcss-linux-x64-gnu@1.32.0:
+ resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
- lightningcss-linux-x64-musl@1.29.2:
- resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==}
+ lightningcss-linux-x64-musl@1.32.0:
+ resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
- lightningcss-win32-arm64-msvc@1.29.2:
- resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==}
+ lightningcss-win32-arm64-msvc@1.32.0:
+ resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
- lightningcss-win32-x64-msvc@1.29.2:
- resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==}
+ lightningcss-win32-x64-msvc@1.32.0:
+ resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
- lightningcss@1.29.2:
- resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==}
+ lightningcss@1.32.0:
+ resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+ locate-path@3.0.0:
+ resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
+ engines: {node: '>=6'}
+
log-symbols@6.0.0:
resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
engines: {node: '>=18'}
- loose-envify@1.4.0:
- resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
- hasBin: true
-
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ lucide-react@0.545.0:
+ resolution: {integrity: sha512-7r1/yUuflQDSt4f1bpn5ZAocyIxcTyVyBBChSVtBKn5M+392cPmI5YJMWOJKk/HUWGm5wg83chlAZtCcGbEZtw==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -2676,6 +2369,10 @@ packages:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
+ mimic-fn@3.1.0:
+ resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==}
+ engines: {node: '>=8'}
+
mimic-function@5.0.1:
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=18'}
@@ -2718,33 +2415,34 @@ packages:
socks:
optional: true
- ms@2.1.3:
- resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ motion-dom@12.42.2:
+ resolution: {integrity: sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==}
- msw@2.13.2:
- resolution: {integrity: sha512-go2H1TIERKkC48pXiwec5l6sbNqYuvqOk3/vHGo1Zd+pq/H63oFawDQerH+WQdUw/flJFHDG7F+QdWMwhntA/A==}
- engines: {node: '>=18'}
- hasBin: true
+ motion-utils@12.39.0:
+ resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==}
+
+ motion@12.42.2:
+ resolution: {integrity: sha512-Atvv11yUKIid41cVrRBDVX5m8tF8kNpExRSlbpt6APClhDjtwQssgFHhQzejxw7/7YYbjHSPKBVbHo05BuJT5Q==}
peerDependencies:
- typescript: '>= 4.8.x'
+ '@emotion/is-prop-valid': '*'
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
- typescript:
+ '@emotion/is-prop-valid':
+ optional: true
+ react:
+ optional: true
+ react-dom:
optional: true
- mute-stream@2.0.0:
- resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
- engines: {node: ^18.17.0 || >=20.5.0}
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
nanoid@3.3.12:
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
- nanoid@3.3.8:
- resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
- engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
- hasBin: true
-
nanostores@1.2.0:
resolution: {integrity: sha512-F0wCzbsH80G7XXo0Jd9/AVQC7ouWY6idUCTnMwW5t/Rv9W8qmO6endavDwg7TNp5GbugwSukFMVZqzPSrSMndg==}
engines: {node: ^20.0.0 || >=22.0.0}
@@ -2753,12 +2451,6 @@ packages:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
- next-themes@0.4.4:
- resolution: {integrity: sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==}
- peerDependencies:
- react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
- react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
-
next@15.5.18:
resolution: {integrity: sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
@@ -2780,14 +2472,39 @@ packages:
sass:
optional: true
- node-domexception@1.0.0:
- resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
- engines: {node: '>=10.5.0'}
- deprecated: Use your platform's native DOMException instead
+ nf3@0.3.21:
+ resolution: {integrity: sha512-DJ1Ss/jfUdqETSbouezgAyh6NmD3r2NnpX7Vhwz7nPTc8TvqFddJ/tMgYDwhWU4H/vTOUXDYG3sWsB9vGBX/HA==}
- node-fetch@3.3.2:
- resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ nitro@3.0.260610-beta:
+ resolution: {integrity: sha512-KPb4L5yaF/Rx/xoGMpgHRJvZhbhGiqbRKOwwPLCH9jKTKTsEUHLjnJas85AeCzaswqa8Wi52eQBtRsODC4PS0Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@vercel/queue': ^0.3.0
+ dotenv: '*'
+ giget: '*'
+ jiti: ^2.7.0
+ rollup: ^4.61.1
+ vite: ^7 || ^8
+ xml2js: ^0.6.2
+ zephyr-agent: ^0.2.0
+ peerDependenciesMeta:
+ '@vercel/queue':
+ optional: true
+ dotenv:
+ optional: true
+ giget:
+ optional: true
+ jiti:
+ optional: true
+ rollup:
+ optional: true
+ vite:
+ optional: true
+ xml2js:
+ optional: true
+ zephyr-agent:
+ optional: true
node-releases@2.0.37:
resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==}
@@ -2812,6 +2529,15 @@ packages:
resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==}
engines: {node: '>= 10'}
+ ocache@0.1.5:
+ resolution: {integrity: sha512-kNNnkkVQup/QDvmTz8Q84wc2ntiyoVHDxa6eHWKt5qdGAmFRBIxy83rxgCYEjW0x06UJ9E3P6VgM2yY4rOBH4w==}
+
+ ofetch@2.0.0-alpha.3:
+ resolution: {integrity: sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA==}
+
+ ohash@2.0.11:
+ resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
+
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
@@ -2831,12 +2557,25 @@ packages:
resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==}
engines: {node: '>=20'}
+ open@8.4.2:
+ resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
+ engines: {node: '>=12'}
+
ora@8.2.0:
resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==}
engines: {node: '>=18'}
- outvariant@1.4.3:
- resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==}
+ p-limit@2.3.0:
+ resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+ engines: {node: '>=6'}
+
+ p-locate@3.0.0:
+ resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
+ engines: {node: '>=6'}
+
+ p-try@2.2.0:
+ resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+ engines: {node: '>=6'}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
@@ -2857,6 +2596,10 @@ packages:
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+ path-exists@3.0.0:
+ resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
+ engines: {node: '>=4'}
+
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
@@ -2865,12 +2608,12 @@ packages:
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
engines: {node: '>=12'}
- path-to-regexp@6.3.0:
- resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
-
path-to-regexp@8.4.2:
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2878,47 +2621,43 @@ packages:
resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
engines: {node: '>=8.6'}
- picomatch@4.0.4:
- resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
+ picomatch@4.0.5:
+ resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
engines: {node: '>=12'}
pkce-challenge@5.0.1:
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
engines: {node: '>=16.20.0'}
- postcss-selector-parser@7.1.1:
- resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
+ pkg-up@3.1.0:
+ resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
+ engines: {node: '>=8'}
+
+ postcss-selector-parser@7.1.4:
+ resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==}
engines: {node: '>=4'}
postcss@8.4.31:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
engines: {node: ^10 || ^12 || >=14}
- postcss@8.5.2:
- resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==}
+ postcss@8.5.16:
+ resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==}
engines: {node: ^10 || ^12 || >=14}
- postcss@8.5.9:
- resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==}
- engines: {node: ^10 || ^12 || >=14}
-
- postgres@3.4.5:
- resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==}
- engines: {node: '>=12'}
-
powershell-utils@0.1.0:
resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
engines: {node: '>=20'}
+ prettier@3.9.5:
+ resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==}
+ engines: {node: '>=14'}
+ hasBin: true
+
pretty-ms@9.3.0:
resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
engines: {node: '>=18'}
- prism-react-renderer@2.4.1:
- resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==}
- peerDependencies:
- react: '>=16.0.0'
-
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
@@ -2938,60 +2677,35 @@ packages:
resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==}
engines: {node: '>=16.0.0'}
- qs@6.15.1:
- resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==}
+ qs@6.15.3:
+ resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==}
engines: {node: '>=0.6'}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- radix-ui@1.4.3:
- resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==}
- peerDependencies:
- '@types/react': '*'
- '@types/react-dom': '*'
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
-
- range-parser@1.2.1:
- resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ range-parser@1.3.0:
+ resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==}
engines: {node: '>= 0.6'}
raw-body@3.0.2:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
engines: {node: '>= 0.10'}
- react-day-picker@9.14.0:
- resolution: {integrity: sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==}
+ react-day-picker@10.0.1:
+ resolution: {integrity: sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w==}
engines: {node: '>=18'}
peerDependencies:
+ '@types/react': '>=16.8.0'
react: '>=16.8.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- react-dom@18.3.1:
- resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
- peerDependencies:
- react: ^18.3.1
-
- react-error-boundary@6.1.0:
- resolution: {integrity: sha512-02k9WQ/mUhdbXir0tC1NiMesGzRPaCsJEWU/4bcFrbY1YMZOtHShtZP6zw0SJrBWA/31H0KT9/FgdL8+sPKgHA==}
- peerDependencies:
- react: ^18.0.0 || ^19.0.0
-
- react-hook-form@7.55.0:
- resolution: {integrity: sha512-XRnjsH3GVMQz1moZTW53MxfoWN7aDpUg/GpVNc4A3eXRVNdGXfbzJ4vM4aLQ8g6XCUh1nIbx70aaNCl7kxnjog==}
- engines: {node: '>=18.0.0'}
- peerDependencies:
- react: ^16.8.0 || ^17 || ^18 || ^19
-
- react-icons@5.7.0:
- resolution: {integrity: sha512-LBLy340Rzqy6+/yVhZKT3B/QpP1BZaesGqasf09HPOBzRarcDIFH0WwXlXQfE7q7ipxK4MSiC5DIBWURCny6fw==}
+ react-dom@19.2.7:
+ resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==}
peerDependencies:
- react: '*'
+ react: ^19.2.7
react-remove-scroll-bar@2.3.8:
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
@@ -3003,8 +2717,8 @@ packages:
'@types/react':
optional: true
- react-remove-scroll@2.6.3:
- resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==}
+ react-remove-scroll@2.7.2:
+ resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
@@ -3023,27 +2737,27 @@ packages:
'@types/react':
optional: true
- react@18.3.1:
- resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ react@19.2.7:
+ resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==}
engines: {node: '>=0.10.0'}
- recast@0.23.11:
- resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
+ readdirp@5.0.0:
+ resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
+ engines: {node: '>= 20.19.0'}
+
+ recast@0.23.12:
+ resolution: {integrity: sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA==}
engines: {node: '>= 4'}
reflect-metadata@0.2.2:
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
- require-directory@2.1.1:
- resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
- engines: {node: '>=0.10.0'}
-
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
- reselect@5.1.1:
- resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
+ reselect@5.2.0:
+ resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
@@ -3053,16 +2767,21 @@ packages:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
- rettime@0.10.1:
- resolution: {integrity: sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==}
-
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ rolldown@1.1.5:
+ resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+
rou3@0.7.12:
resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==}
+ rou3@0.8.1:
+ resolution: {integrity: sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==}
+
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
@@ -3077,8 +2796,8 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
- scheduler@0.23.2:
- resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
@@ -3093,21 +2812,29 @@ packages:
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
engines: {node: '>= 18'}
+ seroval-plugins@1.5.5:
+ resolution: {integrity: sha512-+BDhqYM6CEn3x09v44dpa9p6974FuUB2dxk+Ctn04k0cO1Zt6QODTXfmEZK0eBaTe/fJBvP4NMGuNJ+R8T+QMg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ seroval: ^1.0
+
+ seroval@1.5.5:
+ resolution: {integrity: sha512-bSjOuPcwPKLSJNhr9+bZxA20nQxVle5J5MNsYRVE6cIg7KpRLXGupymePavu0jrxlPiPsr4xGZSB8yUY2sH2sw==}
+ engines: {node: '>=10'}
+
serve-static@2.2.1:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
- server-only@0.0.1:
- resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
-
set-cookie-parser@3.0.1:
resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
- shadcn@4.2.0:
- resolution: {integrity: sha512-ZDuV340itidaUd4Gi1BxQX+Y7Ush6BHp6URZBM2RyxUUBZ6yFtOWIr4nVY+Ro+YRSpo82v7JrsmtcU5xoBCMJQ==}
+ shadcn@4.13.0:
+ resolution: {integrity: sha512-5fuJ4jI/GcPeA/iTL4cJivCZuYQGXz/N3bIzyd+Gd/FM6xUCy2MxGG+LaDQuw2cjNy9zGPSFPTEmI048UwPTZA==}
+ engines: {node: '>=20.18.1'}
hasBin: true
sharp@0.34.5:
@@ -3134,8 +2861,8 @@ packages:
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
engines: {node: '>= 0.4'}
- side-channel@1.1.0:
- resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
+ side-channel@1.1.1:
+ resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
engines: {node: '>= 0.4'}
signal-exit@3.0.7:
@@ -3148,8 +2875,11 @@ packages:
sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
- sonner@2.0.3:
- resolution: {integrity: sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==}
+ solid-js@1.9.14:
+ resolution: {integrity: sha512-sAEXC0Kk0S1EDg+8ysEWJDbYhA3RRoEjwuySUGlKIemeo0I5YZfOyumNjNs9Sv3y2nmhD+0rW66ag2HsMuQiGQ==}
+
+ sonner@2.0.7:
+ resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
peerDependencies:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
@@ -3162,9 +2892,18 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
+ source-map@0.7.6:
+ resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
+ engines: {node: '>= 12'}
+
sparse-bitfield@3.0.3:
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
+ srvx@0.11.21:
+ resolution: {integrity: sha512-GWTHjKMeekX8CwJf4VU9Oo6mJpSGaflGMddbCvR+Cmmh9sslRMiGbAoqqZacE0r1ncARh6buCEETr2W52F8b1w==}
+ engines: {node: '>=20.16.0'}
+ hasBin: true
+
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
@@ -3173,13 +2912,6 @@ packages:
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
engines: {node: '>=18'}
- strict-event-emitter@0.5.1:
- resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==}
-
- string-width@4.2.3:
- resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
- engines: {node: '>=8'}
-
string-width@7.2.0:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
@@ -3225,43 +2957,28 @@ packages:
resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
engines: {node: '>=16'}
- tabbable@6.4.0:
- resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
-
- tagged-tag@1.0.0:
- resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
- engines: {node: '>=20'}
-
- tailwind-merge@3.0.1:
- resolution: {integrity: sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==}
-
- tailwind-scrollbar@4.0.2:
- resolution: {integrity: sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==}
- engines: {node: '>=12.13.0'}
- peerDependencies:
- tailwindcss: 4.x
+ systeminformation@5.31.15:
+ resolution: {integrity: sha512-7mqCtD28TK5dVdLAQONVa/Do/NBgMH2dxqf49nh6DIKoEWuDg6tkgGBP+dN22VEJVPZa/QqiHomhWNRn4WUNTQ==}
+ engines: {node: '>=8.0.0'}
+ os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
+ hasBin: true
- tailwindcss-animate@1.0.7:
- resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
- peerDependencies:
- tailwindcss: '>=3.0.0 || insiders'
+ tailwind-merge@3.6.0:
+ resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==}
- tailwindcss@4.1.4:
- resolution: {integrity: sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==}
+ tailwindcss@4.3.2:
+ resolution: {integrity: sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==}
- tapable@2.2.1:
- resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
+ tapable@2.3.3:
+ resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
engines: {node: '>=6'}
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
- tldts-core@7.0.28:
- resolution: {integrity: sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==}
-
- tldts@7.0.28:
- resolution: {integrity: sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==}
- hasBin: true
+ tinyglobby@0.2.17:
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
@@ -3271,10 +2988,6 @@ packages:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
- tough-cookie@6.0.1:
- resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==}
- engines: {node: '>=16'}
-
tr46@5.1.1:
resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
engines: {node: '>=18'}
@@ -3299,21 +3012,27 @@ packages:
tw-animate-css@1.4.0:
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
- type-fest@5.5.0:
- resolution: {integrity: sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==}
- engines: {node: '>=20'}
-
- type-is@2.0.1:
- resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
- engines: {node: '>= 0.6'}
+ type-is@2.1.0:
+ resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==}
+ engines: {node: '>= 18'}
- typescript@5.7.3:
- resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
+ typescript@6.0.3:
+ resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
engines: {node: '>=14.17'}
hasBin: true
- undici-types@6.19.8:
- resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+ ufo@1.6.4:
+ resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ undici@7.28.0:
+ resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==}
+ engines: {node: '>=20.18.1'}
+
+ unenv@2.0.0-rc.24:
+ resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
unicorn-magic@0.3.0:
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
@@ -3327,8 +3046,112 @@ packages:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
- until-async@3.0.2:
- resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==}
+ unplugin@3.3.0:
+ resolution: {integrity: sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ '@farmfe/core': '*'
+ '@rspack/core': '*'
+ bun-types-no-globals: '*'
+ esbuild: '*'
+ rolldown: '*'
+ rollup: '*'
+ unloader: '*'
+ vite: '*'
+ webpack: '*'
+ peerDependenciesMeta:
+ '@farmfe/core':
+ optional: true
+ '@rspack/core':
+ optional: true
+ bun-types-no-globals:
+ optional: true
+ esbuild:
+ optional: true
+ rolldown:
+ optional: true
+ rollup:
+ optional: true
+ unloader:
+ optional: true
+ vite:
+ optional: true
+ webpack:
+ optional: true
+
+ unstorage@2.0.0-alpha.7:
+ resolution: {integrity: sha512-ELPztchk2zgFJnakyodVY3vJWGW9jy//keJ32IOJVGUMyaPydwcA1FtVvWqT0TNRch9H+cMNEGllfVFfScImog==}
+ peerDependencies:
+ '@azure/app-configuration': ^1.11.0
+ '@azure/cosmos': ^4.9.1
+ '@azure/data-tables': ^13.3.2
+ '@azure/identity': ^4.13.0
+ '@azure/keyvault-secrets': ^4.10.0
+ '@azure/storage-blob': ^12.31.0
+ '@capacitor/preferences': ^6 || ^7 || ^8
+ '@deno/kv': '>=0.13.0'
+ '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0
+ '@planetscale/database': ^1.19.0
+ '@upstash/redis': ^1.36.2
+ '@vercel/blob': '>=0.27.3'
+ '@vercel/functions': ^2.2.12 || ^3.0.0
+ '@vercel/kv': ^1.0.1
+ aws4fetch: ^1.0.20
+ chokidar: ^4 || ^5
+ db0: '>=0.3.4'
+ idb-keyval: ^6.2.2
+ ioredis: ^5.9.3
+ lru-cache: ^11.2.6
+ mongodb: ^6 || ^7
+ ofetch: '*'
+ uploadthing: ^7.7.4
+ peerDependenciesMeta:
+ '@azure/app-configuration':
+ optional: true
+ '@azure/cosmos':
+ optional: true
+ '@azure/data-tables':
+ optional: true
+ '@azure/identity':
+ optional: true
+ '@azure/keyvault-secrets':
+ optional: true
+ '@azure/storage-blob':
+ optional: true
+ '@capacitor/preferences':
+ optional: true
+ '@deno/kv':
+ optional: true
+ '@netlify/blobs':
+ optional: true
+ '@planetscale/database':
+ optional: true
+ '@upstash/redis':
+ optional: true
+ '@vercel/blob':
+ optional: true
+ '@vercel/functions':
+ optional: true
+ '@vercel/kv':
+ optional: true
+ aws4fetch:
+ optional: true
+ chokidar:
+ optional: true
+ db0:
+ optional: true
+ idb-keyval:
+ optional: true
+ ioredis:
+ optional: true
+ lru-cache:
+ optional: true
+ mongodb:
+ optional: true
+ ofetch:
+ optional: true
+ uploadthing:
+ optional: true
update-browserslist-db@1.2.3:
resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
@@ -3372,14 +3195,64 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
- web-streams-polyfill@3.3.3:
- resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
- engines: {node: '>= 8'}
+ vite@8.1.4:
+ resolution: {integrity: sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ '@vitejs/devtools': ^0.3.0
+ esbuild: ^0.27.0 || ^0.28.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ '@vitejs/devtools':
+ optional: true
+ esbuild:
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ vitefu@1.1.3:
+ resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==}
+ peerDependencies:
+ vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ vite:
+ optional: true
webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
+ webpack-virtual-modules@0.6.2:
+ resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
+
whatwg-url@14.2.0:
resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
engines: {node: '>=18'}
@@ -3394,14 +3267,6 @@ packages:
engines: {node: ^16.13.0 || >=18.0.0}
hasBin: true
- wrap-ansi@6.2.0:
- resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
- engines: {node: '>=8'}
-
- wrap-ansi@7.0.0:
- resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
- engines: {node: '>=10'}
-
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -3409,29 +3274,17 @@ packages:
resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==}
engines: {node: '>=20'}
- y18n@5.0.8:
- resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
- engines: {node: '>=10'}
+ xmlbuilder2@4.0.3:
+ resolution: {integrity: sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==}
+ engines: {node: '>=20.0'}
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
- yargs-parser@21.1.1:
- resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
- engines: {node: '>=12'}
-
- yargs@17.7.2:
- resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
- engines: {node: '>=12'}
-
- yocto-spinner@1.1.0:
- resolution: {integrity: sha512-/BY0AUXnS7IKO354uLLA2eRcWiqDifEbd6unXCsOxkFDAkhgUL3PH9X2bFoaU0YchnDXsF+iKleeTLJGckbXfA==}
+ yocto-spinner@1.2.1:
+ resolution: {integrity: sha512-9cbFWLhbiZp+820O4pkHGNncI7+MrUGzBOjw8NMG+ewsY+aG0DdEXnr19Smxao32YOjLZRMdn1UtaxcrXOYOIg==}
engines: {node: '>=18.19'}
- yoctocolors-cjs@2.1.3:
- resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==}
- engines: {node: '>=18'}
-
yoctocolors@2.1.2:
resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
engines: {node: '>=18'}
@@ -3450,9 +3303,16 @@ packages:
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
+ zod@4.4.3:
+ resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
+
snapshots:
- '@alloc/quick-lru@5.2.0': {}
+ '@babel/code-frame@7.27.1':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
'@babel/code-frame@7.29.0':
dependencies:
@@ -3460,6 +3320,12 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
+ '@babel/code-frame@7.29.7':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
'@babel/compat-data@7.29.0': {}
'@babel/core@7.29.0':
@@ -3490,9 +3356,17 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
- '@babel/helper-annotate-as-pure@7.27.3':
+ '@babel/generator@7.29.7':
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-annotate-as-pure@7.29.7':
dependencies:
- '@babel/types': 7.28.2
+ '@babel/types': 7.29.7
'@babel/helper-compilation-targets@7.28.6':
dependencies:
@@ -3502,25 +3376,27 @@ snapshots:
lru-cache: 5.1.1
semver: 6.3.1
- '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)':
+ '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-member-expression-to-functions': 7.28.5
- '@babel/helper-optimise-call-expression': 7.27.1
- '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0)
- '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
- '@babel/traverse': 7.29.0
+ '@babel/helper-annotate-as-pure': 7.29.7
+ '@babel/helper-member-expression-to-functions': 7.29.7
+ '@babel/helper-optimise-call-expression': 7.29.7
+ '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.0)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.29.7
+ '@babel/traverse': 7.29.7
semver: 6.3.1
transitivePeerDependencies:
- supports-color
'@babel/helper-globals@7.28.0': {}
- '@babel/helper-member-expression-to-functions@7.28.5':
+ '@babel/helper-globals@7.29.7': {}
+
+ '@babel/helper-member-expression-to-functions@7.29.7':
dependencies:
- '@babel/traverse': 7.29.0
- '@babel/types': 7.29.0
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
@@ -3531,6 +3407,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@babel/helper-module-imports@7.29.7':
+ dependencies:
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
@@ -3540,36 +3423,49 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/helper-optimise-call-expression@7.27.1':
+ '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.0)':
dependencies:
- '@babel/types': 7.28.2
+ '@babel/core': 7.29.0
+ '@babel/helper-module-imports': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+ '@babel/traverse': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-optimise-call-expression@7.29.7':
+ dependencies:
+ '@babel/types': 7.29.7
- '@babel/helper-plugin-utils@7.28.6': {}
+ '@babel/helper-plugin-utils@7.29.7': {}
- '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)':
+ '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-member-expression-to-functions': 7.28.5
- '@babel/helper-optimise-call-expression': 7.27.1
- '@babel/traverse': 7.29.0
+ '@babel/helper-member-expression-to-functions': 7.29.7
+ '@babel/helper-optimise-call-expression': 7.29.7
+ '@babel/traverse': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ '@babel/helper-skip-transparent-expression-wrappers@7.29.7':
dependencies:
- '@babel/traverse': 7.29.0
- '@babel/types': 7.28.2
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
'@babel/helper-string-parser@7.27.1': {}
- '@babel/helper-validator-identifier@7.27.1': {}
+ '@babel/helper-string-parser@7.29.7': {}
'@babel/helper-validator-identifier@7.28.5': {}
+ '@babel/helper-validator-identifier@7.29.7': {}
+
'@babel/helper-validator-option@7.27.1': {}
+ '@babel/helper-validator-option@7.29.7': {}
+
'@babel/helpers@7.29.2':
dependencies:
'@babel/template': 7.28.6
@@ -3579,47 +3475,51 @@ snapshots:
dependencies:
'@babel/types': 7.29.0
- '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)':
+ '@babel/parser@7.29.7':
+ dependencies:
+ '@babel/types': 7.29.7
+
+ '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-plugin-utils': 7.29.7
- '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-plugin-utils': 7.29.7
- '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
- '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
- '@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
- '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-annotate-as-pure': 7.29.7
+ '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.29.7
+ '@babel/helper-skip-transparent-expression-wrappers': 7.29.7
+ '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.0)
transitivePeerDependencies:
- supports-color
- '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)':
+ '@babel/preset-typescript@7.29.7(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-validator-option': 7.27.1
- '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.29.7
+ '@babel/helper-validator-option': 7.29.7
+ '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0)
+ '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.0)
+ '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.0)
transitivePeerDependencies:
- supports-color
- '@babel/runtime@7.29.2': {}
+ '@babel/runtime@7.29.7': {}
'@babel/template@7.28.6':
dependencies:
@@ -3627,6 +3527,12 @@ snapshots:
'@babel/parser': 7.29.2
'@babel/types': 7.29.0
+ '@babel/template@7.29.7':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+
'@babel/traverse@7.29.0':
dependencies:
'@babel/code-frame': 7.29.0
@@ -3639,39 +3545,52 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/types@7.28.2':
+ '@babel/traverse@7.29.7':
dependencies:
- '@babel/helper-string-parser': 7.27.1
- '@babel/helper-validator-identifier': 7.27.1
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.7
+ '@babel/helper-globals': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.7
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
'@babel/types@7.29.0':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
- '@base-ui/react@1.3.0(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@babel/types@7.29.7':
+ dependencies:
+ '@babel/helper-string-parser': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+
+ '@base-ui/react@1.6.0(@date-fns/tz@1.5.0)(@types/react@19.2.17)(date-fns@4.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
- '@babel/runtime': 7.29.2
- '@base-ui/utils': 0.2.6(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@babel/runtime': 7.29.7
+ '@base-ui/utils': 0.3.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
'@floating-ui/utils': 0.2.11
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- tabbable: 6.4.0
- use-sync-external-store: 1.6.0(react@18.3.1)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ use-sync-external-store: 1.6.0(react@19.2.7)
optionalDependencies:
- '@types/react': 18.3.18
+ '@date-fns/tz': 1.5.0
+ '@types/react': 19.2.17
+ date-fns: 4.4.0
- '@base-ui/utils@0.2.6(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@base-ui/utils@0.3.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
- '@babel/runtime': 7.29.2
+ '@babel/runtime': 7.29.7
'@floating-ui/utils': 0.2.11
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- reselect: 5.1.1
- use-sync-external-store: 1.6.0(react@18.3.1)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ reselect: 5.2.0
+ use-sync-external-store: 1.6.0(react@19.2.7)
optionalDependencies:
- '@types/react': 18.3.18
+ '@types/react': 19.2.17
'@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0)':
dependencies:
@@ -3706,14 +3625,14 @@ snapshots:
'@better-auth/utils': 0.3.1
mongodb: 7.1.0
- '@better-auth/passkey@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(mongodb@7.1.0)(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(better-call@1.3.2(zod@4.3.5))(nanostores@1.2.0)':
+ '@better-auth/passkey@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(@tanstack/react-start@1.168.27(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0)))(mongodb@7.1.0)(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(solid-js@1.9.14))(better-call@1.3.2(zod@4.3.5))(nanostores@1.2.0)':
dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0)
'@better-auth/utils': 0.3.1
'@better-fetch/fetch': 1.1.21
'@simplewebauthn/browser': 13.2.2
'@simplewebauthn/server': 13.3.0
- better-auth: 1.5.5(mongodb@7.1.0)(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ better-auth: 1.5.5(@tanstack/react-start@1.168.27(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0)))(mongodb@7.1.0)(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(solid-js@1.9.14)
better-call: 1.3.2(zod@4.3.5)
nanostores: 1.2.0
zod: 4.3.6
@@ -3768,73 +3687,67 @@ snapshots:
'@biomejs/cli-win32-x64@2.3.10':
optional: true
- '@date-fns/tz@1.4.1': {}
+ '@date-fns/tz@1.5.0': {}
- '@dotenvx/dotenvx@1.61.0':
+ '@dotenvx/dotenvx@1.75.1':
dependencies:
+ '@dotenvx/primitives': 0.8.0
commander: 11.1.0
- dotenv: 17.4.1
- eciesjs: 0.4.18
+ conf: 10.2.0
+ dotenv: 17.4.2
+ enquirer: 2.4.1
+ env-paths: 2.2.1
execa: 5.1.1
- fdir: 6.5.0(picomatch@4.0.4)
+ fdir: 6.5.0(picomatch@4.0.5)
ignore: 5.3.2
object-treeify: 1.1.33
- picomatch: 4.0.4
+ open: 8.4.2
+ picomatch: 4.0.5
+ systeminformation: 5.31.15
+ undici: 7.28.0
which: 4.0.0
- yocto-spinner: 1.1.0
+ yocto-spinner: 1.2.1
- '@ecies/ciphers@0.2.6(@noble/ciphers@1.3.0)':
+ '@dotenvx/primitives@0.8.0': {}
+
+ '@emnapi/core@1.11.1':
dependencies:
- '@noble/ciphers': 1.3.0
+ '@emnapi/wasi-threads': 1.2.2
+ tslib: 2.8.1
+ optional: true
- '@emnapi/runtime@1.10.0':
+ '@emnapi/runtime@1.11.1':
dependencies:
tslib: 2.8.1
optional: true
- '@floating-ui/core@1.6.9':
+ '@emnapi/wasi-threads@1.2.2':
dependencies:
- '@floating-ui/utils': 0.2.9
+ tslib: 2.8.1
+ optional: true
'@floating-ui/core@1.7.5':
dependencies:
'@floating-ui/utils': 0.2.11
- '@floating-ui/dom@1.6.13':
- dependencies:
- '@floating-ui/core': 1.6.9
- '@floating-ui/utils': 0.2.9
-
'@floating-ui/dom@1.7.6':
dependencies:
'@floating-ui/core': 1.7.5
'@floating-ui/utils': 0.2.11
- '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@floating-ui/dom': 1.6.13
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
-
- '@floating-ui/react-dom@2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
'@floating-ui/dom': 1.7.6
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
'@floating-ui/utils@0.2.11': {}
- '@floating-ui/utils@0.2.9': {}
-
'@hexagon/base64@1.1.28': {}
- '@hono/node-server@1.19.13(hono@4.12.12)':
+ '@hono/node-server@1.19.14(hono@4.12.29)':
dependencies:
- hono: 4.12.12
-
- '@hookform/resolvers@3.10.0(react-hook-form@7.55.0(react@18.3.1))':
- dependencies:
- react-hook-form: 7.55.0(react@18.3.1)
+ hono: 4.12.29
'@img/colour@1.1.0':
optional: true
@@ -3921,7 +3834,7 @@ snapshots:
'@img/sharp-wasm32@0.34.5':
dependencies:
- '@emnapi/runtime': 1.10.0
+ '@emnapi/runtime': 1.11.1
optional: true
'@img/sharp-win32-arm64@0.34.5':
@@ -3933,34 +3846,6 @@ snapshots:
'@img/sharp-win32-x64@0.34.5':
optional: true
- '@inquirer/ansi@1.0.2': {}
-
- '@inquirer/confirm@5.1.21(@types/node@20.17.19)':
- dependencies:
- '@inquirer/core': 10.3.2(@types/node@20.17.19)
- '@inquirer/type': 3.0.10(@types/node@20.17.19)
- optionalDependencies:
- '@types/node': 20.17.19
-
- '@inquirer/core@10.3.2(@types/node@20.17.19)':
- dependencies:
- '@inquirer/ansi': 1.0.2
- '@inquirer/figures': 1.0.15
- '@inquirer/type': 3.0.10(@types/node@20.17.19)
- cli-width: 4.1.0
- mute-stream: 2.0.0
- signal-exit: 4.1.0
- wrap-ansi: 6.2.0
- yoctocolors-cjs: 2.1.3
- optionalDependencies:
- '@types/node': 20.17.19
-
- '@inquirer/figures@1.0.15': {}
-
- '@inquirer/type@3.0.10(@types/node@20.17.19)':
- optionalDependencies:
- '@types/node': 20.17.19
-
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -3984,17 +3869,17 @@ snapshots:
'@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)':
dependencies:
- '@hono/node-server': 1.19.13(hono@4.12.12)
- ajv: 8.18.0
- ajv-formats: 3.0.1(ajv@8.18.0)
+ '@hono/node-server': 1.19.14(hono@4.12.29)
+ ajv: 8.20.0
+ ajv-formats: 3.0.1(ajv@8.20.0)
content-type: 1.0.5
cors: 2.8.6
cross-spawn: 7.0.6
eventsource: 3.0.7
- eventsource-parser: 3.0.6
+ eventsource-parser: 3.1.0
express: 5.2.1
- express-rate-limit: 8.3.2(express@5.2.1)
- hono: 4.12.12
+ express-rate-limit: 8.5.2(express@5.2.1)
+ hono: 4.12.29
jose: 6.1.3
json-schema-typed: 8.0.2
pkce-challenge: 5.0.1
@@ -4008,16 +3893,15 @@ snapshots:
dependencies:
sparse-bitfield: 3.0.3
- '@mswjs/interceptors@0.41.3':
+ '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
dependencies:
- '@open-draft/deferred-promise': 2.2.0
- '@open-draft/logger': 0.3.0
- '@open-draft/until': 2.1.0
- is-node-process: 1.2.0
- outvariant: 1.4.3
- strict-event-emitter: 0.5.1
+ '@emnapi/core': 1.11.1
+ '@emnapi/runtime': 1.11.1
+ '@tybys/wasm-util': 0.10.3
+ optional: true
- '@next/env@15.5.18': {}
+ '@next/env@15.5.18':
+ optional: true
'@next/swc-darwin-arm64@15.5.18':
optional: true
@@ -4043,16 +3927,8 @@ snapshots:
'@next/swc-win32-x64-msvc@15.5.18':
optional: true
- '@noble/ciphers@1.3.0': {}
-
'@noble/ciphers@2.1.1': {}
- '@noble/curves@1.9.7':
- dependencies:
- '@noble/hashes': 1.8.0
-
- '@noble/hashes@1.8.0': {}
-
'@noble/hashes@2.0.1': {}
'@nodelib/fs.scandir@2.1.5':
@@ -4067,14 +3943,24 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.20.1
- '@open-draft/deferred-promise@2.2.0': {}
+ '@oozcitak/dom@2.0.2':
+ dependencies:
+ '@oozcitak/infra': 2.0.2
+ '@oozcitak/url': 3.0.0
+ '@oozcitak/util': 10.0.0
+
+ '@oozcitak/infra@2.0.2':
+ dependencies:
+ '@oozcitak/util': 10.0.0
- '@open-draft/logger@0.3.0':
+ '@oozcitak/url@3.0.0':
dependencies:
- is-node-process: 1.2.0
- outvariant: 1.4.3
+ '@oozcitak/infra': 2.0.2
+ '@oozcitak/util': 10.0.0
+
+ '@oozcitak/util@10.0.0': {}
- '@open-draft/until@2.1.0': {}
+ '@oxc-project/types@0.139.0': {}
'@peculiar/asn1-android@2.6.0':
dependencies:
@@ -4174,787 +4060,193 @@ snapshots:
'@polinetwork/backend@0.17.0': {}
- '@radix-ui/number@1.1.1': {}
+ '@radix-ui/primitive@1.1.6': {}
- '@radix-ui/primitive@1.1.3': {}
-
- '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-accordion@1.2.12(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-slot': 1.2.3(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-avatar@1.1.10(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-checkbox@1.3.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-collapsible@1.1.12(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-slot': 1.2.3(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
-
- '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.18)(react@18.3.1)':
+ '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.17)(react@19.2.7)':
dependencies:
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
-
- '@radix-ui/react-context-menu@2.2.16(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.2.7
optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@types/react': 19.2.17
- '@radix-ui/react-context@1.1.2(@types/react@18.3.18)(react@18.3.1)':
+ '@radix-ui/react-context@1.2.0(@types/react@19.2.17)(react@19.2.7)':
dependencies:
- react: 18.3.1
+ react: 19.2.7
optionalDependencies:
- '@types/react': 18.3.18
-
- '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-slot': 1.2.3(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- aria-hidden: 1.2.4
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- react-remove-scroll: 2.6.3(@types/react@18.3.18)(react@18.3.1)
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-dialog@1.1.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.6
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-dismissable-layer': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-focus-scope': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-portal': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-presence': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-controllable-state': 1.2.4(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ aria-hidden: 1.2.6
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7)
optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-direction@1.1.1(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- react: 18.3.1
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-dismissable-layer@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.6
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
optionalDependencies:
- '@types/react': 18.3.18
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
- '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.17)(react@19.2.7)':
dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.2.7
optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@types/react': 19.2.17
- '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.18)(react@18.3.1)':
+ '@radix-ui/react-focus-scope@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
- react: 18.3.1
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
optionalDependencies:
- '@types/react': 18.3.18
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
- '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-id@1.1.2(@types/react@19.2.17)(react@19.2.7)':
dependencies:
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-form@0.1.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-label': 2.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-hover-card@1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@types/react': 19.2.17
- '@radix-ui/react-id@1.1.0(@types/react@18.3.18)(react@18.3.1)':
+ '@radix-ui/react-portal@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
- '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
+ '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
optionalDependencies:
- '@types/react': 18.3.18
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
- '@radix-ui/react-id@1.1.1(@types/react@18.3.18)(react@18.3.1)':
+ '@radix-ui/react-presence@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
optionalDependencies:
- '@types/react': 18.3.18
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
- '@radix-ui/react-label@2.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-primitive@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-slot': 1.2.3(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- aria-hidden: 1.2.4
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- react-remove-scroll: 2.6.3(@types/react@18.3.18)(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-menubar@1.1.16(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/number': 1.1.1
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-slot': 1.2.3(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- aria-hidden: 1.2.4
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- react-remove-scroll: 2.6.3(@types/react@18.3.18)(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/rect': 1.1.1
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
- '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-slot@1.3.0(@types/react@19.2.17)(react@19.2.7)':
dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@types/react': 19.2.17
- '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.17)(react@19.2.7)':
dependencies:
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.2.7
optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@types/react': 19.2.17
- '@radix-ui/react-primitive@2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-use-controllable-state@1.2.4(@types/react@19.2.17)(react@19.2.7)':
dependencies:
- '@radix-ui/react-slot': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ '@radix-ui/primitive': 1.1.6
+ '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@types/react': 19.2.17
- '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.17)(react@19.2.7)':
dependencies:
- '@radix-ui/react-slot': 1.2.3(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@types/react': 19.2.17
- '@radix-ui/react-progress@1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.17)(react@19.2.7)':
dependencies:
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-radio-group@1.3.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.2.7
optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/number': 1.1.1
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/number': 1.1.1
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-slot': 1.2.3(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- aria-hidden: 1.2.4
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- react-remove-scroll: 2.6.3(@types/react@18.3.18)(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@types/react': 19.2.17
- '@radix-ui/react-separator@1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-slider@1.3.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/number': 1.1.1
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@rolldown/binding-android-arm64@1.1.5':
+ optional: true
- '@radix-ui/react-slot@1.1.2(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
+ '@rolldown/binding-darwin-arm64@1.1.5':
+ optional: true
- '@radix-ui/react-slot@1.2.3(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
-
- '@radix-ui/react-switch@1.2.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-toast@1.2.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-toggle': 1.1.10(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@rolldown/binding-darwin-x64@1.1.5':
+ optional: true
- '@radix-ui/react-toggle@1.1.10(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-toolbar@1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-separator': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-slot': 1.2.3(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@rolldown/binding-freebsd-x64@1.1.5':
+ optional: true
- '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
+ '@rolldown/binding-linux-arm-gnueabihf@1.1.5':
+ optional: true
- '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
+ '@rolldown/binding-linux-arm64-gnu@1.1.5':
+ optional: true
- '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
+ '@rolldown/binding-linux-arm64-musl@1.1.5':
+ optional: true
- '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
+ '@rolldown/binding-linux-ppc64-gnu@1.1.5':
+ optional: true
- '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- react: 18.3.1
- use-sync-external-store: 1.6.0(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
+ '@rolldown/binding-linux-s390x-gnu@1.1.5':
+ optional: true
- '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
+ '@rolldown/binding-linux-x64-gnu@1.1.5':
+ optional: true
- '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
+ '@rolldown/binding-linux-x64-musl@1.1.5':
+ optional: true
- '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
+ '@rolldown/binding-openharmony-arm64@1.1.5':
+ optional: true
- '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.18)(react@18.3.1)':
+ '@rolldown/binding-wasm32-wasi@1.1.5':
dependencies:
- '@radix-ui/rect': 1.1.1
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
+ '@emnapi/core': 1.11.1
+ '@emnapi/runtime': 1.11.1
+ '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
+ optional: true
- '@radix-ui/react-use-size@1.1.1(@types/react@18.3.18)(react@18.3.1)':
- dependencies:
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.18
+ '@rolldown/binding-win32-arm64-msvc@1.1.5':
+ optional: true
- '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
+ '@rolldown/binding-win32-x64-msvc@1.1.5':
+ optional: true
- '@radix-ui/rect@1.1.1': {}
+ '@rolldown/pluginutils@1.0.1': {}
'@sec-ant/readable-stream@0.4.1': {}
@@ -4978,134 +4270,326 @@ snapshots:
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
+ optional: true
- '@t3-oss/env-core@0.13.10(typescript@5.7.3)(zod@4.3.5)':
- optionalDependencies:
- typescript: 5.7.3
- zod: 4.3.5
-
- '@t3-oss/env-nextjs@0.13.10(typescript@5.7.3)(zod@4.3.5)':
- dependencies:
- '@t3-oss/env-core': 0.13.10(typescript@5.7.3)(zod@4.3.5)
+ '@t3-oss/env-core@0.13.11(typescript@6.0.3)(zod@4.3.5)':
optionalDependencies:
- typescript: 5.7.3
+ typescript: 6.0.3
zod: 4.3.5
- '@tabby_ai/hijri-converter@1.0.5': {}
-
- '@tailwindcss/node@4.1.4':
+ '@tailwindcss/node@4.3.2':
dependencies:
- enhanced-resolve: 5.18.1
- jiti: 2.4.2
- lightningcss: 1.29.2
- tailwindcss: 4.1.4
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.21.6
+ jiti: 2.7.0
+ lightningcss: 1.32.0
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.3.2
- '@tailwindcss/oxide-android-arm64@4.1.4':
+ '@tailwindcss/oxide-android-arm64@4.3.2':
optional: true
- '@tailwindcss/oxide-darwin-arm64@4.1.4':
+ '@tailwindcss/oxide-darwin-arm64@4.3.2':
optional: true
- '@tailwindcss/oxide-darwin-x64@4.1.4':
+ '@tailwindcss/oxide-darwin-x64@4.3.2':
optional: true
- '@tailwindcss/oxide-freebsd-x64@4.1.4':
+ '@tailwindcss/oxide-freebsd-x64@4.3.2':
optional: true
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.4':
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.2':
optional: true
- '@tailwindcss/oxide-linux-arm64-gnu@4.1.4':
+ '@tailwindcss/oxide-linux-arm64-gnu@4.3.2':
optional: true
- '@tailwindcss/oxide-linux-arm64-musl@4.1.4':
+ '@tailwindcss/oxide-linux-arm64-musl@4.3.2':
optional: true
- '@tailwindcss/oxide-linux-x64-gnu@4.1.4':
+ '@tailwindcss/oxide-linux-x64-gnu@4.3.2':
optional: true
- '@tailwindcss/oxide-linux-x64-musl@4.1.4':
+ '@tailwindcss/oxide-linux-x64-musl@4.3.2':
optional: true
- '@tailwindcss/oxide-wasm32-wasi@4.1.4':
+ '@tailwindcss/oxide-wasm32-wasi@4.3.2':
optional: true
- '@tailwindcss/oxide-win32-arm64-msvc@4.1.4':
+ '@tailwindcss/oxide-win32-arm64-msvc@4.3.2':
optional: true
- '@tailwindcss/oxide-win32-x64-msvc@4.1.4':
+ '@tailwindcss/oxide-win32-x64-msvc@4.3.2':
optional: true
- '@tailwindcss/oxide@4.1.4':
+ '@tailwindcss/oxide@4.3.2':
optionalDependencies:
- '@tailwindcss/oxide-android-arm64': 4.1.4
- '@tailwindcss/oxide-darwin-arm64': 4.1.4
- '@tailwindcss/oxide-darwin-x64': 4.1.4
- '@tailwindcss/oxide-freebsd-x64': 4.1.4
- '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.4
- '@tailwindcss/oxide-linux-arm64-gnu': 4.1.4
- '@tailwindcss/oxide-linux-arm64-musl': 4.1.4
- '@tailwindcss/oxide-linux-x64-gnu': 4.1.4
- '@tailwindcss/oxide-linux-x64-musl': 4.1.4
- '@tailwindcss/oxide-wasm32-wasi': 4.1.4
- '@tailwindcss/oxide-win32-arm64-msvc': 4.1.4
- '@tailwindcss/oxide-win32-x64-msvc': 4.1.4
-
- '@tailwindcss/postcss@4.1.4':
- dependencies:
- '@alloc/quick-lru': 5.2.0
- '@tailwindcss/node': 4.1.4
- '@tailwindcss/oxide': 4.1.4
- postcss: 8.5.2
- tailwindcss: 4.1.4
-
- '@tanstack/query-core@5.90.19':
- optional: true
+ '@tailwindcss/oxide-android-arm64': 4.3.2
+ '@tailwindcss/oxide-darwin-arm64': 4.3.2
+ '@tailwindcss/oxide-darwin-x64': 4.3.2
+ '@tailwindcss/oxide-freebsd-x64': 4.3.2
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.2
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.3.2
+ '@tailwindcss/oxide-linux-arm64-musl': 4.3.2
+ '@tailwindcss/oxide-linux-x64-gnu': 4.3.2
+ '@tailwindcss/oxide-linux-x64-musl': 4.3.2
+ '@tailwindcss/oxide-wasm32-wasi': 4.3.2
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.3.2
+ '@tailwindcss/oxide-win32-x64-msvc': 4.3.2
+
+ '@tailwindcss/vite@4.3.2(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))':
+ dependencies:
+ '@tailwindcss/node': 4.3.2
+ '@tailwindcss/oxide': 4.3.2
+ tailwindcss: 4.3.2
+ vite: 8.1.4(@types/node@22.20.1)(jiti@2.7.0)
+
+ '@tanstack/history@1.162.0': {}
+
+ '@tanstack/react-router@1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@tanstack/history': 1.162.0
+ '@tanstack/react-store': 0.9.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@tanstack/router-core': 1.171.14
+ isbot: 5.2.0
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+
+ '@tanstack/react-start-client@1.168.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@tanstack/react-router': 1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@tanstack/router-core': 1.171.14
+ '@tanstack/start-client-core': 1.170.13
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+
+ '@tanstack/react-start-rsc@0.1.26(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))':
+ dependencies:
+ '@tanstack/react-router': 1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@tanstack/router-core': 1.171.14
+ '@tanstack/router-utils': 1.162.2
+ '@tanstack/start-client-core': 1.170.13
+ '@tanstack/start-fn-stubs': 1.162.0
+ '@tanstack/start-plugin-core': 1.171.19(@tanstack/react-router@1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(crossws@0.4.9(srvx@0.11.21))(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))
+ '@tanstack/start-server-core': 1.169.16(crossws@0.4.9(srvx@0.11.21))
+ '@tanstack/start-storage-context': 1.167.16
+ pathe: 2.0.3
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ transitivePeerDependencies:
+ - '@farmfe/core'
+ - '@rsbuild/core'
+ - bun-types-no-globals
+ - crossws
+ - esbuild
+ - rolldown
+ - rollup
+ - supports-color
+ - unloader
+ - vite
+ - vite-plugin-solid
+ - webpack
+
+ '@tanstack/react-start-server@1.167.21(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@tanstack/react-router': 1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@tanstack/router-core': 1.171.14
+ '@tanstack/start-server-core': 1.169.16(crossws@0.4.9(srvx@0.11.21))
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ transitivePeerDependencies:
+ - crossws
+
+ '@tanstack/react-start@1.168.27(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))':
+ dependencies:
+ '@tanstack/react-router': 1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@tanstack/react-start-client': 1.168.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@tanstack/react-start-rsc': 0.1.26(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))
+ '@tanstack/react-start-server': 1.167.21(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@tanstack/router-utils': 1.162.2
+ '@tanstack/start-client-core': 1.170.13
+ '@tanstack/start-plugin-core': 1.171.19(@tanstack/react-router@1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(crossws@0.4.9(srvx@0.11.21))(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))
+ '@tanstack/start-server-core': 1.169.16(crossws@0.4.9(srvx@0.11.21))
+ pathe: 2.0.3
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ vite: 8.1.4(@types/node@22.20.1)(jiti@2.7.0)
+ transitivePeerDependencies:
+ - '@farmfe/core'
+ - '@rspack/core'
+ - bun-types-no-globals
+ - crossws
+ - esbuild
+ - react-server-dom-rspack
+ - rolldown
+ - rollup
+ - supports-color
+ - unloader
+ - vite-plugin-solid
+ - webpack
- '@tanstack/react-query@5.90.19(react@18.3.1)':
+ '@tanstack/react-store@0.11.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
- '@tanstack/query-core': 5.90.19
- react: 18.3.1
- optional: true
+ '@tanstack/store': 0.11.0
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ use-sync-external-store: 1.6.0(react@19.2.7)
+
+ '@tanstack/react-store@0.9.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@tanstack/store': 0.9.3
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ use-sync-external-store: 1.6.0(react@19.2.7)
- '@tanstack/react-table@8.21.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@tanstack/react-table@9.0.0-beta.38(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
- '@tanstack/table-core': 8.21.2
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ '@tanstack/react-store': 0.11.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@tanstack/table-core': 9.0.0-beta.38
+ react: 19.2.7
+ transitivePeerDependencies:
+ - react-dom
- '@tanstack/table-core@8.21.2': {}
+ '@tanstack/router-core@1.171.14':
+ dependencies:
+ '@tanstack/history': 1.162.0
+ cookie-es: 3.1.1
+ seroval: 1.5.5
+ seroval-plugins: 1.5.5(seroval@1.5.5)
- '@trpc/client@11.5.1(@trpc/server@11.5.1(typescript@5.7.3))(typescript@5.7.3)':
+ '@tanstack/router-generator@1.167.18':
dependencies:
- '@trpc/server': 11.5.1(typescript@5.7.3)
- typescript: 5.7.3
+ '@babel/types': 7.29.7
+ '@tanstack/router-core': 1.171.14
+ '@tanstack/router-utils': 1.162.2
+ '@tanstack/virtual-file-routes': 1.162.0
+ jiti: 2.7.0
+ magic-string: 0.30.21
+ prettier: 3.9.5
+ zod: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
- '@trpc/next@11.5.1(@tanstack/react-query@5.90.19(react@18.3.1))(@trpc/client@11.5.1(@trpc/server@11.5.1(typescript@5.7.3))(typescript@5.7.3))(@trpc/react-query@11.5.1(@tanstack/react-query@5.90.19(react@18.3.1))(@trpc/client@11.5.1(@trpc/server@11.5.1(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.5.1(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3))(@trpc/server@11.5.1(typescript@5.7.3))(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)':
+ '@tanstack/router-plugin@1.168.19(@tanstack/react-router@1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))':
dependencies:
- '@trpc/client': 11.5.1(@trpc/server@11.5.1(typescript@5.7.3))(typescript@5.7.3)
- '@trpc/server': 11.5.1(typescript@5.7.3)
- next: 15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- typescript: 5.7.3
+ '@babel/core': 7.29.0
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.7
+ '@tanstack/router-core': 1.171.14
+ '@tanstack/router-generator': 1.167.18
+ '@tanstack/router-utils': 1.162.2
+ chokidar: 5.0.0
+ unplugin: 3.3.0(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))
+ zod: 4.4.3
optionalDependencies:
- '@tanstack/react-query': 5.90.19(react@18.3.1)
- '@trpc/react-query': 11.5.1(@tanstack/react-query@5.90.19(react@18.3.1))(@trpc/client@11.5.1(@trpc/server@11.5.1(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.5.1(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)
+ '@tanstack/react-router': 1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ vite: 8.1.4(@types/node@22.20.1)(jiti@2.7.0)
+ transitivePeerDependencies:
+ - '@farmfe/core'
+ - '@rspack/core'
+ - bun-types-no-globals
+ - esbuild
+ - rolldown
+ - rollup
+ - supports-color
+ - unloader
- '@trpc/react-query@11.5.1(@tanstack/react-query@5.90.19(react@18.3.1))(@trpc/client@11.5.1(@trpc/server@11.5.1(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.5.1(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)':
+ '@tanstack/router-utils@1.162.2':
dependencies:
- '@tanstack/react-query': 5.90.19(react@18.3.1)
- '@trpc/client': 11.5.1(@trpc/server@11.5.1(typescript@5.7.3))(typescript@5.7.3)
- '@trpc/server': 11.5.1(typescript@5.7.3)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- typescript: 5.7.3
- optional: true
+ '@babel/generator': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+ ansis: 4.3.1
+ babel-dead-code-elimination: 1.0.12
+ diff: 8.0.4
+ pathe: 2.0.3
+ tinyglobby: 0.2.17
+ transitivePeerDependencies:
+ - supports-color
+
+ '@tanstack/start-client-core@1.170.13':
+ dependencies:
+ '@tanstack/router-core': 1.171.14
+ '@tanstack/start-fn-stubs': 1.162.0
+ '@tanstack/start-storage-context': 1.167.16
+ seroval: 1.5.5
+
+ '@tanstack/start-fn-stubs@1.162.0': {}
+
+ '@tanstack/start-plugin-core@1.171.19(@tanstack/react-router@1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(crossws@0.4.9(srvx@0.11.21))(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/types': 7.29.7
+ '@tanstack/router-core': 1.171.14
+ '@tanstack/router-generator': 1.167.18
+ '@tanstack/router-plugin': 1.168.19(@tanstack/react-router@1.170.17(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))
+ '@tanstack/router-utils': 1.162.2
+ '@tanstack/start-server-core': 1.169.16(crossws@0.4.9(srvx@0.11.21))
+ exsolve: 1.1.0
+ lightningcss: 1.32.0
+ pathe: 2.0.3
+ picomatch: 4.0.5
+ seroval: 1.5.5
+ source-map: 0.7.6
+ srvx: 0.11.21
+ tinyglobby: 0.2.17
+ ufo: 1.6.4
+ vitefu: 1.1.3(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))
+ xmlbuilder2: 4.0.3
+ zod: 4.4.3
+ optionalDependencies:
+ vite: 8.1.4(@types/node@22.20.1)(jiti@2.7.0)
+ transitivePeerDependencies:
+ - '@farmfe/core'
+ - '@rspack/core'
+ - '@tanstack/react-router'
+ - bun-types-no-globals
+ - crossws
+ - esbuild
+ - rolldown
+ - rollup
+ - supports-color
+ - unloader
+ - vite-plugin-solid
+ - webpack
+
+ '@tanstack/start-server-core@1.169.16(crossws@0.4.9(srvx@0.11.21))':
+ dependencies:
+ '@tanstack/history': 1.162.0
+ '@tanstack/router-core': 1.171.14
+ '@tanstack/start-client-core': 1.170.13
+ '@tanstack/start-storage-context': 1.167.16
+ fetchdts: 0.1.7
+ h3-v2: h3@2.0.1-rc.20(crossws@0.4.9(srvx@0.11.21))
+ seroval: 1.5.5
+ transitivePeerDependencies:
+ - crossws
- '@trpc/server@11.5.1(typescript@5.7.3)':
+ '@tanstack/start-storage-context@1.167.16':
dependencies:
- typescript: 5.7.3
+ '@tanstack/router-core': 1.171.14
+
+ '@tanstack/store@0.11.0': {}
+
+ '@tanstack/store@0.9.3': {}
+
+ '@tanstack/table-core@9.0.0-beta.38':
+ dependencies:
+ '@tanstack/store': 0.11.0
+
+ '@tanstack/virtual-file-routes@1.162.0': {}
+
+ '@trpc/client@11.5.1(@trpc/server@11.5.1(typescript@6.0.3))(typescript@6.0.3)':
+ dependencies:
+ '@trpc/server': 11.5.1(typescript@6.0.3)
+ typescript: 6.0.3
+
+ '@trpc/server@11.5.1(typescript@6.0.3)':
+ dependencies:
+ typescript: 6.0.3
'@ts-morph/common@0.27.0':
dependencies:
@@ -5113,24 +4597,22 @@ snapshots:
minimatch: 10.2.5
path-browserify: 1.0.1
- '@types/node@20.17.19':
+ '@tybys/wasm-util@0.10.3':
dependencies:
- undici-types: 6.19.8
-
- '@types/prismjs@1.26.5': {}
-
- '@types/prop-types@15.7.14': {}
+ tslib: 2.8.1
+ optional: true
- '@types/react-dom@18.3.5(@types/react@18.3.18)':
+ '@types/node@22.20.1':
dependencies:
- '@types/react': 18.3.18
+ undici-types: 6.21.0
- '@types/react@18.3.18':
+ '@types/react-dom@19.2.3(@types/react@19.2.17)':
dependencies:
- '@types/prop-types': 15.7.14
- csstype: 3.1.3
+ '@types/react': 19.2.17
- '@types/statuses@2.0.6': {}
+ '@types/react@19.2.17':
+ dependencies:
+ csstype: 3.2.3
'@types/validate-npm-package-name@4.0.2': {}
@@ -5140,35 +4622,44 @@ snapshots:
dependencies:
'@types/webidl-conversions': 7.0.3
+ '@vitejs/plugin-react@6.0.3(babel-plugin-react-compiler@1.0.0)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))':
+ dependencies:
+ '@rolldown/pluginutils': 1.0.1
+ vite: 8.1.4(@types/node@22.20.1)(jiti@2.7.0)
+ optionalDependencies:
+ babel-plugin-react-compiler: 1.0.0
+
accepts@2.0.0:
dependencies:
mime-types: 3.0.2
negotiator: 1.0.0
- agent-base@7.1.4: {}
+ ajv-formats@2.1.1(ajv@8.20.0):
+ optionalDependencies:
+ ajv: 8.20.0
- ajv-formats@3.0.1(ajv@8.18.0):
+ ajv-formats@3.0.1(ajv@8.20.0):
optionalDependencies:
- ajv: 8.18.0
+ ajv: 8.20.0
- ajv@8.18.0:
+ ajv@8.20.0:
dependencies:
fast-deep-equal: 3.1.3
- fast-uri: 3.1.0
+ fast-uri: 3.1.3
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
+ ansi-colors@4.1.3: {}
+
ansi-regex@5.0.1: {}
ansi-regex@6.2.2: {}
- ansi-styles@4.3.0:
- dependencies:
- color-convert: 2.0.1
+ ansis@4.3.1: {}
argparse@2.0.1: {}
- aria-hidden@1.2.4:
+ aria-hidden@1.2.6:
dependencies:
tslib: 2.8.1
@@ -5182,15 +4673,27 @@ snapshots:
dependencies:
tslib: 2.8.1
+ atomically@1.7.0: {}
+
+ babel-dead-code-elimination@1.0.12:
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/parser': 7.29.7
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
babel-plugin-react-compiler@1.0.0:
dependencies:
- '@babel/types': 7.28.2
+ '@babel/types': 7.29.7
+ optional: true
balanced-match@4.0.4: {}
baseline-browser-mapping@2.10.16: {}
- better-auth@1.5.5(mongodb@7.1.0)(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ better-auth@1.5.5(@tanstack/react-start@1.168.27(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0)))(mongodb@7.1.0)(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(solid-js@1.9.14):
dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0)
'@better-auth/drizzle-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)
@@ -5210,10 +4713,12 @@ snapshots:
nanostores: 1.2.0
zod: 4.3.6
optionalDependencies:
+ '@tanstack/react-start': 1.168.27(crossws@0.4.9(srvx@0.11.21))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0))
mongodb: 7.1.0
- next: 15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ next: 15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ solid-js: 1.9.14
transitivePeerDependencies:
- '@cloudflare/workers-types'
@@ -5226,21 +4731,21 @@ snapshots:
optionalDependencies:
zod: 4.3.5
- body-parser@2.2.2:
+ body-parser@2.3.0:
dependencies:
bytes: 3.1.2
- content-type: 1.0.5
+ content-type: 2.0.0
debug: 4.4.3
http-errors: 2.0.1
- iconv-lite: 0.7.2
+ iconv-lite: 0.7.3
on-finished: 2.4.1
- qs: 6.15.1
+ qs: 6.15.3
raw-body: 3.0.2
- type-is: 2.0.1
+ type-is: 2.1.0
transitivePeerDependencies:
- supports-color
- brace-expansion@5.0.5:
+ brace-expansion@5.0.7:
dependencies:
balanced-match: 4.0.4
@@ -5280,6 +4785,10 @@ snapshots:
chalk@5.6.2: {}
+ chokidar@5.0.0:
+ dependencies:
+ readdirp: 5.0.0
+
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
@@ -5290,54 +4799,58 @@ snapshots:
cli-spinners@2.9.2: {}
- cli-width@4.1.0: {}
-
- client-only@0.0.1: {}
-
- cliui@8.0.1:
- dependencies:
- string-width: 4.2.3
- strip-ansi: 6.0.1
- wrap-ansi: 7.0.0
+ client-only@0.0.1:
+ optional: true
clsx@2.1.1: {}
- cmdk@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
dependencies:
- '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-dialog': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
code-block-writer@13.0.3: {}
- color-convert@2.0.1:
- dependencies:
- color-name: 1.1.4
-
- color-name@1.1.4: {}
-
commander@11.1.0: {}
commander@14.0.3: {}
+ conf@10.2.0:
+ dependencies:
+ ajv: 8.20.0
+ ajv-formats: 2.1.1(ajv@8.20.0)
+ atomically: 1.7.0
+ debounce-fn: 4.0.0
+ dot-prop: 6.0.1
+ env-paths: 2.2.1
+ json-schema-typed: 7.0.3
+ onetime: 5.1.2
+ pkg-up: 3.1.0
+ semver: 7.8.2
+
+ consola@3.4.2: {}
+
content-disposition@1.1.0: {}
content-type@1.0.5: {}
+ content-type@2.0.0: {}
+
convert-source-map@2.0.0: {}
+ cookie-es@3.1.1: {}
+
cookie-signature@1.2.2: {}
cookie@0.7.2: {}
- cookie@1.1.1: {}
-
copy-anything@3.0.5:
dependencies:
is-what: 4.1.16
@@ -5347,14 +4860,14 @@ snapshots:
object-assign: 4.1.1
vary: 1.1.2
- cosmiconfig@9.0.1(typescript@5.7.3):
+ cosmiconfig@9.0.2(typescript@6.0.3):
dependencies:
env-paths: 2.2.1
import-fresh: 3.3.1
js-yaml: 4.1.1
parse-json: 5.2.0
optionalDependencies:
- typescript: 5.7.3
+ typescript: 6.0.3
cross-spawn@7.0.6:
dependencies:
@@ -5362,15 +4875,21 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
+ crossws@0.4.9(srvx@0.11.21):
+ optionalDependencies:
+ srvx: 0.11.21
+
cssesc@3.0.0: {}
- csstype@3.1.3: {}
+ csstype@3.2.3: {}
- data-uri-to-buffer@4.0.1: {}
+ date-fns@4.4.0: {}
- date-fns-jalali@4.1.0-0: {}
+ db0@0.3.4: {}
- date-fns@4.1.0: {}
+ debounce-fn@4.0.0:
+ dependencies:
+ mimic-fn: 3.1.0
debug@4.4.3:
dependencies:
@@ -5387,6 +4906,8 @@ snapshots:
bundle-name: 4.1.0
default-browser-id: 5.0.1
+ define-lazy-prop@2.0.0: {}
+
define-lazy-prop@3.0.0: {}
defu@6.1.4: {}
@@ -5399,7 +4920,11 @@ snapshots:
diff@8.0.4: {}
- dotenv@17.4.1: {}
+ dot-prop@6.0.1:
+ dependencies:
+ is-obj: 2.0.0
+
+ dotenv@17.4.2: {}
dunder-proto@1.0.1:
dependencies:
@@ -5407,30 +4932,33 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
- eciesjs@0.4.18:
- dependencies:
- '@ecies/ciphers': 0.2.6(@noble/ciphers@1.3.0)
- '@noble/ciphers': 1.3.0
- '@noble/curves': 1.9.7
- '@noble/hashes': 1.8.0
-
ee-first@1.1.1: {}
electron-to-chromium@1.5.334: {}
emoji-regex@10.6.0: {}
- emoji-regex@8.0.0: {}
-
encodeurl@2.0.0: {}
- enhanced-resolve@5.18.1:
+ enhanced-resolve@5.21.6:
dependencies:
graceful-fs: 4.2.11
- tapable: 2.2.1
+ tapable: 2.3.3
+
+ enquirer@2.4.1:
+ dependencies:
+ ansi-colors: 4.1.3
+ strip-ansi: 6.0.1
env-paths@2.2.1: {}
+ env-runner@0.1.16:
+ dependencies:
+ crossws: 0.4.9(srvx@0.11.21)
+ exsolve: 1.1.0
+ httpxy: 0.5.5
+ srvx: 0.11.21
+
error-ex@1.3.4:
dependencies:
is-arrayish: 0.2.1
@@ -5439,7 +4967,7 @@ snapshots:
es-errors@1.3.0: {}
- es-object-atoms@1.1.1:
+ es-object-atoms@1.1.2:
dependencies:
es-errors: 1.3.0
@@ -5451,11 +4979,11 @@ snapshots:
etag@1.8.1: {}
- eventsource-parser@3.0.6: {}
+ eventsource-parser@3.1.0: {}
eventsource@3.0.7:
dependencies:
- eventsource-parser: 3.0.6
+ eventsource-parser: 3.1.0
execa@5.1.1:
dependencies:
@@ -5484,15 +5012,15 @@ snapshots:
strip-final-newline: 4.0.0
yoctocolors: 2.1.2
- express-rate-limit@8.3.2(express@5.2.1):
+ express-rate-limit@8.5.2(express@5.2.1):
dependencies:
express: 5.2.1
- ip-address: 10.1.0
+ ip-address: 10.2.0
express@5.2.1:
dependencies:
accepts: 2.0.0
- body-parser: 2.2.2
+ body-parser: 2.3.0
content-disposition: 1.1.0
content-type: 1.0.5
cookie: 0.7.2
@@ -5511,17 +5039,19 @@ snapshots:
once: 1.4.0
parseurl: 1.3.3
proxy-addr: 2.0.7
- qs: 6.15.1
- range-parser: 1.2.1
+ qs: 6.15.3
+ range-parser: 1.3.0
router: 2.2.0
send: 1.2.1
serve-static: 2.2.1
statuses: 2.0.2
- type-is: 2.0.1
+ type-is: 2.1.0
vary: 1.1.2
transitivePeerDependencies:
- supports-color
+ exsolve@1.1.0: {}
+
fast-deep-equal@3.1.3: {}
fast-glob@3.3.3:
@@ -5532,20 +5062,17 @@ snapshots:
merge2: 1.4.1
micromatch: 4.0.8
- fast-uri@3.1.0: {}
+ fast-uri@3.1.3: {}
fastq@1.20.1:
dependencies:
reusify: 1.1.0
- fdir@6.5.0(picomatch@4.0.4):
+ fdir@6.5.0(picomatch@4.0.5):
optionalDependencies:
- picomatch: 4.0.4
+ picomatch: 4.0.5
- fetch-blob@3.2.0:
- dependencies:
- node-domexception: 1.0.0
- web-streams-polyfill: 3.3.3
+ fetchdts@0.1.7: {}
figures@6.1.0:
dependencies:
@@ -5566,45 +5093,51 @@ snapshots:
transitivePeerDependencies:
- supports-color
- formdata-polyfill@4.0.10:
+ find-up@3.0.0:
dependencies:
- fetch-blob: 3.2.0
+ locate-path: 3.0.0
forwarded@0.2.0: {}
+ framer-motion@12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
+ dependencies:
+ motion-dom: 12.42.2
+ motion-utils: 12.39.0
+ tslib: 2.8.1
+ optionalDependencies:
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+
fresh@2.0.0: {}
- fs-extra@11.3.4:
+ fs-extra@11.3.6:
dependencies:
graceful-fs: 4.2.11
- jsonfile: 6.2.0
+ jsonfile: 6.2.1
universalify: 2.0.1
+ fsevents@2.3.3:
+ optional: true
+
function-bind@1.1.2: {}
fuzzysort@3.1.0: {}
- geist@1.3.1(next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
- dependencies:
- next: 15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-
gensync@1.0.0-beta.2: {}
- get-caller-file@2.0.5: {}
-
- get-east-asian-width@1.5.0: {}
+ get-east-asian-width@1.6.0: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
es-define-property: 1.0.1
es-errors: 1.3.0
- es-object-atoms: 1.1.1
+ es-object-atoms: 1.1.2
function-bind: 1.1.2
get-proto: 1.0.1
gopd: 1.2.0
has-symbols: 1.1.0
- hasown: 2.0.2
+ hasown: 2.0.4
math-intrinsics: 1.1.0
get-nonce@1.0.1: {}
@@ -5614,7 +5147,7 @@ snapshots:
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
- es-object-atoms: 1.1.1
+ es-object-atoms: 1.1.2
get-stream@6.0.1: {}
@@ -5631,17 +5164,29 @@ snapshots:
graceful-fs@4.2.11: {}
- graphql@16.13.2: {}
+ h3@2.0.1-rc.20(crossws@0.4.9(srvx@0.11.21)):
+ dependencies:
+ rou3: 0.8.1
+ srvx: 0.11.21
+ optionalDependencies:
+ crossws: 0.4.9(srvx@0.11.21)
+
+ h3@2.0.1-rc.22(crossws@0.4.9(srvx@0.11.21)):
+ dependencies:
+ rou3: 0.8.1
+ srvx: 0.11.21
+ optionalDependencies:
+ crossws: 0.4.9(srvx@0.11.21)
has-symbols@1.1.0: {}
- hasown@2.0.2:
+ hasown@2.0.4:
dependencies:
function-bind: 1.1.2
- headers-polyfill@4.0.3: {}
+ hono@4.12.29: {}
- hono@4.12.12: {}
+ hookable@6.1.1: {}
http-errors@2.0.1:
dependencies:
@@ -5651,18 +5196,13 @@ snapshots:
statuses: 2.0.2
toidentifier: 1.0.1
- https-proxy-agent@7.0.6:
- dependencies:
- agent-base: 7.1.4
- debug: 4.4.3
- transitivePeerDependencies:
- - supports-color
+ httpxy@0.5.5: {}
human-signals@2.1.0: {}
human-signals@8.0.1: {}
- iconv-lite@0.7.2:
+ iconv-lite@0.7.3:
dependencies:
safer-buffer: 2.1.2
@@ -5675,23 +5215,18 @@ snapshots:
inherits@2.0.4: {}
- input-otp@1.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
- dependencies:
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
-
- ip-address@10.1.0: {}
+ ip-address@10.2.0: {}
ipaddr.js@1.9.1: {}
is-arrayish@0.2.1: {}
+ is-docker@2.2.1: {}
+
is-docker@3.0.0: {}
is-extglob@2.1.1: {}
- is-fullwidth-code-point@3.0.0: {}
-
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
@@ -5704,10 +5239,10 @@ snapshots:
is-interactive@2.0.0: {}
- is-node-process@1.2.0: {}
-
is-number@7.0.0: {}
+ is-obj@2.0.0: {}
+
is-obj@3.0.0: {}
is-plain-obj@4.1.0: {}
@@ -5726,15 +5261,21 @@ snapshots:
is-what@4.1.16: {}
+ is-wsl@2.2.0:
+ dependencies:
+ is-docker: 2.2.1
+
is-wsl@3.1.1:
dependencies:
is-inside-container: 1.0.0
+ isbot@5.2.0: {}
+
isexe@2.0.0: {}
isexe@3.1.5: {}
- jiti@2.4.2: {}
+ jiti@2.7.0: {}
jose@6.1.3: {}
@@ -5750,11 +5291,13 @@ snapshots:
json-schema-traverse@1.0.0: {}
+ json-schema-typed@7.0.3: {}
+
json-schema-typed@8.0.2: {}
json5@2.2.3: {}
- jsonfile@6.2.0:
+ jsonfile@6.2.1:
dependencies:
universalify: 2.0.1
optionalDependencies:
@@ -5766,66 +5309,79 @@ snapshots:
kysely@0.28.12: {}
- lightningcss-darwin-arm64@1.29.2:
+ lightningcss-android-arm64@1.32.0:
+ optional: true
+
+ lightningcss-darwin-arm64@1.32.0:
optional: true
- lightningcss-darwin-x64@1.29.2:
+ lightningcss-darwin-x64@1.32.0:
optional: true
- lightningcss-freebsd-x64@1.29.2:
+ lightningcss-freebsd-x64@1.32.0:
optional: true
- lightningcss-linux-arm-gnueabihf@1.29.2:
+ lightningcss-linux-arm-gnueabihf@1.32.0:
optional: true
- lightningcss-linux-arm64-gnu@1.29.2:
+ lightningcss-linux-arm64-gnu@1.32.0:
optional: true
- lightningcss-linux-arm64-musl@1.29.2:
+ lightningcss-linux-arm64-musl@1.32.0:
optional: true
- lightningcss-linux-x64-gnu@1.29.2:
+ lightningcss-linux-x64-gnu@1.32.0:
optional: true
- lightningcss-linux-x64-musl@1.29.2:
+ lightningcss-linux-x64-musl@1.32.0:
optional: true
- lightningcss-win32-arm64-msvc@1.29.2:
+ lightningcss-win32-arm64-msvc@1.32.0:
optional: true
- lightningcss-win32-x64-msvc@1.29.2:
+ lightningcss-win32-x64-msvc@1.32.0:
optional: true
- lightningcss@1.29.2:
+ lightningcss@1.32.0:
dependencies:
detect-libc: 2.1.2
optionalDependencies:
- lightningcss-darwin-arm64: 1.29.2
- lightningcss-darwin-x64: 1.29.2
- lightningcss-freebsd-x64: 1.29.2
- lightningcss-linux-arm-gnueabihf: 1.29.2
- lightningcss-linux-arm64-gnu: 1.29.2
- lightningcss-linux-arm64-musl: 1.29.2
- lightningcss-linux-x64-gnu: 1.29.2
- lightningcss-linux-x64-musl: 1.29.2
- lightningcss-win32-arm64-msvc: 1.29.2
- lightningcss-win32-x64-msvc: 1.29.2
+ lightningcss-android-arm64: 1.32.0
+ lightningcss-darwin-arm64: 1.32.0
+ lightningcss-darwin-x64: 1.32.0
+ lightningcss-freebsd-x64: 1.32.0
+ lightningcss-linux-arm-gnueabihf: 1.32.0
+ lightningcss-linux-arm64-gnu: 1.32.0
+ lightningcss-linux-arm64-musl: 1.32.0
+ lightningcss-linux-x64-gnu: 1.32.0
+ lightningcss-linux-x64-musl: 1.32.0
+ lightningcss-win32-arm64-msvc: 1.32.0
+ lightningcss-win32-x64-msvc: 1.32.0
lines-and-columns@1.2.4: {}
+ locate-path@3.0.0:
+ dependencies:
+ p-locate: 3.0.0
+ path-exists: 3.0.0
+
log-symbols@6.0.0:
dependencies:
chalk: 5.6.2
is-unicode-supported: 1.3.0
- loose-envify@1.4.0:
- dependencies:
- js-tokens: 4.0.0
-
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
+ lucide-react@0.545.0(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
math-intrinsics@1.1.0: {}
media-typer@1.1.0: {}
@@ -5851,11 +5407,13 @@ snapshots:
mimic-fn@2.1.0: {}
+ mimic-fn@3.1.0: {}
+
mimic-function@5.0.1: {}
minimatch@10.2.5:
dependencies:
- brace-expansion: 5.0.5
+ brace-expansion: 5.0.7
minimist@1.2.8: {}
@@ -5870,57 +5428,37 @@ snapshots:
bson: 7.2.0
mongodb-connection-string-url: 7.0.1
- ms@2.1.3: {}
+ motion-dom@12.42.2:
+ dependencies:
+ motion-utils: 12.39.0
- msw@2.13.2(@types/node@20.17.19)(typescript@5.7.3):
- dependencies:
- '@inquirer/confirm': 5.1.21(@types/node@20.17.19)
- '@mswjs/interceptors': 0.41.3
- '@open-draft/deferred-promise': 2.2.0
- '@types/statuses': 2.0.6
- cookie: 1.1.1
- graphql: 16.13.2
- headers-polyfill: 4.0.3
- is-node-process: 1.2.0
- outvariant: 1.4.3
- path-to-regexp: 6.3.0
- picocolors: 1.1.1
- rettime: 0.10.1
- statuses: 2.0.2
- strict-event-emitter: 0.5.1
- tough-cookie: 6.0.1
- type-fest: 5.5.0
- until-async: 3.0.2
- yargs: 17.7.2
+ motion-utils@12.39.0: {}
+
+ motion@12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
+ dependencies:
+ framer-motion: 12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ tslib: 2.8.1
optionalDependencies:
- typescript: 5.7.3
- transitivePeerDependencies:
- - '@types/node'
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
- mute-stream@2.0.0: {}
+ ms@2.1.3: {}
nanoid@3.3.12: {}
- nanoid@3.3.8: {}
-
nanostores@1.2.0: {}
negotiator@1.0.0: {}
- next-themes@0.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
- dependencies:
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
-
- next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ next@15.5.18(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
dependencies:
'@next/env': 15.5.18
'@swc/helpers': 0.5.15
caniuse-lite: 1.0.30001797
postcss: 8.4.31
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- styled-jsx: 5.1.6(@babel/core@7.29.0)(react@18.3.1)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.7)
optionalDependencies:
'@next/swc-darwin-arm64': 15.5.18
'@next/swc-darwin-x64': 15.5.18
@@ -5935,14 +5473,61 @@ snapshots:
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
+ optional: true
- node-domexception@1.0.0: {}
-
- node-fetch@3.3.2:
- dependencies:
- data-uri-to-buffer: 4.0.1
- fetch-blob: 3.2.0
- formdata-polyfill: 4.0.10
+ nf3@0.3.21: {}
+
+ nitro@3.0.260610-beta(chokidar@5.0.0)(dotenv@17.4.2)(jiti@2.7.0)(mongodb@7.1.0)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0)):
+ dependencies:
+ consola: 3.4.2
+ crossws: 0.4.9(srvx@0.11.21)
+ db0: 0.3.4
+ env-runner: 0.1.16
+ h3: 2.0.1-rc.22(crossws@0.4.9(srvx@0.11.21))
+ hookable: 6.1.1
+ nf3: 0.3.21
+ ocache: 0.1.5
+ ofetch: 2.0.0-alpha.3
+ ohash: 2.0.11
+ rolldown: 1.1.5
+ srvx: 0.11.21
+ unenv: 2.0.0-rc.24
+ unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4)(mongodb@7.1.0)(ofetch@2.0.0-alpha.3)
+ optionalDependencies:
+ dotenv: 17.4.2
+ jiti: 2.7.0
+ vite: 8.1.4(@types/node@22.20.1)(jiti@2.7.0)
+ transitivePeerDependencies:
+ - '@azure/app-configuration'
+ - '@azure/cosmos'
+ - '@azure/data-tables'
+ - '@azure/identity'
+ - '@azure/keyvault-secrets'
+ - '@azure/storage-blob'
+ - '@capacitor/preferences'
+ - '@deno/kv'
+ - '@electric-sql/pglite'
+ - '@libsql/client'
+ - '@netlify/blobs'
+ - '@netlify/runtime'
+ - '@planetscale/database'
+ - '@upstash/redis'
+ - '@vercel/blob'
+ - '@vercel/functions'
+ - '@vercel/kv'
+ - aws4fetch
+ - better-sqlite3
+ - chokidar
+ - drizzle-orm
+ - idb-keyval
+ - ioredis
+ - lru-cache
+ - miniflare
+ - mongodb
+ - mysql2
+ - sqlite3
+ - uploadthing
+ - wrangler
node-releases@2.0.37: {}
@@ -5961,6 +5546,14 @@ snapshots:
object-treeify@1.1.33: {}
+ ocache@0.1.5:
+ dependencies:
+ ohash: 2.0.11
+
+ ofetch@2.0.0-alpha.3: {}
+
+ ohash@2.0.11: {}
+
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
@@ -5986,6 +5579,12 @@ snapshots:
powershell-utils: 0.1.0
wsl-utils: 0.3.1
+ open@8.4.2:
+ dependencies:
+ define-lazy-prop: 2.0.0
+ is-docker: 2.2.1
+ is-wsl: 2.2.0
+
ora@8.2.0:
dependencies:
chalk: 5.6.2
@@ -5998,7 +5597,15 @@ snapshots:
string-width: 7.2.0
strip-ansi: 7.2.0
- outvariant@1.4.3: {}
+ p-limit@2.3.0:
+ dependencies:
+ p-try: 2.2.0
+
+ p-locate@3.0.0:
+ dependencies:
+ p-limit: 2.3.0
+
+ p-try@2.2.0: {}
parent-module@1.0.1:
dependencies:
@@ -6017,23 +5624,29 @@ snapshots:
path-browserify@1.0.1: {}
+ path-exists@3.0.0: {}
+
path-key@3.1.1: {}
path-key@4.0.0: {}
- path-to-regexp@6.3.0: {}
-
path-to-regexp@8.4.2: {}
+ pathe@2.0.3: {}
+
picocolors@1.1.1: {}
picomatch@2.3.2: {}
- picomatch@4.0.4: {}
+ picomatch@4.0.5: {}
pkce-challenge@5.0.1: {}
- postcss-selector-parser@7.1.1:
+ pkg-up@3.1.0:
+ dependencies:
+ find-up: 3.0.0
+
+ postcss-selector-parser@7.1.4:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
@@ -6043,33 +5656,22 @@ snapshots:
nanoid: 3.3.12
picocolors: 1.1.1
source-map-js: 1.2.1
+ optional: true
- postcss@8.5.2:
- dependencies:
- nanoid: 3.3.8
- picocolors: 1.1.1
- source-map-js: 1.2.1
-
- postcss@8.5.9:
+ postcss@8.5.16:
dependencies:
nanoid: 3.3.12
picocolors: 1.1.1
source-map-js: 1.2.1
- postgres@3.4.5: {}
-
powershell-utils@0.1.0: {}
+ prettier@3.9.5: {}
+
pretty-ms@9.3.0:
dependencies:
parse-ms: 4.0.0
- prism-react-renderer@2.4.1(react@18.3.1):
- dependencies:
- '@types/prismjs': 1.26.5
- clsx: 2.1.1
- react: 18.3.1
-
prompts@2.4.2:
dependencies:
kleur: 3.0.3
@@ -6088,142 +5690,67 @@ snapshots:
pvutils@1.1.5: {}
- qs@6.15.1:
+ qs@6.15.3:
dependencies:
- side-channel: 1.1.0
+ es-define-property: 1.0.1
+ side-channel: 1.1.1
queue-microtask@1.2.3: {}
- radix-ui@1.4.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
- dependencies:
- '@radix-ui/primitive': 1.1.3
- '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-accordion': 1.2.12(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-avatar': 1.1.10(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-form': 0.1.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-label': 2.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-menubar': 1.1.16(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-popover': 1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-progress': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-select': 2.2.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-separator': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-slider': 1.3.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-slot': 1.2.3(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-switch': 1.2.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-tabs': 1.1.13(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-toast': 1.2.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-toggle': 1.1.10(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.18)(react@18.3.1)
- '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.18
- '@types/react-dom': 18.3.5(@types/react@18.3.18)
-
- range-parser@1.2.1: {}
+ range-parser@1.3.0: {}
raw-body@3.0.2:
dependencies:
bytes: 3.1.2
http-errors: 2.0.1
- iconv-lite: 0.7.2
+ iconv-lite: 0.7.3
unpipe: 1.0.0
- react-day-picker@9.14.0(react@18.3.1):
- dependencies:
- '@date-fns/tz': 1.4.1
- '@tabby_ai/hijri-converter': 1.0.5
- date-fns: 4.1.0
- date-fns-jalali: 4.1.0-0
- react: 18.3.1
-
- react-dom@18.3.1(react@18.3.1):
- dependencies:
- loose-envify: 1.4.0
- react: 18.3.1
- scheduler: 0.23.2
-
- react-error-boundary@6.1.0(react@18.3.1):
- dependencies:
- react: 18.3.1
-
- react-hook-form@7.55.0(react@18.3.1):
+ react-day-picker@10.0.1(@types/react@19.2.17)(react@19.2.7):
dependencies:
- react: 18.3.1
+ '@date-fns/tz': 1.5.0
+ date-fns: 4.4.0
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
- react-icons@5.7.0(react@18.3.1):
+ react-dom@19.2.7(react@19.2.7):
dependencies:
- react: 18.3.1
+ react: 19.2.7
+ scheduler: 0.27.0
- react-remove-scroll-bar@2.3.8(@types/react@18.3.18)(react@18.3.1):
+ react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.7):
dependencies:
- react: 18.3.1
- react-style-singleton: 2.2.3(@types/react@18.3.18)(react@18.3.1)
+ react: 19.2.7
+ react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7)
tslib: 2.8.1
optionalDependencies:
- '@types/react': 18.3.18
+ '@types/react': 19.2.17
- react-remove-scroll@2.6.3(@types/react@18.3.18)(react@18.3.1):
+ react-remove-scroll@2.7.2(@types/react@19.2.17)(react@19.2.7):
dependencies:
- react: 18.3.1
- react-remove-scroll-bar: 2.3.8(@types/react@18.3.18)(react@18.3.1)
- react-style-singleton: 2.2.3(@types/react@18.3.18)(react@18.3.1)
+ react: 19.2.7
+ react-remove-scroll-bar: 2.3.8(@types/react@19.2.17)(react@19.2.7)
+ react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7)
tslib: 2.8.1
- use-callback-ref: 1.3.3(@types/react@18.3.18)(react@18.3.1)
- use-sidecar: 1.1.3(@types/react@18.3.18)(react@18.3.1)
+ use-callback-ref: 1.3.3(@types/react@19.2.17)(react@19.2.7)
+ use-sidecar: 1.1.3(@types/react@19.2.17)(react@19.2.7)
optionalDependencies:
- '@types/react': 18.3.18
+ '@types/react': 19.2.17
- react-style-singleton@2.2.3(@types/react@18.3.18)(react@18.3.1):
+ react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.7):
dependencies:
get-nonce: 1.0.1
- react: 18.3.1
+ react: 19.2.7
tslib: 2.8.1
optionalDependencies:
- '@types/react': 18.3.18
+ '@types/react': 19.2.17
- react@18.3.1:
- dependencies:
- loose-envify: 1.4.0
+ react@19.2.7: {}
+
+ readdirp@5.0.0: {}
- recast@0.23.11:
+ recast@0.23.12:
dependencies:
ast-types: 0.16.1
esprima: 4.0.1
@@ -6233,11 +5760,9 @@ snapshots:
reflect-metadata@0.2.2: {}
- require-directory@2.1.1: {}
-
require-from-string@2.0.2: {}
- reselect@5.1.1: {}
+ reselect@5.2.0: {}
resolve-from@4.0.0: {}
@@ -6246,12 +5771,33 @@ snapshots:
onetime: 7.0.0
signal-exit: 4.1.0
- rettime@0.10.1: {}
-
reusify@1.1.0: {}
+ rolldown@1.1.5:
+ dependencies:
+ '@oxc-project/types': 0.139.0
+ '@rolldown/pluginutils': 1.0.1
+ optionalDependencies:
+ '@rolldown/binding-android-arm64': 1.1.5
+ '@rolldown/binding-darwin-arm64': 1.1.5
+ '@rolldown/binding-darwin-x64': 1.1.5
+ '@rolldown/binding-freebsd-x64': 1.1.5
+ '@rolldown/binding-linux-arm-gnueabihf': 1.1.5
+ '@rolldown/binding-linux-arm64-gnu': 1.1.5
+ '@rolldown/binding-linux-arm64-musl': 1.1.5
+ '@rolldown/binding-linux-ppc64-gnu': 1.1.5
+ '@rolldown/binding-linux-s390x-gnu': 1.1.5
+ '@rolldown/binding-linux-x64-gnu': 1.1.5
+ '@rolldown/binding-linux-x64-musl': 1.1.5
+ '@rolldown/binding-openharmony-arm64': 1.1.5
+ '@rolldown/binding-wasm32-wasi': 1.1.5
+ '@rolldown/binding-win32-arm64-msvc': 1.1.5
+ '@rolldown/binding-win32-x64-msvc': 1.1.5
+
rou3@0.7.12: {}
+ rou3@0.8.1: {}
+
router@2.2.0:
dependencies:
debug: 4.4.3
@@ -6270,14 +5816,11 @@ snapshots:
safer-buffer@2.1.2: {}
- scheduler@0.23.2:
- dependencies:
- loose-envify: 1.4.0
+ scheduler@0.27.0: {}
semver@6.3.1: {}
- semver@7.8.2:
- optional: true
+ semver@7.8.2: {}
send@1.2.1:
dependencies:
@@ -6290,11 +5833,17 @@ snapshots:
mime-types: 3.0.2
ms: 2.1.3
on-finished: 2.4.1
- range-parser: 1.2.1
+ range-parser: 1.3.0
statuses: 2.0.2
transitivePeerDependencies:
- supports-color
+ seroval-plugins@1.5.5(seroval@1.5.5):
+ dependencies:
+ seroval: 1.5.5
+
+ seroval@1.5.5: {}
+
serve-static@2.2.1:
dependencies:
encodeurl: 2.0.0
@@ -6304,51 +5853,46 @@ snapshots:
transitivePeerDependencies:
- supports-color
- server-only@0.0.1: {}
-
set-cookie-parser@3.0.1: {}
setprototypeof@1.2.0: {}
- shadcn@4.2.0(@types/node@20.17.19)(typescript@5.7.3):
+ shadcn@4.13.0(typescript@6.0.3):
dependencies:
'@babel/core': 7.29.0
'@babel/parser': 7.29.2
- '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0)
- '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0)
- '@dotenvx/dotenvx': 1.61.0
+ '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.0)
+ '@babel/preset-typescript': 7.29.7(@babel/core@7.29.0)
+ '@dotenvx/dotenvx': 1.75.1
'@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76)
'@types/validate-npm-package-name': 4.0.2
browserslist: 4.28.2
commander: 14.0.3
- cosmiconfig: 9.0.1(typescript@5.7.3)
+ cosmiconfig: 9.0.2(typescript@6.0.3)
dedent: 1.7.2
deepmerge: 4.3.1
diff: 8.0.4
execa: 9.6.1
fast-glob: 3.3.3
- fs-extra: 11.3.4
+ fs-extra: 11.3.6
fuzzysort: 3.1.0
- https-proxy-agent: 7.0.6
kleur: 4.1.5
- msw: 2.13.2(@types/node@20.17.19)(typescript@5.7.3)
- node-fetch: 3.3.2
open: 11.0.0
ora: 8.2.0
- postcss: 8.5.9
- postcss-selector-parser: 7.1.1
+ postcss: 8.5.16
+ postcss-selector-parser: 7.1.4
prompts: 2.4.2
- recast: 0.23.11
+ recast: 0.23.12
stringify-object: 5.0.0
- tailwind-merge: 3.0.1
+ tailwind-merge: 3.6.0
ts-morph: 26.0.0
tsconfig-paths: 4.2.0
+ undici: 7.28.0
validate-npm-package-name: 7.0.2
zod: 3.25.76
zod-to-json-schema: 3.25.2(zod@3.25.76)
transitivePeerDependencies:
- '@cfworker/json-schema'
- - '@types/node'
- babel-plugin-macros
- supports-color
- typescript
@@ -6411,7 +5955,7 @@ snapshots:
object-inspect: 1.13.4
side-channel-map: 1.0.1
- side-channel@1.1.0:
+ side-channel@1.1.1:
dependencies:
es-errors: 1.3.0
object-inspect: 1.13.4
@@ -6425,35 +5969,38 @@ snapshots:
sisteransi@1.0.5: {}
- sonner@2.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ solid-js@1.9.14:
+ dependencies:
+ csstype: 3.2.3
+ seroval: 1.5.5
+ seroval-plugins: 1.5.5(seroval@1.5.5)
+ optional: true
+
+ sonner@2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
dependencies:
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
source-map-js@1.2.1: {}
source-map@0.6.1: {}
+ source-map@0.7.6: {}
+
sparse-bitfield@3.0.3:
dependencies:
memory-pager: 1.5.0
+ srvx@0.11.21: {}
+
statuses@2.0.2: {}
stdin-discarder@0.2.2: {}
- strict-event-emitter@0.5.1: {}
-
- string-width@4.2.3:
- dependencies:
- emoji-regex: 8.0.0
- is-fullwidth-code-point: 3.0.0
- strip-ansi: 6.0.1
-
string-width@7.2.0:
dependencies:
emoji-regex: 10.6.0
- get-east-asian-width: 1.5.0
+ get-east-asian-width: 1.6.0
strip-ansi: 7.2.0
stringify-object@5.0.0:
@@ -6476,45 +6023,32 @@ snapshots:
strip-final-newline@4.0.0: {}
- styled-jsx@5.1.6(@babel/core@7.29.0)(react@18.3.1):
+ styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.7):
dependencies:
client-only: 0.0.1
- react: 18.3.1
+ react: 19.2.7
optionalDependencies:
'@babel/core': 7.29.0
+ optional: true
superjson@2.2.2:
dependencies:
copy-anything: 3.0.5
- tabbable@6.4.0: {}
-
- tagged-tag@1.0.0: {}
+ systeminformation@5.31.15: {}
- tailwind-merge@3.0.1: {}
+ tailwind-merge@3.6.0: {}
- tailwind-scrollbar@4.0.2(react@18.3.1)(tailwindcss@4.1.4):
- dependencies:
- prism-react-renderer: 2.4.1(react@18.3.1)
- tailwindcss: 4.1.4
- transitivePeerDependencies:
- - react
-
- tailwindcss-animate@1.0.7(tailwindcss@4.1.4):
- dependencies:
- tailwindcss: 4.1.4
+ tailwindcss@4.3.2: {}
- tailwindcss@4.1.4: {}
-
- tapable@2.2.1: {}
+ tapable@2.3.3: {}
tiny-invariant@1.3.3: {}
- tldts-core@7.0.28: {}
-
- tldts@7.0.28:
+ tinyglobby@0.2.17:
dependencies:
- tldts-core: 7.0.28
+ fdir: 6.5.0(picomatch@4.0.5)
+ picomatch: 4.0.5
to-regex-range@5.0.1:
dependencies:
@@ -6522,10 +6056,6 @@ snapshots:
toidentifier@1.0.1: {}
- tough-cookie@6.0.1:
- dependencies:
- tldts: 7.0.28
-
tr46@5.1.1:
dependencies:
punycode: 2.3.1
@@ -6551,19 +6081,23 @@ snapshots:
tw-animate-css@1.4.0: {}
- type-fest@5.5.0:
+ type-is@2.1.0:
dependencies:
- tagged-tag: 1.0.0
-
- type-is@2.0.1:
- dependencies:
- content-type: 1.0.5
+ content-type: 2.0.0
media-typer: 1.1.0
mime-types: 3.0.2
- typescript@5.7.3: {}
+ typescript@6.0.3: {}
+
+ ufo@1.6.4: {}
- undici-types@6.19.8: {}
+ undici-types@6.21.0: {}
+
+ undici@7.28.0: {}
+
+ unenv@2.0.0-rc.24:
+ dependencies:
+ pathe: 2.0.3
unicorn-magic@0.3.0: {}
@@ -6571,7 +6105,21 @@ snapshots:
unpipe@1.0.0: {}
- until-async@3.0.2: {}
+ unplugin@3.3.0(rolldown@1.1.5)(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0)):
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ picomatch: 4.0.5
+ webpack-virtual-modules: 0.6.2
+ optionalDependencies:
+ rolldown: 1.1.5
+ vite: 8.1.4(@types/node@22.20.1)(jiti@2.7.0)
+
+ unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4)(mongodb@7.1.0)(ofetch@2.0.0-alpha.3):
+ optionalDependencies:
+ chokidar: 5.0.0
+ db0: 0.3.4
+ mongodb: 7.1.0
+ ofetch: 2.0.0-alpha.3
update-browserslist-db@1.2.3(browserslist@4.28.2):
dependencies:
@@ -6579,24 +6127,24 @@ snapshots:
escalade: 3.2.0
picocolors: 1.1.1
- use-callback-ref@1.3.3(@types/react@18.3.18)(react@18.3.1):
+ use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.7):
dependencies:
- react: 18.3.1
+ react: 19.2.7
tslib: 2.8.1
optionalDependencies:
- '@types/react': 18.3.18
+ '@types/react': 19.2.17
- use-sidecar@1.1.3(@types/react@18.3.18)(react@18.3.1):
+ use-sidecar@1.1.3(@types/react@19.2.17)(react@19.2.7):
dependencies:
detect-node-es: 1.1.0
- react: 18.3.1
+ react: 19.2.7
tslib: 2.8.1
optionalDependencies:
- '@types/react': 18.3.18
+ '@types/react': 19.2.17
- use-sync-external-store@1.6.0(react@18.3.1):
+ use-sync-external-store@1.6.0(react@19.2.7):
dependencies:
- react: 18.3.1
+ react: 19.2.7
util-deprecate@1.0.2: {}
@@ -6604,10 +6152,26 @@ snapshots:
vary@1.1.2: {}
- web-streams-polyfill@3.3.3: {}
+ vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0):
+ dependencies:
+ lightningcss: 1.32.0
+ picomatch: 4.0.5
+ postcss: 8.5.16
+ rolldown: 1.1.5
+ tinyglobby: 0.2.17
+ optionalDependencies:
+ '@types/node': 22.20.1
+ fsevents: 2.3.3
+ jiti: 2.7.0
+
+ vitefu@1.1.3(vite@8.1.4(@types/node@22.20.1)(jiti@2.7.0)):
+ optionalDependencies:
+ vite: 8.1.4(@types/node@22.20.1)(jiti@2.7.0)
webidl-conversions@7.0.0: {}
+ webpack-virtual-modules@0.6.2: {}
+
whatwg-url@14.2.0:
dependencies:
tr46: 5.1.1
@@ -6621,18 +6185,6 @@ snapshots:
dependencies:
isexe: 3.1.5
- wrap-ansi@6.2.0:
- dependencies:
- ansi-styles: 4.3.0
- string-width: 4.2.3
- strip-ansi: 6.0.1
-
- wrap-ansi@7.0.0:
- dependencies:
- ansi-styles: 4.3.0
- string-width: 4.2.3
- strip-ansi: 6.0.1
-
wrappy@1.0.2: {}
wsl-utils@0.3.1:
@@ -6640,28 +6192,19 @@ snapshots:
is-wsl: 3.1.1
powershell-utils: 0.1.0
- y18n@5.0.8: {}
+ xmlbuilder2@4.0.3:
+ dependencies:
+ '@oozcitak/dom': 2.0.2
+ '@oozcitak/infra': 2.0.2
+ '@oozcitak/util': 10.0.0
+ js-yaml: 4.1.1
yallist@3.1.1: {}
- yargs-parser@21.1.1: {}
-
- yargs@17.7.2:
- dependencies:
- cliui: 8.0.1
- escalade: 3.2.0
- get-caller-file: 2.0.5
- require-directory: 2.1.1
- string-width: 4.2.3
- y18n: 5.0.8
- yargs-parser: 21.1.1
-
- yocto-spinner@1.1.0:
+ yocto-spinner@1.2.1:
dependencies:
yoctocolors: 2.1.2
- yoctocolors-cjs@2.1.3: {}
-
yoctocolors@2.1.2: {}
zod-to-json-schema@3.25.2(zod@3.25.76):
@@ -6673,3 +6216,5 @@ snapshots:
zod@4.3.5: {}
zod@4.3.6: {}
+
+ zod@4.4.3: {}
diff --git a/postcss.config.js b/postcss.config.js
deleted file mode 100644
index fb05b56..0000000
--- a/postcss.config.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- plugins: {
- "@tailwindcss/postcss": {},
- },
-}
diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx
deleted file mode 100644
index f7109d7..0000000
--- a/src/app/(auth)/layout.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Shape } from "@/components/shapes"
-
-export default function Layout({ children }: { children: React.ReactNode }) {
- return (
-
- {children}
-
-
-
-
-
-
-
- )
-}
diff --git a/src/app/(auth)/login/can-i-access.tsx b/src/app/(auth)/login/can-i-access.tsx
deleted file mode 100644
index 00fd427..0000000
--- a/src/app/(auth)/login/can-i-access.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import Image from "next/image"
-import { FiAward, FiBriefcase, FiHexagon, FiLock, FiShield, FiStar } from "react-icons/fi"
-import loginSvg1 from "@/assets/svg/login-1.svg"
-import loginSvg2 from "@/assets/svg/login-2.svg"
-import loginSvg3 from "@/assets/svg/login-3.svg"
-import { Card } from "@/components/ui/card"
-import { FeatureBox } from "./what-is"
-
-export function CanIAccess() {
- return (
-
-
-
-
How can I access?
-
-
You can access this dashboard if you have at least one of this role:
-
-
-
-
-
-
-
-
-
When you have checked that you are eligible, these are the steps to access:
-
-
-
-
-
-
1. Login with email
-
We send an OTP to your email
-
-
-
-
-
-
-
-
2. Link your Telegram account
-
This allows to verify your role
-
-
-
-
-
-
-
-
3. Use the dashboard
-
- If your role is not recognized, be patient until the IT team fixes it!
-
-
-
-
-
-
- )
-}
diff --git a/src/app/(auth)/login/login-button.tsx b/src/app/(auth)/login/login-button.tsx
deleted file mode 100644
index 748d318..0000000
--- a/src/app/(auth)/login/login-button.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-"use client"
-import Link from "next/link"
-import { FiLogIn } from "react-icons/fi"
-import { Card, CardContent } from "@/components/ui/card"
-
-export function LoginButton() {
- return (
-
-
-
-
-
- Enter your reserved area
-
-
-
- )
-}
diff --git a/src/app/(auth)/login/login-form.tsx b/src/app/(auth)/login/login-form.tsx
deleted file mode 100644
index c810324..0000000
--- a/src/app/(auth)/login/login-form.tsx
+++ /dev/null
@@ -1,196 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { useState } from "react"
-import { FiKey, FiLoader, FiMail } from "react-icons/fi"
-import { toast } from "sonner"
-import { Button } from "@/components/ui/button"
-import { FieldSeparator } from "@/components/ui/field"
-import { Input } from "@/components/ui/input"
-import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"
-import { Label } from "@/components/ui/label"
-import { auth } from "@/lib/auth"
-
-export default function LoginForm() {
- const [email, setEmail] = useState("")
- const [sent, setSent] = useState(false)
-
- return (
-
- {!sent ? (
- setEmail(v)} onSend={() => setSent(true)} />
- ) : (
-
- )}
-
- )
- // Or continue with
- // Login with Passkey
-}
-
-function EmailCard({
- email,
- onChange,
- onSend,
-}: {
- email: string
- onChange: (value: string) => void
- onSend: () => void
-}) {
- const [loading, setLoading] = useState(false)
- const [passkeyLoading, setPasskeyLoading] = useState(false)
- const router = useRouter()
-
- async function sendOtp() {
- const { data, error } = await auth.emailOtp.sendVerificationOtp({
- type: "sign-in",
- email,
- })
-
- if (data?.success) {
- onSend()
- toast.success("OTP sent. Check your inbox and spam!")
- return
- }
-
- if (error?.code === "INVALID_EMAIL") {
- toast.error("Invalid email")
- return
- }
-
- toast.error("There was an unexpected error")
- console.error({ error })
- }
-
- async function passkeyLogin() {
- setPasskeyLoading(true)
- const { data, error } = await auth.signIn.passkey()
- if (error || !data) toast.error("Unable to login with passkey")
- else {
- toast.success("Login successful")
- router.replace("/dashboard")
- }
- setPasskeyLoading(false)
- }
-
- return (
-
-
-
Or continue with
-
- {passkeyLoading ? (
-
- ) : (
- <>
- Login with Passkey
- >
- )}
-
-
- )
-}
-
-function OTPCard({ email }: { email: string }) {
- const router = useRouter()
- const [loading, setLoading] = useState(false)
- const [otp, setOtp] = useState("")
-
- async function verifyOtp() {
- const { data, error } = await auth.signIn.emailOtp({
- email,
- otp,
- fetchOptions: {
- onRequest: () => {
- setLoading(true)
- },
- onResponse: () => {
- setLoading(false)
- },
- },
- })
-
- if (data) {
- toast.success("Successfully logged in!")
- return router.replace("/dashboard")
- }
-
- if (error.code === "INVALID_OTP") toast.error("You entered an invalid OTP")
- else {
- toast.error("There was an unexpected error")
- console.error({ error })
- }
- }
- return (
-
- )
-}
diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx
deleted file mode 100644
index cd3684f..0000000
--- a/src/app/(auth)/login/page.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { redirect } from "next/navigation"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
-import { getServerSession } from "@/server/auth"
-import LoginForm from "./login-form"
-
-export default async function Page() {
- const { data: session } = await getServerSession()
- if (session) return redirect("/dashboard")
-
- return (
-
-
-
- Login
-
- Login with Email OTP or Passkey
-
-
-
-
-
-
-
- )
-}
diff --git a/src/app/(auth)/login/what-is.tsx b/src/app/(auth)/login/what-is.tsx
deleted file mode 100644
index 29344d8..0000000
--- a/src/app/(auth)/login/what-is.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { IconType } from "react-icons"
-import {
- FiBookOpen,
- FiCheckSquare,
- FiClipboard,
- FiCpu,
- FiEdit3,
- FiHelpCircle,
- FiLayout,
- FiMoreHorizontal,
- FiShield,
- FiUsers,
-} from "react-icons/fi"
-import { Card } from "@/components/ui/card"
-
-export function WhatIs() {
- return (
-
-
-
-
What is this?
-
-
- This is a PoliNetwork's internal tool that allows admins and department staff to perform multiple actions
- via an easy web interface.
-
-
What actions you can perform depends on your role, but some remarkable ones are:
-
-
-
-
-
-
-
-
-
-
-
-
- It's clear that you'll have access to different features depending on your role(s).
-
-
- )
-}
-
-export function FeatureBox({ icon: Icon, feature }: { icon: IconType; feature: string }) {
- return (
-
-
-
- )
-}
diff --git a/src/app/(auth)/onboarding/_layout.tsx b/src/app/(auth)/onboarding/_layout.tsx
deleted file mode 100644
index baf33f4..0000000
--- a/src/app/(auth)/onboarding/_layout.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function Layout({ children }: { children: React.ReactNode }) {
- return (
-
- {children}
-
- )
-}
diff --git a/src/app/(auth)/onboarding/link/logout.tsx b/src/app/(auth)/onboarding/link/logout.tsx
deleted file mode 100644
index 0b743f8..0000000
--- a/src/app/(auth)/onboarding/link/logout.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { toast } from "sonner"
-import { auth } from "../../../../lib/auth"
-
-export function Logout({ email }: { email: string }) {
- const router = useRouter()
-
- async function handleLogout() {
- const { data, error } = await auth.signOut()
- if (data?.success || !error) {
- toast.success("Successfully logged out")
- return router.replace("/")
- }
-
- toast.error("There was an unexpected error")
- console.error({ error })
- }
-
- return (
-
-
- Logged in as {email}
-
-
Wrong account? {" "}
-
- Logout
-
-
- )
-}
diff --git a/src/app/(auth)/onboarding/link/page.tsx b/src/app/(auth)/onboarding/link/page.tsx
deleted file mode 100644
index fc3e0ce..0000000
--- a/src/app/(auth)/onboarding/link/page.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import Image from "next/image"
-import { redirect } from "next/navigation"
-import loginSvg2 from "@/assets/svg/login-2.svg"
-import { Card } from "@/components/ui/card"
-import { env } from "@/env"
-import { getServerSession } from "@/server/auth"
-import { Logout } from "./logout"
-import { TelegramLink } from "./telegram"
-
-const BOT_USERNAME = env.NODE_ENV === "production" ? "pn_ts_dev_bot" : "pn_ts_devlocal_bot"
-
-export default async function OnboardingLink() {
- const { data: session } = await getServerSession()
- if (!session || session.user.telegramId) redirect("/dashboard")
-
- return (
-
-
-
-
-
-
Link your Telegram account
-
This allows to verify your role
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/app/(auth)/onboarding/link/telegram.tsx b/src/app/(auth)/onboarding/link/telegram.tsx
deleted file mode 100644
index d6dd6bd..0000000
--- a/src/app/(auth)/onboarding/link/telegram.tsx
+++ /dev/null
@@ -1,225 +0,0 @@
-"use client"
-import { APIError } from "better-auth/api"
-import { useRouter } from "next/navigation"
-import { useCallback, useEffect, useState } from "react"
-import { FiCheckCircle, FiClock } from "react-icons/fi"
-import { toast } from "sonner"
-import { Code } from "@/components/code"
-import { InputWithPrefix } from "@/components/input-prefix"
-import { Button } from "@/components/ui/button"
-import { Label } from "@/components/ui/label"
-import { Progress, ProgressIndicator, ProgressTrack } from "@/components/ui/progress"
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
-import { useLocalStorage } from "@/hooks/use-local-storage"
-import { auth, useSession } from "@/lib/auth"
-
-type SavedLink = {
- username: string
- code: string
- ttl: number
- startTime: number
-}
-
-export function TelegramLink({ botUsername }: { botUsername: string }) {
- const router = useRouter()
- const { refetch } = useSession()
- const { data: savedLink, update, remove } = useLocalStorage("linktg")
- const timeLeft = savedLink ? savedLink.ttl - (Date.now() - savedLink.startTime) / 1000 : null
- if (timeLeft !== null && timeLeft <= 0) {
- remove()
- }
-
- const [usernameInput, setUsernameInput] = useState("")
- const [expired, setExpired] = useState(false)
- const [success, setSuccess] = useState(false)
-
- const handleComplete = useCallback(() => {
- setSuccess(true)
- void refetch()
- remove()
-
- setTimeout(() => {
- router.push("/dashboard")
- }, 5000)
- }, [router, refetch, remove])
-
- async function handleSubmit(e: React.FormEvent | React.MouseEvent) {
- e.stopPropagation()
- e.preventDefault()
- if (usernameInput.length === 0) return
- const res = await auth.telegram.link.start({
- telegramUsername: usernameInput,
- })
-
- if (res.error) return console.error("custom error", res.error)
-
- setExpired(false)
- update({
- code: res.data.code,
- startTime: Date.now(),
- ttl: res.data.ttl,
- username: usernameInput,
- })
- }
-
- function reset() {
- setExpired(false)
- setUsernameInput("")
- remove()
- }
-
- useEffect(() => {
- if (savedLink === undefined || savedLink === null) return
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
- const interval = setInterval(async () => {
- const res = await auth.telegram.link.verify({
- query: { code: savedLink.code },
- })
-
- if (res.error) return console.error("custom error", res.error)
- if (res.data instanceof APIError) return console.error("better-auth APIError", res.data)
- if (!("verified" in res.data)) return console.error("unexpected response", res.data)
-
- const data = res.data as { verified: boolean; expired?: boolean }
-
- if (data.verified) {
- clearInterval(interval)
- handleComplete()
- return
- }
-
- if (data.expired) {
- setExpired(true)
- remove()
- clearInterval(interval)
- return
- }
-
- console.log("polling... not verified or expired")
- }, 5000)
- return () => clearInterval(interval)
- }, [savedLink, handleComplete, remove])
-
- if (savedLink === undefined) return null
- if (success)
- return (
-
-
-
Linked successfully!
-
Redirecting you in 5s...
-
- )
-
- if (expired)
- return (
-
-
-
Code Expired!
-
Regenerate
-
- )
-
- return savedLink && timeLeft ? (
-
-
{
- try {
- await navigator.clipboard.writeText(savedLink.code)
- toast.success("Code copied to clipboard!")
- } catch (err) {
- toast.error("Cannot copy code to clipboard")
- console.error(err)
- }
- }}
- >
-
- {savedLink.code.split("").map((c, i) => (
- {c}
- ))}
-
-
-
setExpired(true)} />
-
-
-
-
- Having troubles with this code?{" "}
-
- Click to reset
-
-
-
- ) : (
-
- )
-}
-
-function Timer({ ttl, timeLeft: pTimeLeft, onEnd }: { ttl: number; timeLeft: number; onEnd: () => void }) {
- const ttlMs = ttl * 1000
- const [timeLeft, setTimeLeft] = useState(pTimeLeft * 1000)
-
- const percentage = (timeLeft / ttlMs) * 100
- useEffect(() => {
- if (timeLeft === 0) return
- const interval = setInterval(() => {
- setTimeLeft((prevTime) => {
- if (prevTime <= 100) {
- clearInterval(interval)
- onEnd()
- return 0
- }
- return prevTime - 100
- })
- }, 100)
-
- return () => clearInterval(interval)
- }, [onEnd, timeLeft])
-
- return (
- <>
-
-
-
-
-
-
- Time left:
- {Math.floor(timeLeft / 1000)}s
-
- >
- )
-}
diff --git a/src/app/(auth)/onboarding/no-role/page.tsx b/src/app/(auth)/onboarding/no-role/page.tsx
deleted file mode 100644
index 7146387..0000000
--- a/src/app/(auth)/onboarding/no-role/page.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import Image from "next/image"
-import { redirect } from "next/navigation"
-import loginSvg2 from "@/assets/svg/login-2.svg"
-import { Card } from "@/components/ui/card"
-import { getUserRoles } from "@/server/actions/users"
-import { getServerSession } from "@/server/auth"
-import { Logout } from "../link/logout"
-
-export default async function OnboardingNoRole() {
- const { data: session } = await getServerSession()
- if (!session) redirect("/login")
- if (!session.user.telegramId) redirect("/onboarding/link")
-
- const { roles } = await getUserRoles(session.user.telegramId)
- if (roles?.includes("creator")) redirect("/onboarding/unauthorized")
- if (roles && roles.length > 0) redirect("/dashboard")
-
- return (
-
-
-
-
-
-
Your account is not enabled
-
Please contact an IT member
-
-
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/account/delete-passkey.tsx b/src/app/dashboard/(active)/account/delete-passkey.tsx
deleted file mode 100644
index 56a1435..0000000
--- a/src/app/dashboard/(active)/account/delete-passkey.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { FiTrash2 } from "react-icons/fi"
-import { toast } from "sonner"
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogMedia,
- AlertDialogTitle,
- AlertDialogTrigger,
-} from "@/components/ui/alert-dialog"
-import { Button } from "@/components/ui/button"
-import { auth } from "@/lib/auth"
-
-export function DeletePasskey({ id }: { id: string }) {
- const router = useRouter()
-
- async function deletePasskey() {
- const { data, error } = await auth.passkey.deletePasskey({ id })
- if (!data || error) return toast.error("There was an error")
- toast.success("Passkey deleted!")
- router.refresh()
- }
-
- return (
-
- Delete}>
-
-
-
-
-
- Remove passkey
- Are you sure you want to delete this passkey?
-
-
- Cancel
-
- Confirm
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/account/loading.tsx b/src/app/dashboard/(active)/account/loading.tsx
deleted file mode 100644
index f67b7c1..0000000
--- a/src/app/dashboard/(active)/account/loading.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Skeleton } from "@/components/ui/skeleton"
-import { NewPasskeyButton } from "./passkey-button"
-
-export default function Loading() {
- return (
-
- Account
-
-
-
-
-
- Name:
-
-
-
-
- Email:
-
-
-
- Telegram:
-
-
-
-
-
-
Passkeys
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/account/page.tsx b/src/app/dashboard/(active)/account/page.tsx
deleted file mode 100644
index 8267448..0000000
--- a/src/app/dashboard/(active)/account/page.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { headers } from "next/headers"
-import { FiAlertCircle, FiCalendar, FiKey } from "react-icons/fi"
-import { auth } from "@/lib/auth"
-import { getServerSession } from "@/server/auth"
-import { DeletePasskey } from "./delete-passkey"
-import { NewPasskeyButton } from "./passkey-button"
-import { ProfilePic } from "./profile-pic"
-import { SetName } from "./set-name"
-import { Telegram } from "./telegram"
-
-export default async function Account() {
- const { data: session } = await getServerSession()
- if (!session) return
-
- const { data: passkeys } = await auth.passkey.listUserPasskeys({
- fetchOptions: {
- headers: await headers(),
- },
- })
-
- const { user } = session
-
- return (
-
- Account
-
-
-
-
-
- {!user.name &&
}
-
Name:
- {user.name ?
{user.name}
:
}
-
-
-
-
Email:
-
{user.email}
-
-
- Telegram:
-
-
-
-
-
-
Passkeys
- {passkeys?.map((p) => (
-
-
-
-
-
-
{p.name}
-
-
- Created on {p.createdAt.toLocaleDateString()}
-
-
-
-
- ))}
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/account/passkey-button.tsx b/src/app/dashboard/(active)/account/passkey-button.tsx
deleted file mode 100644
index 0cd4836..0000000
--- a/src/app/dashboard/(active)/account/passkey-button.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { useState } from "react"
-import { toast } from "sonner"
-import { Button } from "@/components/ui/button"
-import { auth } from "@/lib/auth"
-
-export function NewPasskeyButton() {
- const router = useRouter()
- const [isLoading, setIsLoading] = useState(false)
-
- return (
- {
- setIsLoading(true)
- const { data, error } = await auth.passkey.addPasskey({ name: "default" })
- setIsLoading(false)
- if (error) {
- console.error(error)
- toast.error("There was an unexpected error")
- return
- }
-
- toast.success("Passkey created!")
- console.log("passkey created", { data })
- router.refresh()
- return
- }}
- >
- New Passkey
-
- )
-}
diff --git a/src/app/dashboard/(active)/account/profile-pic.tsx b/src/app/dashboard/(active)/account/profile-pic.tsx
deleted file mode 100644
index bfdf238..0000000
--- a/src/app/dashboard/(active)/account/profile-pic.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-"use client"
-
-import type { User } from "better-auth"
-import { useRouter } from "next/navigation"
-import { forwardRef, useRef } from "react"
-import { FiEdit2, FiUpload, FiUser, FiX } from "react-icons/fi"
-import { toast } from "sonner"
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
-import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
-import { auth, useSession } from "@/lib/auth"
-import { getInitials } from "@/lib/utils"
-import { updateProfilePic } from "@/server/actions/users"
-
-type Props = {
- user: User
-}
-
-export function ProfilePic({ user }: Props) {
- const uploadRef = useRef(null)
- const router = useRouter()
- const { refetch } = useSession()
-
- async function handleRemove() {
- const ok = confirm("Are you sure you want to reset your profile picture?")
- if (!ok) return
-
- const r = await auth.updateUser({ image: null })
- if (r.error) {
- toast.error("There was an error")
- console.error(r.error)
- return
- }
-
- toast.success("Profile picture removed successfully'")
- await refetch()
- router.refresh()
- }
-
- return (
-
-
-
-
-
-
-
-
-
- uploadRef.current?.click()}>
- Upload
-
-
- Remove
-
-
-
-
-
-
-
-
- {user.name ? getInitials(user.name) : }
-
-
-
Max 1MB
-
- )
-}
-
-const UploadProfilePicture = forwardRef((_, ref) => {
- const router = useRouter()
- const { refetch } = useSession()
-
- const handleFileChange = async (event: React.ChangeEvent) => {
- const files = event.target.files
- if (files && files.length > 0) {
- const selectedFile = files[0]
- if (!selectedFile) return
-
- if (selectedFile.size > 1024 * 1024) {
- toast.error("The image must be no larger than 1 MB")
- return
- }
-
- if (selectedFile.type !== "image/png" && selectedFile.type !== "image/jpeg") {
- toast.error("The image must be jpeg or png")
- return
- }
-
- const { success } = await updateProfilePic(selectedFile)
- if (success) toast.success("Image changed successfully!")
- else toast.error("There was an error, try again")
- await refetch()
- router.refresh()
- }
- }
- return (
-
- )
-})
diff --git a/src/app/dashboard/(active)/account/set-name.tsx b/src/app/dashboard/(active)/account/set-name.tsx
deleted file mode 100644
index e0c24b9..0000000
--- a/src/app/dashboard/(active)/account/set-name.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { type FormEvent, useState } from "react"
-import { Button } from "@/components/ui/button"
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import { auth } from "@/lib/auth"
-
-export function SetName({ initialName }: { initialName?: string }) {
- const router = useRouter()
- const [name, setName] = useState(initialName ?? "")
- const [open, setOpen] = useState(false)
-
- async function handleUpdate(e: FormEvent) {
- e.preventDefault()
- await auth.updateUser({ name })
- setOpen(false)
- router.refresh()
- }
-
- return (
-
- Set name} />
-
-
- Set your name
- Make changes to your profile here. Click save when you're done.
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/account/telegram.tsx b/src/app/dashboard/(active)/account/telegram.tsx
deleted file mode 100644
index d573433..0000000
--- a/src/app/dashboard/(active)/account/telegram.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { getUserRoles } from "@/server/actions/users"
-import { getServerSession } from "@/server/auth"
-
-export async function Telegram() {
- const { data } = await getServerSession()
- if (!data || !data.user.telegramId) return null
-
- const { roles } = await getUserRoles(data.user.telegramId)
- const length = roles?.length ?? 0
-
- return (
- <>
- {data.user.telegramUsername ? `@${data.user.telegramUsername}` : data.user.id}
- {roles && length > 0 && (roles: {roles.join(", ")}) }
- >
- )
-}
diff --git a/src/app/dashboard/(active)/azure/groups/groups-list.tsx b/src/app/dashboard/(active)/azure/groups/groups-list.tsx
deleted file mode 100644
index 2bf11d5..0000000
--- a/src/app/dashboard/(active)/azure/groups/groups-list.tsx
+++ /dev/null
@@ -1,443 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { useMemo, useState } from "react"
-import { FiChevronDown, FiInfo, FiLoader, FiMail, FiUserMinus, FiUserPlus } from "react-icons/fi"
-import { toast } from "sonner"
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
-import { Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount } from "@/components/ui/avatar"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import { Card, CardContent, CardHeader } from "@/components/ui/card"
-import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
-import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog"
-import { Separator } from "@/components/ui/separator"
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
-import { addAzureGroupMember, removeAzureGroupMember } from "@/server/actions/azure"
-import type { AzureGroup, AzureMember } from "@/server/trpc/types"
-
-const MAX_VISIBLE_MEMBERS = 7
-
-function initials(name: string | null | undefined) {
- const parts = name?.trim().split(/\s+/).filter(Boolean) ?? []
- if (parts.length === 0) return "?"
- return parts
- .slice(0, 2)
- .map((part) => part[0])
- .join("")
- .toUpperCase()
-}
-
-function mutationErrorMessage(error: "UNAUTHORIZED" | "INTERNAL_SERVER_ERROR") {
- if (error === "UNAUTHORIZED") return "You don't have permission to manage this group."
- return "Microsoft 365 could not complete the change. Please try again."
-}
-
-export function GroupsList({ groups, directoryMembers }: { groups: AzureGroup[]; directoryMembers: AzureMember[] }) {
- const sortedGroups = useMemo(() => [...groups].sort((a, b) => a.displayName.localeCompare(b.displayName)), [groups])
- const multiMemberGroups = sortedGroups.filter((group) => group.members.length > 1)
- const singleMemberGroups = sortedGroups.filter((group) => group.members.length <= 1)
- const memberships = groups.reduce((count, group) => count + group.members.length, 0)
-
- return (
-
-
- Azure directory
-
-
-
-
Microsoft 365 Groups
-
- {groups.length} groups
- {memberships} memberships
-
-
-
-
- Looking for groups with 0-1 member(s)?
-
- Groups with max one member are collected in a collapsed section at the bottom of the page.
-
-
-
-
-
-
- {multiMemberGroups.length > 0 && (
-
-
-
-
-
- Groups with 2+ members
-
- {multiMemberGroups.length} {singleMemberGroups.length === 1 ? "group" : "groups"}
-
-
-
-
- }
- />
-
- }>
- {multiMemberGroups.map((group) => (
-
-
-
-
- ))}
-
-
-
- )}
-
- {singleMemberGroups.length > 0 && (
-
-
-
-
-
- Groups with 0-1 member
-
- {singleMemberGroups.length} {singleMemberGroups.length === 1 ? "group" : "groups"}
-
-
-
-
- }
- />
-
- }>
- {singleMemberGroups.map((group) => (
-
-
-
-
- ))}
-
-
-
- )}
-
- )
-}
-
-function GroupRow({ group, directoryMembers }: { group: AzureGroup; directoryMembers: AzureMember[] }) {
- const sortedMembers = [...group.members].sort((a, b) => a.displayName.localeCompare(b.displayName))
- const visibleMembers = sortedMembers.slice(0, MAX_VISIBLE_MEMBERS)
- const hiddenMembers = sortedMembers.slice(MAX_VISIBLE_MEMBERS)
-
- return (
-
-
-
-
{group.displayName}
- {group.mailAddress && (
-
-
- {group.mailAddress}
-
- )}
-
-
- {group.members.length} {group.members.length === 1 ? "member" : "members"}
-
-
-
-
- {group.members.length > 0 ? (
-
- {visibleMembers.map((member) => (
-
- ))}
- {hiddenMembers.length > 0 && }
-
- ) : (
-
No members yet
- )}
-
-
-
- )
-}
-
-function MemberAvatar({ member }: { member: AzureGroup["members"][number] }) {
- return (
-
-
-
- {initials(member.displayName)}
-
-
- {member.displayName}
-
- )
-}
-
-function HiddenMembersCount({ members }: { members: AzureGroup["members"] }) {
- return (
-
-
- +{members.length}
-
-
- {members.map((member) => (
-
-
- {initials(member.displayName)}
-
-
{member.displayName}
-
- ))}
-
-
- )
-}
-
-function AddMemberDialog({ group, directoryMembers }: { group: AzureGroup; directoryMembers: AzureMember[] }) {
- const [open, setOpen] = useState(false)
- const [pending, setPending] = useState(false)
- const [selectedMember, setSelectedMember] = useState(null)
- const router = useRouter()
-
- const availableMembers = useMemo(() => {
- const currentIds = new Set(group.members.map((member) => member.id))
- return directoryMembers
- .filter((member) => !currentIds.has(member.id))
- .sort((a, b) => (a.displayName ?? "").localeCompare(b.displayName ?? ""))
- }, [directoryMembers, group.members])
-
- function handleOpenChange(nextOpen: boolean) {
- if (pending) return
- setOpen(nextOpen)
- if (!nextOpen) setSelectedMember(null)
- }
-
- async function addMember() {
- if (!selectedMember || pending) return
-
- setPending(true)
- try {
- const { error } = await addAzureGroupMember({ groupId: group.id, userId: selectedMember.id })
- if (error) {
- toast.error(mutationErrorMessage(error))
- return
- }
-
- toast.success(`${selectedMember.displayName ?? "Member"} added to ${group.displayName}.`)
- setOpen(false)
- setSelectedMember(null)
- router.refresh()
- } catch (error) {
- console.error(error)
- toast.error("There was an unexpected error while adding the member.")
- } finally {
- setPending(false)
- }
- }
-
- return (
-
-
- }
- />
- }
- >
-
-
- Add member
-
-
-
- Add a group member
-
- Choose a directory user to add to {group.displayName} .
-
-
-
-
-
-
- No available users found.
-
- {availableMembers.map((member) => (
- setSelectedMember(member)}
- >
-
- {initials(member.displayName)}
-
-
- {member.displayName ?? "Unnamed user"}
- {member.mail && {member.mail} }
-
-
- ))}
-
-
-
-
-
-
- Cancel
-
- }
- />
-
- {pending ? (
-
- ) : (
-
- )}
- {pending ? "Adding…" : "Add member"}
-
-
-
-
- )
-}
-
-function RemoveMemberDialog({ group, directoryMembers }: { group: AzureGroup; directoryMembers: AzureMember[] }) {
- const [open, setOpen] = useState(false)
- const [pending, setPending] = useState(false)
- const [selectedMember, setSelectedMember] = useState(null)
- const router = useRouter()
-
- const currentMembers = useMemo(() => {
- const currentIds = new Set(group.members.map((member) => member.id))
- return directoryMembers
- .filter((member) => currentIds.has(member.id))
- .sort((a, b) => (a.displayName ?? "").localeCompare(b.displayName ?? ""))
- }, [directoryMembers, group.members])
-
- function handleOpenChange(nextOpen: boolean) {
- if (pending) return
- setOpen(nextOpen)
- if (!nextOpen) setSelectedMember(null)
- }
-
- async function removeMember() {
- if (!selectedMember || pending) return
-
- setPending(true)
- try {
- const { error } = await removeAzureGroupMember({ groupId: group.id, userId: selectedMember.id })
- if (error) {
- toast.error(mutationErrorMessage(error))
- return
- }
-
- toast.success(`${selectedMember.displayName ?? "Member"} removed from ${group.displayName}.`)
- setOpen(false)
- setSelectedMember(null)
- router.refresh()
- } catch (error) {
- console.error(error)
- toast.error("There was an unexpected error while removing the member.")
- } finally {
- setPending(false)
- }
- }
-
- return (
-
-
-
- }
- />
- }
- >
-
-
- Remove member
-
-
-
-
- Remove a group member
-
- Choose a member to remove from {group.displayName} .
- Their Microsoft 365 account will not be deleted.
-
-
-
-
-
-
- No group members found.
-
- {currentMembers.map((member) => (
- setSelectedMember(member)}
- >
-
- {initials(member.displayName)}
-
-
- {member.displayName ?? "Unnamed user"}
- {member.mail && {member.mail} }
-
-
- ))}
-
-
-
-
-
-
- Cancel
-
- }
- />
-
- {pending ? (
-
- ) : (
-
- )}
- {pending ? "Removing…" : "Remove member"}
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/azure/groups/loading.tsx b/src/app/dashboard/(active)/azure/groups/loading.tsx
deleted file mode 100644
index cb33360..0000000
--- a/src/app/dashboard/(active)/azure/groups/loading.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Card, CardContent, CardHeader } from "@/components/ui/card"
-import { Separator } from "@/components/ui/separator"
-import { Skeleton } from "@/components/ui/skeleton"
-
-export default function Loading() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {Array.from({ length: 4 }, (_, index) => (
-
- {index > 0 &&
}
-
-
-
-
-
-
-
- {Array.from({ length: 5 }, (_, avatarIndex) => (
-
- ))}
-
-
-
-
- ))}
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/azure/groups/page.tsx b/src/app/dashboard/(active)/azure/groups/page.tsx
deleted file mode 100644
index ef7cfd2..0000000
--- a/src/app/dashboard/(active)/azure/groups/page.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { Metadata } from "next"
-import { getAzureGroups, getAzureMembers } from "@/server/actions/azure"
-import { GroupsList } from "./groups-list"
-
-export const metadata: Metadata = {
- title: "Microsoft 365 Groups",
-}
-
-export default async function AzureGroupsPage() {
- const [groups, directoryMembers] = await Promise.all([getAzureGroups(), getAzureMembers()])
-
- return (
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/azure/members/columns.tsx b/src/app/dashboard/(active)/azure/members/columns.tsx
deleted file mode 100644
index 0673bbf..0000000
--- a/src/app/dashboard/(active)/azure/members/columns.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-"use client"
-import { createColumnHelper, type Row } from "@tanstack/react-table"
-import { Badge } from "@/components/ui/badge"
-import type { AzureMember } from "@/server/trpc/types"
-import { SetAssocNumberDialog } from "./set-assoc-number-dialog"
-
-const ch = createColumnHelper()
-
-export const columns = [
- ch.accessor("employeeId", {
- id: "number",
- header: "#",
- footer: (props) => props.column.id,
- cell: ({ getValue, row }) => {
- const value = getValue()
- return value ? {value} :
- },
- }),
- ch.accessor("displayName", {
- id: "displayName",
- header: "Full Name",
- cell: ({ getValue, row }) => (
- <>
- {row.original.isMember && Socio }
- {getValue()}
- >
- ),
- }),
- ch.accessor("mail", { id: "mail", header: "Email" }),
- ch.accessor("assignedLicensesIds", {
- id: "licenses",
- header: "Licenses",
- cell: ({ getValue }) => {
- const licenses = getValue().sort((a, b) => a.localeCompare(b))
- return licenses.map((l) => (
-
- {l}
-
- ))
- },
- }),
- ch.group({
- id: "actions",
- cell: (props) => ,
- }),
-]
-
-function RowActions({ row: _ }: { row: Row }) {
- return
-}
diff --git a/src/app/dashboard/(active)/azure/members/create-assoc-member.tsx b/src/app/dashboard/(active)/azure/members/create-assoc-member.tsx
deleted file mode 100644
index 3e741f4..0000000
--- a/src/app/dashboard/(active)/azure/members/create-assoc-member.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-"use client"
-import { useRouter } from "next/navigation"
-import { useState, useTransition } from "react"
-import { toast } from "sonner"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import {
- Sheet,
- SheetClose,
- SheetContent,
- SheetFooter,
- SheetHeader,
- SheetTitle,
- SheetTrigger,
-} from "@/components/ui/sheet"
-import { createAzureMember } from "@/server/actions/azure"
-
-export function CreateAssocUser() {
- const [open, setOpen] = useState(false)
- const [firstName, setFirstName] = useState("")
- const [lastName, setLastName] = useState("")
- const [assocNumber, setAssocNumber] = useState("")
- const [sendTo, setSendTo] = useState("")
- const router = useRouter()
-
- function handleOpenChange(v: boolean): void {
- setOpen(v)
- setFirstName("")
- setLastName("")
- setAssocNumber("")
- setSendTo("")
- }
-
- const [pending, startTransition] = useTransition()
-
- async function handleSubmit() {
- if (!assocNumber || Number.isNaN(parseInt(assocNumber, 10))) return
-
- const res = await createAzureMember({
- assocNumber: parseInt(assocNumber, 10),
- firstName,
- lastName,
- sendEmailTo: sendTo,
- }).catch(() => null)
-
- if (!res) {
- toast.error("There was an error")
- return
- }
-
- if (res.error !== null) {
- toast.error(res.error)
- return
- }
-
- console.log("Created user", res)
- toast.success(`User created, email: ${res.email}`, { duration: 10_000 })
- router.refresh()
- handleOpenChange(false)
- }
-
- return (
-
- New Member}>
-
-
- Create new member account
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/azure/members/loading.tsx b/src/app/dashboard/(active)/azure/members/loading.tsx
deleted file mode 100644
index a6bd30c..0000000
--- a/src/app/dashboard/(active)/azure/members/loading.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { SkeletonAssocTable } from "./table"
-
-export default function Loading() {
- return (
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/azure/members/page.tsx b/src/app/dashboard/(active)/azure/members/page.tsx
deleted file mode 100644
index 4a4d975..0000000
--- a/src/app/dashboard/(active)/azure/members/page.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Suspense } from "react"
-import { ErrorBoundary } from "react-error-boundary"
-import { Spinner } from "@/components/spinner"
-import { getAzureMembers } from "@/server/actions/azure"
-import { AssocTable } from "./table"
-
-export default async function AssocMembers() {
- const members = await getAzureMembers()
-
- return (
-
- Something went wrong
}>
- }>
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/azure/members/set-assoc-number-dialog.tsx b/src/app/dashboard/(active)/azure/members/set-assoc-number-dialog.tsx
deleted file mode 100644
index ae3e7dc..0000000
--- a/src/app/dashboard/(active)/azure/members/set-assoc-number-dialog.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-"use client"
-import { useRouter } from "next/navigation"
-import { useState, useTransition } from "react"
-import { toast } from "sonner"
-import { Button } from "@/components/ui/button"
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import { wait } from "@/lib/utils"
-import { setAzureMemberNumber } from "@/server/actions/azure"
-
-export function SetAssocNumberDialog({ userId }: { userId: string }) {
- const router = useRouter()
- const [value, setValue] = useState("")
- const [open, setOpen] = useState(false)
-
- function handleOpenChange(v: boolean): void {
- setOpen(v)
- setValue("")
- }
-
- const [pending, startTransition] = useTransition()
-
- async function handleSubmit() {
- if (pending || !value || Number.isNaN(parseInt(value, 10))) return
-
- const loading = toast.loading(`Updating...`)
- const res = await setAzureMemberNumber({ userId, assocNumber: parseInt(value, 10) })
- handleOpenChange(false)
- if (res.error !== null) {
- toast.error("There was an error")
- console.error(res.error)
- return
- }
-
- await wait(2000)
- toast.success(`Updated successfully!`, { id: loading })
- router.refresh()
- console.log("Updated user assocNumber", userId, value)
- }
-
- return (
-
-
- Set
-
- }
- />
-
-
- Set Assoc Number
- This changes the `employeeId` field in the Azure User properties.
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/azure/members/table.tsx b/src/app/dashboard/(active)/azure/members/table.tsx
deleted file mode 100644
index 8209cf5..0000000
--- a/src/app/dashboard/(active)/azure/members/table.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-"use client"
-
-import { useState } from "react"
-import { DataTable } from "@/components/data-table"
-import { Badge } from "@/components/ui/badge"
-import { Skeleton } from "@/components/ui/skeleton"
-import type { AzureMember } from "@/server/trpc/types"
-import { columns } from "./columns"
-import { CreateAssocUser } from "./create-assoc-member"
-
-function sortByAssocNumber(users: AzureMember[]) {
- if (!users || users.length === 0) return users
- if (users.every((u) => !u.employeeId))
- return users.sort((a, b) => (a.displayName ?? "").localeCompare(b.displayName ?? ""))
-
- return users.sort((a, b) => {
- if (a.employeeId && b.employeeId) {
- const aInt = parseInt(a.employeeId, 10)
- const bInt = parseInt(b.employeeId, 10)
- if (Number.isNaN(aInt) && Number.isNaN(bInt)) return 0
- if (Number.isNaN(aInt)) return 1
- if (Number.isNaN(bInt)) return -1
- return aInt - bInt
- }
- if (a.employeeId) return -1
- if (b.employeeId) return 1
- return 0
- })
-}
-
-export function AssocTable({ members }: { members: AzureMember[] }) {
- const [sociFilter, setSociFilter] = useState(false)
- const users = sociFilter ? members.filter((v) => v.isMember) : members
-
- return (
-
-
-
Utenti MS @polinetwork.org
-
setSociFilter((v) => !v)}
- >
- {members.filter((d) => d.isMember).length} Soci
-
-
{members.filter((d) => !d.isMember).length} fuori
-
{users.filter((u) => u.assignedLicensesIds.includes("OFFICE_365")).length} licenze Office
-
-
-
-
-
-
- )
-}
-
-export function SkeletonAssocTable() {
- return (
-
-
-
Utenti MS @polinetwork.org
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/breadcrumb.tsx b/src/app/dashboard/(active)/breadcrumb.tsx
deleted file mode 100644
index 17052fe..0000000
--- a/src/app/dashboard/(active)/breadcrumb.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-"use client"
-import { usePathname } from "next/navigation"
-import { Fragment, useMemo } from "react"
-import { NAV_MAP } from "@/components/dashboard-sidebar/data"
-import {
- BreadcrumbItem as BreadcrumbItemComponent,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- Breadcrumb as BreadcrumbRoot,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb"
-
-export type BreadcrumbItem = {
- title: string
- url?: string
-}
-
-export function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
- const pathname = usePathname()
- const items = useMemo(() => getBreadcrumbs(NAV_MAP, pathname), [pathname])
-
- return (
-
-
- {items.map((item, i) => (
-
-
- {i === items.length - 1 ? (
- {item.title}
- ) : item.url ? (
- {item.title}
- ) : (
- item.title
- )}
-
- {i < items.length - 1 && }
-
- ))}
-
-
- )
-}
-
-function getBreadcrumbs(navMap: Map, pathname: string): BreadcrumbItem[] {
- const segments = pathname.split("/").filter(Boolean)
- const breadcrumbs: BreadcrumbItem[] = []
-
- let currentPath = ""
- let i = 0
-
- for (const segment of segments) {
- currentPath += `/${segment}`
- let title = navMap.get(currentPath)
- if (!title) {
- if (isUUIDorId(segment)) {
- title = "Details"
- } else {
- title = segment.charAt(0).toUpperCase() + segment.slice(1)
- }
- }
-
- // note: at the moment we do not plan to make category pages.
- // If such pages are made in the future, this logic can be removed
- breadcrumbs.push({ title, url: i !== 1 ? currentPath : undefined })
- i++
- }
-
- return breadcrumbs
-}
-
-function isUUIDorId(segment: string) {
- return !Number.isNaN(Number(segment)) || segment.length > 20 // Regex custom a seconda dei tuoi ID
-}
diff --git a/src/app/dashboard/(active)/complete-profile.tsx b/src/app/dashboard/(active)/complete-profile.tsx
deleted file mode 100644
index 10c0eb4..0000000
--- a/src/app/dashboard/(active)/complete-profile.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-"use client"
-
-import Link from "next/link"
-import { FiEdit3 } from "react-icons/fi"
-import { Button } from "@/components/ui/button"
-import { useSession } from "@/lib/auth"
-
-export function CompleteProfile() {
- const { data, isPending } = useSession()
-
- if (!data || isPending) return null
-
- return (
- !data.user.name && (
-
-
-
Your profile is incomplete, please enter the missing information.
-
-
Go complete
-
-
- )
- )
-}
diff --git a/src/app/dashboard/(active)/layout.tsx b/src/app/dashboard/(active)/layout.tsx
deleted file mode 100644
index 016881d..0000000
--- a/src/app/dashboard/(active)/layout.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { cookies } from "next/headers"
-import { DashboardSidebar } from "@/components/dashboard-sidebar"
-import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
-import { COOKIES } from "@/constants"
-import { Breadcrumb } from "./breadcrumb"
-
-function parseCookie(cookie: string) {
- try {
- const parsed = JSON.parse(cookie)
- return parsed
- } catch (_e) {
- return {}
- }
-}
-
-export default async function AdminLayout({ children }: { children: React.ReactNode }) {
- const sidebarCookies = await getSidebarCookies()
-
- return (
-
-
-
-
- {children}
-
-
- )
-}
-
-async function getSidebarCookies() {
- const cookieStore = await cookies()
-
- const sidebarCategoryStateCookie = cookieStore.get(COOKIES.SIDEBAR_CATEGORY_STATE)?.value
- const sidebarOpenCookie = cookieStore.get(COOKIES.SIDEBAR_OPEN)?.value
- const state: Record = sidebarCategoryStateCookie ? parseCookie(sidebarCategoryStateCookie) : {}
- const open: boolean = sidebarOpenCookie !== undefined ? parseCookie(sidebarOpenCookie) : true
-
- return { state, open }
-}
diff --git a/src/app/dashboard/(active)/not-found.tsx b/src/app/dashboard/(active)/not-found.tsx
deleted file mode 100644
index e962605..0000000
--- a/src/app/dashboard/(active)/not-found.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import Link from "next/link"
-import { Button } from "@/components/ui/button"
-
-export default function NotFound() {
- return (
-
-
-
404
-
Not found
-
- The resource you're looking for doesn't exist or has been moved.
-
-
-
-
Go Back
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/page.tsx b/src/app/dashboard/(active)/page.tsx
deleted file mode 100644
index b22e159..0000000
--- a/src/app/dashboard/(active)/page.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { FiInfo } from "react-icons/fi"
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
-import { CompleteProfile } from "./complete-profile"
-
-export default function AdminHome() {
- return (
-
-
-
-
- Page under construction
- Use the sidebar to access the sections
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/grants/delete-grant.tsx b/src/app/dashboard/(active)/telegram/grants/delete-grant.tsx
deleted file mode 100644
index 2eb277b..0000000
--- a/src/app/dashboard/(active)/telegram/grants/delete-grant.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { useState } from "react"
-import { FiAlertOctagon } from "react-icons/fi"
-import { toast } from "sonner"
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogMedia,
- AlertDialogTitle,
- AlertDialogTrigger,
-} from "@/components/ui/alert-dialog"
-import { Button } from "@/components/ui/button"
-import { interruptGrant } from "@/server/actions/grants"
-
-export function DeleteGrant({
- userId,
- // onDelete
-}: {
- userId: number
- // onDelete(): void
-}) {
- const router = useRouter()
- const [open, setOpen] = useState(false)
-
- async function interrupt() {
- try {
- const { error } = await interruptGrant(userId)
-
- if (error === "NOT_FOUND") toast.info("The grant was expired or already interrupted")
- else if (error === "UNAUTHORIZED") toast.error("You don't have enough permission")
- else if (error === "INTERNAL_SERVER_ERROR") toast.error("There was an internal server error")
- else {
- toast.success("Grant interrupted successfully")
- router.refresh()
- // onDelete()
- }
- } catch (err) {
- toast.error("There was an error")
- console.error(err)
- } finally {
- handleOpenChange(false)
- }
- }
-
- function handleOpenChange(v: boolean) {
- setOpen(v)
- }
-
- return (
-
-
-
- Interrupt
-
- }
- >
-
-
-
-
-
- Interrupt Grant
- Are you sure you want to interrupt the grant?
-
-
- Cancel
-
- Confirm
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/grants/grant-list.tsx b/src/app/dashboard/(active)/telegram/grants/grant-list.tsx
deleted file mode 100644
index aeb93a0..0000000
--- a/src/app/dashboard/(active)/telegram/grants/grant-list.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-"use client"
-import { format } from "date-fns"
-import type { ApiOutput } from "@/server/trpc/types"
-import { DeleteGrant } from "./delete-grant"
-
-type Grants = NonNullable
-
-export function GrantList({ grants, isScheduled }: { grants: Grants; isScheduled?: boolean }) {
- return (
-
-
-
Telegram ID
-
Username
-
Start Date
-
End Date
-
Interrupt
-
- {grants.map((r) => (
-
- ))}
- {grants.length === 0 && (
-
- There are no {isScheduled ? "scheduled" : "ongoing"} grants
-
- )}
-
- )
-}
-
-function GrantRow({ row: r }: { row: Grants[number] }) {
- return (
-
-
{r.grant.userId}
-
- {r.user?.username ? `@${r.user.username}` : ``}
-
-
{format(r.grant.validSince, "yyyy/MM/dd HH:mm")}
-
{format(r.grant.validUntil, "yyyy/MM/dd HH:mm")}
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/grants/loading.tsx b/src/app/dashboard/(active)/telegram/grants/loading.tsx
deleted file mode 100644
index 3d6aca2..0000000
--- a/src/app/dashboard/(active)/telegram/grants/loading.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Skeleton } from "@/components/ui/skeleton"
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
-import { NewGrant } from "./new-grant"
-
-export default async function Loading() {
- return (
-
-
-
-
-
-
- All
-
- Ongoing
- Scheduled
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-function Content() {
- return (
-
-
-
Telegram ID
-
Username
-
Start Date
-
End Date
-
Interrupt
-
- {new Array(6).fill(0).map((_, i) => (
-
- ))}
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/grants/new-grant.tsx b/src/app/dashboard/(active)/telegram/grants/new-grant.tsx
deleted file mode 100644
index 7c709c1..0000000
--- a/src/app/dashboard/(active)/telegram/grants/new-grant.tsx
+++ /dev/null
@@ -1,254 +0,0 @@
-"use client"
-
-import { format } from "date-fns"
-import { useRouter } from "next/navigation"
-import { useCallback, useEffect, useState } from "react"
-import { FiChevronDown, FiPlus } from "react-icons/fi"
-import { toast } from "sonner"
-import { Button } from "@/components/ui/button"
-import { Calendar } from "@/components/ui/calendar"
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog"
-import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectLabel,
- SelectSeparator,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
-import { UserSelect } from "@/components/user-select"
-import { useSession } from "@/lib/auth"
-import { createGrant } from "@/server/actions/grants"
-import type { TgUser } from "@/server/trpc/types"
-
-export function NewGrant() {
- const sesh = useSession()
- const router = useRouter()
- const adderId = sesh.data?.user.telegramId
-
- const [open, setOpen] = useState(false)
- const [user, setUser] = useState(null)
- const [startDate, setStartDate] = useState()
- const [endDate, setEndDate] = useState()
- const [reason, setReason] = useState("")
-
- const disabled = !user || !startDate || !endDate || endDate.getTime() < Date.now()
-
- function handleOpenChange(v: boolean) {
- setOpen(v)
- if (v === false) {
- setUser(null)
- setStartDate(undefined)
- setEndDate(undefined)
- setReason("")
- }
- }
-
- const handleDateSelect = useCallback((date: Date, startTime: number, duration: number) => {
- const startDate = new Date(date.getTime())
- startDate.setHours(startTime)
- startDate.setMinutes(0)
- startDate.setSeconds(0)
- setStartDate(startDate)
-
- const endDate = new Date(startDate.getTime())
- endDate.setTime(endDate.getTime() + duration * 1000)
- endDate.setSeconds(0)
- setEndDate(endDate)
- }, [])
-
- const isPast = endDate && endDate.getTime() < Date.now()
-
- async function create() {
- if (!user || !startDate || !endDate || !adderId) return
- const res = await createGrant({
- userId: user.id,
- since: startDate,
- until: endDate,
- reason: reason || undefined,
- })
-
- if (res.error === "UNAUTHORIZED") toast.error("You don't have permission to create grants.")
- else if (res.error === "ALREADY_EXISTING") toast.error("This user already has an ongoing grant.")
- else if (res.error === "INTERNAL_SERVER_ERROR") toast.error("There was a server error.")
- else {
- toast.success("Grant created successfully!")
- router.refresh()
- }
-
- handleOpenChange(false)
- }
-
- return (
-
-
- New Grant
-
- }
- />
-
-
- New Grant
- Create a grant to allow user to skip the auto moderation stack.
-
-
- setUser(user)} onReset={() => setUser(null)} />
-
-
-
-
-
Start Date: {startDate && format(startDate, "yyyy/MM/dd HH:mm")}
-
End Date: {endDate && format(endDate, "yyyy/MM/dd HH:mm")}
-
- {isPast &&
The end datetime is in the past
}
-
-
-
- Reason
- setReason(v.target.value)}
- placeholder="Reason for the grant..."
- />
-
-
-
- Cancel} />
-
- Confirm
-
-
-
-
- )
-}
-
-function DatePicker({ onSelect }: { onSelect: (date: Date, startTime: number, duration: number) => void }) {
- const [date, setDate] = useState(new Date())
- const [startTime, setStartTime] = useState(null)
- const [duration, setDuration] = useState(null)
- const [open, setOpen] = useState(false)
-
- const hours = [
- { label: "1h", value: 3600 },
- { label: "2h", value: 7200 },
- { label: "6h", value: 21600 },
- { label: "12h", value: 43200 },
- ]
- const days = [
- { label: "1d", value: 86400 },
- { label: "2d", value: 172800 },
- { label: "7d", value: 604800 },
- ]
- const durations = [{ label: "Select...", value: null }, ...hours, ...days]
-
- const times = [
- { label: "Select...", value: null },
- ...Array.from({ length: 13 }, (_, i) => ({
- label: `${String(i + 8).padStart(2, "0")}:00`,
- value: i + 8,
- })),
- ]
-
- useEffect(() => {
- if (date !== undefined && startTime !== null && duration !== null) {
- onSelect(date, startTime, duration)
- }
- }, [date, duration, onSelect, startTime])
-
- return (
-
-
- Date
-
-
- {date ? format(date, "PPP") : "Select date"}
-
-
- }
- />
-
- {
- setDate(date)
- setOpen(false)
- }}
- />
-
-
-
-
- Start Time
-
-
-
-
-
-
- Time
- {times.slice(1).map((item) => (
-
- {item.label}
-
- ))}
-
-
-
-
-
- Duration
-
-
-
-
-
-
- Hours
- {hours.map((item) => (
-
- {item.label}
-
- ))}
-
-
-
- Days
- {days.map((item) => (
-
- {item.label}
-
- ))}
-
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/grants/page.tsx b/src/app/dashboard/(active)/telegram/grants/page.tsx
deleted file mode 100644
index 0ca8cb4..0000000
--- a/src/app/dashboard/(active)/telegram/grants/page.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
-import { trpc } from "@/server/trpc"
-import { GrantList } from "./grant-list"
-import { NewGrant } from "./new-grant"
-
-export default async function GrantsPage() {
- const { grants: ongoing } = await trpc.tg.grants.getOngoing.query()
- const { grants: scheduled } = await trpc.tg.grants.getScheduled.query()
-
- return (
-
-
-
-
-
-
- All
-
- Ongoing
- Scheduled
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/groups/group-row.tsx b/src/app/dashboard/(active)/telegram/groups/group-row.tsx
deleted file mode 100644
index e17328d..0000000
--- a/src/app/dashboard/(active)/telegram/groups/group-row.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-"use client"
-import { useRouter } from "next/navigation"
-import { FiCopy } from "react-icons/fi"
-import { toast } from "sonner"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import { setGroupHide } from "@/server/actions/groups"
-import type { TgGroup } from "@/server/trpc/types"
-import { LeaveChat } from "./leave-chat"
-
-export function GroupRow({ row: r }: { row: TgGroup }) {
- const router = useRouter()
-
- async function toggleHide() {
- const ok = await setGroupHide(r.telegramId, !r.hide).catch(() => false)
- if (!ok) {
- toast.error("The field cannot be modified")
- } else {
- toast.success("Hide option toggled!")
- router.refresh()
- }
- }
-
- return (
-
-
{r.telegramId}
-
{r.title}
-
{r.tag ? `@${r.tag}` : ``}
-
- {r.link && (
-
- {r.link}
-
- )}
-
{
- try {
- if (!r.link) return
- await navigator.clipboard.writeText(r.link)
- toast.success("Link copied to clipboard!")
- } catch (err) {
- toast.error("Cannot copy link to clipboard")
- console.error(err)
- }
- }}
- >
-
-
-
-
-
- {r.hide ? (
-
- HIDDEN
-
- ) : (
-
- Visibile
-
- )}
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/groups/leave-chat.tsx b/src/app/dashboard/(active)/telegram/groups/leave-chat.tsx
deleted file mode 100644
index 221564b..0000000
--- a/src/app/dashboard/(active)/telegram/groups/leave-chat.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { useState } from "react"
-import { FiAlertOctagon, FiTrash2 } from "react-icons/fi"
-import { toast } from "sonner"
-import { Spinner } from "@/components/spinner"
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogMedia,
- AlertDialogTitle,
- AlertDialogTrigger,
-} from "@/components/ui/alert-dialog"
-import { Button } from "@/components/ui/button"
-import { leaveChat } from "@/server/actions/groups"
-
-export function LeaveChat({ chatId }: { chatId: number }) {
- const router = useRouter()
- const [open, setOpen] = useState(false)
- const [pending, setPending] = useState(false)
-
- async function submit() {
- setPending(true)
- try {
- const { error } = await leaveChat(chatId)
-
- if (error === "UNAUTHORIZED") toast.error("You don't have enough permission")
- else if (error === "BOT_ERROR") {
- toast.error("Cannot leave the group")
- } else if (error === "NOT_FOUND") {
- toast.warning("Group left BUT not found in db")
- } else {
- toast.success("Group left and deleted")
- router.refresh()
- }
- } catch (err) {
- toast.error("There was an error")
- console.error(err)
- } finally {
- setPending(false)
- handleOpenChange(false)
- }
- }
-
- function handleOpenChange(v: boolean) {
- setOpen(v)
- }
-
- return (
-
-
-
- Leave
-
- }
- >
-
-
-
-
-
- Leave chat
-
- This will make the bot leave the chat and the backend delete the group from the table.
-
-
-
- Cancel
-
- {pending ? : "Confirm"}
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/groups/loading.tsx b/src/app/dashboard/(active)/telegram/groups/loading.tsx
deleted file mode 100644
index b66a765..0000000
--- a/src/app/dashboard/(active)/telegram/groups/loading.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { SearchInput } from "@/components/search-input"
-import { Skeleton } from "@/components/ui/skeleton"
-
-export default async function Loading() {
- return (
-
-
-
-
-
-
Telegram ID
-
Title
-
Tag
-
Invite Link
-
Actions
-
- {new Array(12).fill(0).map((_, i) => (
-
- ))}
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/groups/page.tsx b/src/app/dashboard/(active)/telegram/groups/page.tsx
deleted file mode 100644
index 47ba9cd..0000000
--- a/src/app/dashboard/(active)/telegram/groups/page.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { SearchInput } from "@/components/search-input"
-import { trpc } from "@/server/trpc"
-import { GroupRow } from "./group-row"
-
-export default async function TgGroups({ searchParams }: { searchParams: Promise<{ q?: string }> }) {
- const { q } = await searchParams
-
- const rows = q
- ? (await trpc.tg.groups.search.query({ limit: 20, query: q, showHidden: true })).groups
- : await trpc.tg.groups.getAll.query()
-
- const sorted = rows.sort((a, b) => a.title.localeCompare(b.title))
-
- return (
-
-
-
- Count: {rows.length}
-
-
-
-
Telegram ID
-
Title
-
Tag
-
Invite Link
-
Actions
-
- {sorted.map((r) => (
-
- ))}
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/add-role.tsx b/src/app/dashboard/(active)/telegram/users/[id]/add-role.tsx
deleted file mode 100644
index c702ab1..0000000
--- a/src/app/dashboard/(active)/telegram/users/[id]/add-role.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-"use client"
-import { USER_ROLE } from "@polinetwork/backend"
-import { useRouter } from "next/navigation"
-import { useState } from "react"
-import { FiPlus } from "react-icons/fi"
-import { toast } from "sonner"
-import { Spinner } from "@/components/spinner"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog"
-import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
-import { addUserRole } from "@/server/actions/users"
-import type { TgUser, TgUserRole } from "@/server/trpc/types"
-
-const ARRAY_USER_ROLES = [
- USER_ROLE.ADMIN,
- USER_ROLE.HR,
- USER_ROLE.OWNER,
- USER_ROLE.CREATOR,
- USER_ROLE.DIRETTIVO,
- USER_ROLE.PRESIDENT,
-] as const
-
-export function AddRole({
- user,
- alreadyRoles,
- // onAdd
-}: {
- user: TgUser
- alreadyRoles: TgUserRole[]
- // onAdd(): void
-}) {
- const availableRoles = ARRAY_USER_ROLES.filter((r) => !alreadyRoles.includes(r)).map((g) => ({
- value: g,
- label: `${g.slice(0, 1).toUpperCase()}${g.slice(1)}`,
- }))
-
- const [open, setOpen] = useState(false)
- const [pending, setPending] = useState(false)
- const [selectedRole, setSelectedRole] = useState(null)
-
- const router = useRouter()
-
- async function submit() {
- if (!selectedRole) return toast.warning("No group selected, cannot proceed")
- if (!user) return toast.warning("Invalid user, try restarting the dialog")
-
- setPending(true)
-
- try {
- const { error } = await addUserRole(user.id, selectedRole)
-
- if (error === "UNAUTHORIZED") toast.error("You don't have enough permission")
- else if (error === "INTERNAL_SERVER_ERROR") toast.error("There was an internal server error")
- else if (error === "UNAUTHORIZED_SELF_ASSIGN") toast.error("You cannot add roles to yourself")
- else {
- toast.success(`Role added!`)
- router.refresh()
- // onAdd()
- }
- handleOpenChange(false)
- } catch (err) {
- console.error(err)
- toast.error("There was an error, check logs")
- } finally {
- setPending(false)
- handleOpenChange(false)
- }
- }
-
- function handleOpenChange(v: boolean) {
- setOpen(v)
- if (v === false) {
- setSelectedRole(null)
- }
- }
-
- return (
-
-
- Add Role
-
- }
- />
-
-
- Add Role
- Add a new role to a telegram user
-
- {user && (
-
- Target: {user.firstName} {user.username && `@${user.username}`} [{user.id}]
-
- )}
-
- User roles:
- {alreadyRoles.map((r) => (
- {r}
- ))}
-
-
- setSelectedRole(v)}
- disabled={availableRoles.length === 0}
- >
-
-
-
-
-
- {availableRoles.map((item) => (
-
- {item.label}
-
- ))}
-
-
-
-
-
-
- Cancel
-
- }
- />
-
- {pending ? : "Confirm"}
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/card-audit-log.tsx b/src/app/dashboard/(active)/telegram/users/[id]/card-audit-log.tsx
deleted file mode 100644
index 4c412d8..0000000
--- a/src/app/dashboard/(active)/telegram/users/[id]/card-audit-log.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
-import { fmtUser } from "@/lib/utils/telegram"
-import type { getUserDetails } from "@/server/actions/users"
-
-type Data = Awaited>
-type Log = NonNullable["audits"][number]
-
-export function AuditLogCard({ log: m }: { log: Log }) {
- return (
-
-
- {m.type}
-
-
- Chat:
-
- {m.groupTitle && {m.groupTitle} } [{m.groupId}]
-
- Admin ID:
- {m.admin && fmtUser(m.admin)}
-
- {m.createdAt && (
- <>
- Created:
- {m.createdAt.toLocaleString()}
- >
- )}
-
- {m.until && (
- <>
- Until:
- {m.until.toLocaleString()}
- >
- )}
- Reason:
- {m.reason ? {m.reason}
: N/A
}
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/card-group-admin.tsx b/src/app/dashboard/(active)/telegram/users/[id]/card-group-admin.tsx
deleted file mode 100644
index ea70a93..0000000
--- a/src/app/dashboard/(active)/telegram/users/[id]/card-group-admin.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Code } from "@/components/code"
-import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
-import { stripChatId } from "@/lib/utils/telegram"
-import type { ApiOutput } from "@/server/trpc/types"
-import { DeleteGroupAdmin } from "./delete-group-admin"
-
-type User = ApiOutput["tg"]["users"]["getByUsername"]["user"]
-type GroupAdminSingle = NonNullable
-
-export function GroupAdminCard({
- user,
- groupAdminInfo: m,
- // onDelete,
-}: {
- user: NonNullable
- groupAdminInfo: GroupAdminSingle
- // onDelete(): void
-}) {
- return (
-
-
- {m.group.title}
-
-
-
- Chat ID:
- {m.group.id} / {stripChatId(m.group.id)}
-
-
- Added By:
- {m.addedBy.firstName} {m.addedBy.username ? `@${m.addedBy.username}` : ""}
-
-
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/card-message.tsx b/src/app/dashboard/(active)/telegram/users/[id]/card-message.tsx
deleted file mode 100644
index 4f60cda..0000000
--- a/src/app/dashboard/(active)/telegram/users/[id]/card-message.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { FiExternalLink } from "react-icons/fi"
-import { Button } from "@/components/ui/button"
-import { Card, CardContent, CardFooter } from "@/components/ui/card"
-import { stripChatId } from "@/lib/utils/telegram"
-import type { ApiOutput } from "@/server/trpc/types"
-
-type Message = NonNullable[number]
-export function MessageCard({ message: m }: { message: Message }) {
- return (
-
-
-
- Chat:
- {m.group && {m.group.title} } [{m.chatId}]
-
-
- Message ID:
- {m.messageId}
-
-
- Timestamp:
- {m.timestamp.toLocaleString()}
-
- Content:
-
- {m.message.slice(0, 40)} {m.message.length >= 40 && "[...]"}
-
-
-
- {m.group?.inviteLink && (
-
-
- Join Chat
-
-
- )}
-
-
- Open
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/card-user-grant.tsx b/src/app/dashboard/(active)/telegram/users/[id]/card-user-grant.tsx
deleted file mode 100644
index 8e8de0e..0000000
--- a/src/app/dashboard/(active)/telegram/users/[id]/card-user-grant.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-"use client"
-import { format } from "date-fns"
-import { FiZap } from "react-icons/fi"
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
-import type { TgGrant, TgUser } from "@/server/trpc/types"
-import { DeleteGrant } from "../../grants/delete-grant"
-
-export function UserGrantCard({
- user,
- grant,
- //onDelete
-}: {
- user: TgUser
- grant: TgGrant
- //onDelete(): void
-}) {
- return (
-
-
-
- Ongoing Grant
-
- This user has an ongoing grant
-
-
- Start: {format(grant.validSince, "yyyy/MM/dd HH:mm")}
- End: {format(grant.validUntil, "yyyy/MM/dd HH:mm")}
-
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/card-user-info.tsx b/src/app/dashboard/(active)/telegram/users/[id]/card-user-info.tsx
deleted file mode 100644
index 8a44e6b..0000000
--- a/src/app/dashboard/(active)/telegram/users/[id]/card-user-info.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-"use client"
-import { FiStar } from "react-icons/fi"
-import { Badge } from "@/components/ui/badge"
-import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
-import { useSession } from "@/lib/auth"
-import type { ApiOutput } from "@/server/trpc/types"
-import { AddRole } from "./add-role"
-import { RemoveRole } from "./remove-role"
-
-type User = ApiOutput["tg"]["users"]["getByUsername"]["user"]
-type UserRoles = ApiOutput["tg"]["permissions"]["getRoles"]["roles"]
-
-export function UserInfoCard({
- user,
- roles,
- // onUpdate,
-}: {
- user: NonNullable
- roles: UserRoles
- // onUpdate(): void
-}) {
- const sesh = useSession()
- const seshUserId = sesh.data?.user.telegramId
- const isSelf = seshUserId && seshUserId === user.id
-
- return (
-
-
-
- # User ID: {user.id}{" "}
- {isSelf && (
-
- You
-
- )}
-
-
-
-
- Name:
- {user.firstName} {user.lastName ?? ""}
-
-
- Username:
- {user.username}
-
-
- Roles:
- {roles ? roles.map((r) => {r} ) : "N/A"}
-
-
-
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/delete-group-admin.tsx b/src/app/dashboard/(active)/telegram/users/[id]/delete-group-admin.tsx
deleted file mode 100644
index cfcd50b..0000000
--- a/src/app/dashboard/(active)/telegram/users/[id]/delete-group-admin.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { useState } from "react"
-import { FiTrash2 } from "react-icons/fi"
-import { toast } from "sonner"
-import { Spinner } from "@/components/spinner"
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogMedia,
- AlertDialogTitle,
- AlertDialogTrigger,
-} from "@/components/ui/alert-dialog"
-import { Button } from "@/components/ui/button"
-import { delGroupAdmin } from "@/server/actions/users"
-
-export function DeleteGroupAdmin({
- userId,
- chatId,
- // onDelete
-}: {
- userId: number
- chatId: number
- // onDelete(): void
-}) {
- const [open, setOpen] = useState(false)
- const [pending, setPending] = useState(false)
-
- const router = useRouter()
-
- async function deleteGroupAdmin() {
- setPending(true)
-
- try {
- const { error } = await delGroupAdmin(userId, chatId)
-
- if (error === "NOT_FOUND") toast.info("User or group admin not found")
- else if (error === "UNAUTHORIZED") toast.error("You don't have enough permission")
- else if (error === "INTERNAL_SERVER_ERROR") toast.error("There was an internal server error")
- else if (error === "UNAUTHORIZED_SELF_ASSIGN") toast.error("You cannot delete on yourself")
- else {
- toast.success("Group Admin deleted!")
- router.refresh()
- // onDelete()
- }
- } catch (err) {
- toast.error("There was an error")
- console.error(err)
- } finally {
- setOpen(false)
- setPending(false)
- }
- }
-
- return (
-
-
-
- Delete
-
- }
- >
-
-
-
-
-
- Remove Group Admin
- Are you sure you want to remove the group admin?
-
-
- Cancel
-
- {pending ? : "Confirm"}
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/loading.tsx b/src/app/dashboard/(active)/telegram/users/[id]/loading.tsx
deleted file mode 100644
index be3d0ae..0000000
--- a/src/app/dashboard/(active)/telegram/users/[id]/loading.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Skeleton } from "@/components/ui/skeleton"
-
-export default async function Loading() {
- return (
-
-
-
-
-
-
-
-
Admin in groups:
-
-
-
-
-
-
-
-
Last messages (max 12):
-
-
-
-
-
-
-
-
Audit log:
-
- {new Array(9).fill(0).map((_, i) => (
-
- ))}
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/new-group-admin.tsx b/src/app/dashboard/(active)/telegram/users/[id]/new-group-admin.tsx
deleted file mode 100644
index fc7f5e5..0000000
--- a/src/app/dashboard/(active)/telegram/users/[id]/new-group-admin.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-"use client"
-import { useRouter } from "next/navigation"
-import { useState } from "react"
-import { FiPlus, FiSearch, FiX } from "react-icons/fi"
-import { toast } from "sonner"
-import { Button } from "@/components/ui/button"
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger } from "@/components/ui/select"
-import { searchGroup } from "@/server/actions/groups"
-import { addGroupAdmin } from "@/server/actions/users"
-import type { ApiOutput } from "@/server/trpc/types"
-
-type Groups = ApiOutput["tg"]["groups"]["search"]["groups"]
-type User = ApiOutput["tg"]["users"]["getByUsername"]["user"]
-
-export function NewGroupAdmin({
- user,
- alreadyIn,
- // onConfirm
-}: {
- user: User
- alreadyIn: number[]
- // onConfirm(): void
-}) {
- const [open, setOpen] = useState(false)
- const [groupQuery, setGroupQuery] = useState("")
- const [groups, setGroups] = useState([])
- const [selectedGroup, setSelectedGroup] = useState(null)
-
- const router = useRouter()
-
- async function search() {
- const { groups } = await searchGroup(groupQuery)
- setGroups(groups.filter((g) => !alreadyIn.includes(g.telegramId)))
- }
-
- async function submit() {
- if (!selectedGroup) return toast.warning("No group selected, cannot proceed")
- if (!user) return toast.warning("Invalid user, try restarting the dialog")
-
- try {
- const { error } = await addGroupAdmin(user.id, selectedGroup.telegramId)
- if (error) {
- toast.error("You don't have enough permissions")
- } else {
- toast.info(`Group admin added`)
- router.refresh()
- // onConfirm()
- }
- } catch (err) {
- console.error(err)
- toast.error("There was an error, check logs")
- } finally {
- handleOpenChange(false)
- }
- }
-
- function reset() {
- setGroups([])
- setGroupQuery("")
- setSelectedGroup(null)
- }
-
- function handleOpenChange(v: boolean) {
- setOpen(v)
- if (v === false) {
- reset()
- }
- }
-
- return (
-
-
- New
-
- }
- />
-
-
- New Group Admin
- Make the user admin in a group.
-
- {user && (
-
- Target: {user.firstName} {user.username && `@${user.username}`} [{user.id}]
-
- )}
-
-
-
- {groups && (
- ({ value: g, label: g.title }))}
- value={selectedGroup}
- onValueChange={(v) => setSelectedGroup(v)}
- disabled={groups.length === 0}
- >
- {selectedGroup?.title ?? "Select a group"}
-
-
- {groups.map((item) => (
-
- {item.title}
-
- ))}
-
-
-
- )}
-
- Cancel} />
-
- Confirm
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/page.tsx b/src/app/dashboard/(active)/telegram/users/[id]/page.tsx
deleted file mode 100644
index ec6649e..0000000
--- a/src/app/dashboard/(active)/telegram/users/[id]/page.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import { notFound } from "next/navigation"
-import { Suspense } from "react"
-import { getUserDetails } from "@/server/actions/users"
-import { AuditLogCard } from "./card-audit-log"
-import { GroupAdminCard } from "./card-group-admin"
-import { MessageCard } from "./card-message"
-import { UserGrantCard } from "./card-user-grant"
-import { UserInfoCard } from "./card-user-info"
-import { NewGroupAdmin } from "./new-group-admin"
-
-export default async function TgUserDetails({ params }: { params: Promise<{ id: string }> }) {
- const { id } = await params
- const parsedInt = Number(id)
- if (!Number.isInteger(parsedInt) || !Number.isSafeInteger(parsedInt)) notFound()
- const data = await getUserDetails(parsedInt)
-
- if (!data) notFound()
-
- return (
-
- {data && (
- <>
-
-
- {data.grant && }
-
-
-
-
-
Admin in groups:
-
g?.group.id ?? 0) ?? []} />
-
-
-
- {data.groupAdmin
- .filter((m) => m !== null && m !== undefined)
- .map((m) => (
-
- ))}
-
- {data.groupAdmin.length === 0 && (
-
- This user is not group admin in any group.
-
- )}
-
-
-
-
-
Last messages (max 15):
-
- {data.messages?.map((m) => (
-
- ))}
-
- {data.messages?.length === 0 && (
-
- No recent messages sent by this user
-
- )}
-
-
-
-
-
Audit log:
-
- {data.audits.map((m) => (
-
-
-
- ))}
- {data.audits.length === 0 && (
-
- No audit log found for this user
-
- )}
-
-
- >
- )}
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/[id]/remove-role.tsx b/src/app/dashboard/(active)/telegram/users/[id]/remove-role.tsx
deleted file mode 100644
index 261a6b4..0000000
--- a/src/app/dashboard/(active)/telegram/users/[id]/remove-role.tsx
+++ /dev/null
@@ -1,152 +0,0 @@
-"use client"
-import { USER_ROLE } from "@polinetwork/backend"
-import { useRouter } from "next/navigation"
-import { useState } from "react"
-import { FiMinus } from "react-icons/fi"
-import { toast } from "sonner"
-import { Spinner } from "@/components/spinner"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog"
-import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
-import { useSession } from "@/lib/auth"
-import { delUserRole } from "@/server/actions/users"
-import type { TgUser, TgUserRole } from "@/server/trpc/types"
-
-const ARRAY_USER_ROLES = [
- USER_ROLE.ADMIN,
- USER_ROLE.HR,
- USER_ROLE.OWNER,
- USER_ROLE.CREATOR,
- USER_ROLE.DIRETTIVO,
- USER_ROLE.PRESIDENT,
-] as const
-
-export function RemoveRole({
- user,
- alreadyRoles,
- // onDelete,
-}: {
- user: TgUser
- alreadyRoles: TgUserRole[]
- // onDelete(): void
-}) {
- const sesh = useSession()
- const removerId = sesh.data?.user.telegramId
-
- const userRoles = ARRAY_USER_ROLES.filter((r) => alreadyRoles.includes(r)).map((g) => ({
- value: g,
- label: `${g.slice(0, 1).toUpperCase()}${g.slice(1)}`,
- }))
-
- const [open, setOpen] = useState(false)
- const [pending, setPending] = useState(false)
- const [selectedRole, setSelectedRole] = useState(null)
-
- const router = useRouter()
-
- async function submit() {
- if (!removerId) return toast.warning("Invalid session, try reloading the page")
- if (!selectedRole) return toast.warning("No group selected, cannot proceed")
- if (!user) return toast.warning("Invalid user, try restarting the dialog")
- setPending(true)
-
- try {
- const { error } = await delUserRole(user.id, selectedRole)
-
- if (error === "NOT_FOUND") toast.info("User or role not found")
- else if (error === "UNAUTHORIZED") toast.error("You don't have enough permission")
- else if (error === "INTERNAL_SERVER_ERROR") toast.error("There was an internal server error")
- else if (error === "UNAUTHORIZED_SELF_ASSIGN") toast.error("You cannot delete on yourself")
- else {
- toast.success("Role removed!")
- router.refresh()
- // onDelete()
- }
- } catch (err) {
- console.error(err)
- toast.error("There was an error, check logs")
- } finally {
- setPending(false)
- handleOpenChange(false)
- }
- }
-
- function handleOpenChange(v: boolean) {
- setOpen(v)
- if (v === false) {
- setSelectedRole(null)
- }
- }
-
- return (
-
-
- Remove Role
-
- }
- />
-
-
- Remove Role
- Remove a role from the telegram user
-
- {user && (
-
- Target: {user.firstName} {user.username && `@${user.username}`} [{user.id}]
-
- )}
-
- User roles:
- {alreadyRoles.map((r) => (
- {r}
- ))}
-
-
- setSelectedRole(v)}
- disabled={userRoles.length === 0}
- >
-
-
-
-
-
- {userRoles.map((item) => (
-
- {item.label}
-
- ))}
-
-
-
-
-
-
- Cancel
-
- }
- />
-
- {pending ? : "Confirm"}
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/loading.tsx b/src/app/dashboard/(active)/telegram/users/loading.tsx
deleted file mode 100644
index e289840..0000000
--- a/src/app/dashboard/(active)/telegram/users/loading.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { SearchInput } from "@/components/search-input"
-import { Skeleton } from "@/components/ui/skeleton"
-
-export default async function Loading() {
- return (
-
-
-
-
-
-
Telegram ID
-
Username
-
Name
-
Actions
-
- {new Array(12).fill(0).map((_, i) => (
-
- ))}
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/telegram/users/page.tsx b/src/app/dashboard/(active)/telegram/users/page.tsx
deleted file mode 100644
index 79d7a71..0000000
--- a/src/app/dashboard/(active)/telegram/users/page.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import Link from "next/link"
-import { FiEye } from "react-icons/fi"
-import { SearchInput } from "@/components/search-input"
-import { Button } from "@/components/ui/button"
-import { trpc } from "@/server/trpc"
-import type { ApiOutput } from "@/server/trpc/types"
-
-type Users = NonNullable
-
-export default async function TgUsers({ searchParams }: { searchParams: Promise<{ q?: string }> }) {
- const { q } = await searchParams
- const { users } = await trpc.tg.users.getAll.query()
-
- const data = (!q ? users : users?.filter((u) => u.username?.toLowerCase().replace("@", "").startsWith(q))) ?? []
-
- return (
-
-
-
- Count: {users?.length}
-
-
-
-
Telegram ID
-
Username
-
Name
-
Actions
-
- {data.length > 0 ? (
- data.map((r) =>
)
- ) : (
-
No users found
- )}
-
-
- )
-}
-
-function UserRow({ row: r }: { row: Users[number] }) {
- return (
-
-
{r.id}
-
{r.username ? `@${r.username}` : ``}
-
- {r.firstName ?? ""} {r.lastName ?? ""}
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/web/guide/columns.tsx b/src/app/dashboard/(active)/web/guide/columns.tsx
deleted file mode 100644
index 3e3ba00..0000000
--- a/src/app/dashboard/(active)/web/guide/columns.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-"use client"
-import { createColumnHelper } from "@tanstack/react-table"
-import { format } from "date-fns"
-import { DeleteGuide } from "./delete-guide"
-import type { Guide } from "./types"
-
-const ch = createColumnHelper()
-
-export const columns = (onDeleted: (id: number) => void) => [
- ch.accessor("version", {
- id: "version",
- header: "Version",
- cell: ({ getValue }) => {getValue()} ,
- }),
- ch.accessor("date", {
- id: "date",
- header: "Date",
- cell: ({ getValue }) => format(new Date(getValue()), "dd/MM/yyyy"),
- }),
- ch.accessor("file", {
- id: "file",
- header: "File",
- cell: ({ getValue }) => (
-
- Download
-
- ),
- }),
- ch.group({
- id: "actions",
- cell: (props) => (
-
- ),
- }),
-]
diff --git a/src/app/dashboard/(active)/web/guide/create-guide.tsx b/src/app/dashboard/(active)/web/guide/create-guide.tsx
deleted file mode 100644
index 5845298..0000000
--- a/src/app/dashboard/(active)/web/guide/create-guide.tsx
+++ /dev/null
@@ -1,169 +0,0 @@
-"use client"
-
-import { format } from "date-fns"
-import { useState } from "react"
-import { FiChevronDown, FiPlus, FiUpload } from "react-icons/fi"
-import { toast } from "sonner"
-import { Button } from "@/components/ui/button"
-import { Calendar } from "@/components/ui/calendar"
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog"
-import { Field, FieldLabel } from "@/components/ui/field"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
-import { createGuide } from "@/server/actions/guides"
-import type { Guide } from "./types"
-
-export function CreateGuide({
- existingVersions,
- latestVersion,
- onCreated,
-}: {
- existingVersions: string[]
- latestVersion?: string
- onCreated: (guide: Guide) => void
-}) {
- const [open, setOpen] = useState(false)
- const [version, setVersion] = useState(latestVersion ?? "")
- const [date, setDate] = useState(new Date())
- const [datePickerOpen, setDatePickerOpen] = useState(false)
- const [file, setFile] = useState(null)
- const [pending, setPending] = useState(false)
-
- const trimmedVersion = version.trim()
- const isDuplicate = trimmedVersion.length > 0 && existingVersions.includes(trimmedVersion)
- const disabled = !trimmedVersion || !date || !file || isDuplicate || pending
-
- function handleOpenChange(v: boolean) {
- setOpen(v)
- if (v) {
- setVersion(latestVersion ?? "")
- setDate(new Date())
- setFile(null)
- }
- }
-
- async function handleSubmit() {
- if (!date || !file || isDuplicate) return
- setPending(true)
-
- try {
- const formData = new FormData()
- formData.set("version", trimmedVersion)
- formData.set("date", date.toISOString())
- formData.set("file", file)
- const { guide, error } = await createGuide(formData)
-
- if (error === "UNAUTHORIZED") toast.error("You don't have permission to add guides.")
- else if (error === "DUPLICATE_VERSION") toast.error("This version already exists.")
- else if (!guide) toast.error("There was an error creating the guide.")
- else {
- toast.success("Guide created successfully")
- onCreated(guide)
- handleOpenChange(false)
- }
- } catch (err) {
- toast.error("There was an error")
- console.error(err)
- } finally {
- setPending(false)
- }
- }
-
- return (
-
-
- Add Guide
-
- }
- />
-
-
- Add Guide
- Upload a new version of the Guida della Matricola.
-
-
-
-
-
Version
-
setVersion(e.target.value)}
- />
- {isDuplicate &&
This version already exists.
}
-
-
-
- Date
-
-
- {date ? format(date, "dd/MM/yyyy") : "Select date"}
-
-
- }
- />
-
- {
- setDate(d)
- setDatePickerOpen(false)
- }}
- />
-
-
-
-
-
- PDF File
-
-
-
- {file ? file.name : "Choose a PDF file..."}
-
-
- setFile(e.target.files?.[0] ?? null)}
- className="sr-only"
- />
-
-
-
-
- Cancel} />
-
- {pending ? "Saving..." : "Create"}
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/web/guide/delete-guide.tsx b/src/app/dashboard/(active)/web/guide/delete-guide.tsx
deleted file mode 100644
index 9597629..0000000
--- a/src/app/dashboard/(active)/web/guide/delete-guide.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-"use client"
-
-import { useState } from "react"
-import { FiAlertOctagon, FiTrash } from "react-icons/fi"
-import { toast } from "sonner"
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogMedia,
- AlertDialogTitle,
- AlertDialogTrigger,
-} from "@/components/ui/alert-dialog"
-import { Button } from "@/components/ui/button"
-import { deleteGuide } from "@/server/actions/guides"
-
-export function DeleteGuide({
- id,
- version,
- onDeleted,
-}: {
- id: number
- version: string
- onDeleted: (id: number) => void
-}) {
- const [open, setOpen] = useState(false)
-
- async function handleDelete() {
- try {
- const { error } = await deleteGuide(id)
-
- if (error === "UNAUTHORIZED") toast.error("You don't have permission to delete guides.")
- else if (error === "NOT_FOUND") toast.info("This guide was already deleted.")
- else {
- toast.success("Guide deleted successfully")
- onDeleted(id)
- }
- } catch (err) {
- toast.error("There was an error")
- console.error(err)
- } finally {
- setOpen(false)
- }
- }
-
- return (
-
-
-
-
- }
- />
-
-
-
-
-
- Delete Guide
-
- Are you sure you want to delete version {version} ?
- This action cannot be undone.
-
-
-
- Cancel
-
- Confirm
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/web/guide/page.tsx b/src/app/dashboard/(active)/web/guide/page.tsx
deleted file mode 100644
index 8746554..0000000
--- a/src/app/dashboard/(active)/web/guide/page.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { getAllGuides } from "@/server/actions/guides"
-import { GuideTable } from "./table"
-
-export default async function GuideMatricolaPage() {
- const guides = await getAllGuides()
-
- return (
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/web/guide/table.tsx b/src/app/dashboard/(active)/web/guide/table.tsx
deleted file mode 100644
index 860b9cc..0000000
--- a/src/app/dashboard/(active)/web/guide/table.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { useState } from "react"
-import { DataTable } from "@/components/data-table"
-import { columns } from "./columns"
-import { CreateGuide } from "./create-guide"
-import type { Guide } from "./types"
-
-export function GuideTable({ guides }: { guides: Guide[] }) {
- const router = useRouter()
- const [items, setItems] = useState(guides)
-
- function handleCreated(guide: Guide) {
- setItems((prev) => [guide, ...prev])
- router.refresh()
- }
-
- function handleDeleted(id: number) {
- setItems((prev) => prev.filter((g) => g.id !== id))
- router.refresh()
- }
-
- return (
-
-
-
Guida della Matricola
-
-
g.version)}
- latestVersion={items[0]?.version}
- onCreated={handleCreated}
- />
-
-
-
-
- )
-}
diff --git a/src/app/dashboard/(active)/web/guide/types.ts b/src/app/dashboard/(active)/web/guide/types.ts
deleted file mode 100644
index fdc74b2..0000000
--- a/src/app/dashboard/(active)/web/guide/types.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export type Guide = {
- id: number
- version: string
- date: string
- file: string
-}
diff --git a/src/app/dashboard/disabled/page.tsx b/src/app/dashboard/disabled/page.tsx
deleted file mode 100644
index b0c7cba..0000000
--- a/src/app/dashboard/disabled/page.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import Image from "next/image"
-import { redirect } from "next/navigation"
-import accountSvg from "@/assets/svg/account_disabled.svg"
-import { LogoutButton } from "@/components/logout-button"
-import { getServerSession } from "@/server/auth"
-// import { USER_ROLE } from "@/constants";
-
-export default async function AdminInactive() {
- const { data: session } = await getServerSession()
- // if (session?.user.role !== USER_ROLE.DISABLED) redirect("/dashboard");
- // TODO
- if (!session) return redirect("/login")
-
- return (
- session && (
-
-
-
-
- Il tuo account è stato disabilitato
-
-
-
- Il tuo account è stato disabilitato in quanto non sei un membro riconosciuto di PoliNetwork.
-
-
Se credi che sia un errore, manda una richiesta di rivalutazione.
-
-
-
-
- {session.user.name}
- {` <${session.user.email}>`}
-
{" "}
-
-
-
- )
- )
-}
diff --git a/src/app/dashboard/inactive/logout.tsx b/src/app/dashboard/inactive/logout.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/src/app/dashboard/inactive/page.tsx b/src/app/dashboard/inactive/page.tsx
deleted file mode 100644
index 9b36247..0000000
--- a/src/app/dashboard/inactive/page.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import Image from "next/image"
-import { redirect } from "next/navigation"
-import accountSvg from "@/assets/svg/account_inactive.svg"
-import { LogoutButton } from "@/components/logout-button"
-import { getServerSession } from "@/server/auth"
-// import { USER_ROLE } from "@/constants";
-
-export default async function AdminInactive() {
- const { data: session } = await getServerSession()
- if (!session) return redirect("/login")
- // if (session?.user.role !== USER_ROLE.INACTIVE) redirect("/dashboard");
- // TODO
-
- return (
- session && (
-
-
-
-
- Il tuo account deve
- essere abilitato da un dashboard IT
-
-
- Per accedere con GitHub, è necessario che l'account venga abilitato da un dashboard IT, in quanto si
- tratta di una dashboard interna di PoliNetwork.
-
-
- Se sei già un socio di PoliNetwork, puoi accedere direttamente con il tuo indirizzo
- nome.cognome@polinetwork.org tramite "Microsoft Entra ID" e il tuo account sarà automaticamente
- verificato.
-
-
- Se sei entrato con il tuo indirizzo nome.cognome@polinetwork.org e il tuo account risulta inattivo, potrebbe
- essere stato disabilitato. Contatta un dashboard IT per ottenere più informazioni.
-
-
-
-
-
- {session.user.name}
- {` <${session.user.email}>`}
-
{" "}
-
-
-
- )
- )
-}
diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx
deleted file mode 100644
index 54aee61..0000000
--- a/src/app/dashboard/layout.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { redirect } from "next/navigation"
-import { getServerSession } from "@/server/auth"
-import { trpc } from "@/server/trpc"
-
-export default async function AdminLayout({ children }: { children: React.ReactNode }) {
- const session = await getServerSession()
- // console.log(session)
- if (!session.data) redirect("/login")
-
- const tgId = session.data.user.telegramId
- if (!tgId) redirect("/onboarding/link")
- // if (session?.user.role === USER_ROLE.INACTIVE) ;
- // if (session?.user.role === USER_ROLE.DISABLED) redirect("/dashboard/disabled");
-
- const { roles } = await trpc.tg.permissions.getRoles.query({ userId: tgId })
- if (!roles || roles.length === 0) redirect("/onboarding/no-role")
- if (roles.includes("creator")) redirect("/onboarding/unauthorized")
-
- // temp until we have route-based permissions check
- if (!roles.includes("owner") && !roles.includes("direttivo") && !roles.includes("president"))
- redirect("/onboarding/unauthorized")
-
- return children
-}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
deleted file mode 100644
index 02c7f72..0000000
--- a/src/app/layout.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { Metadata } from "next"
-import { Geist, Poppins } from "next/font/google"
-import "@/index.css"
-import { HEADER_HEIGHT } from "@/components/header"
-import { ThemeProvider } from "@/components/theme-provider"
-import { Toaster } from "@/components/ui/sonner"
-import { TooltipProvider } from "@/components/ui/tooltip"
-import { cn } from "@/lib/utils/shadcn"
-
-const geist = Geist({ subsets: ["latin"], variable: "--font-sans" })
-
-const desc = "PoliNetwork Admin Dashboard"
-
-export const metadata: Metadata = {
- title: {
- default: "PoliNetwork Admin",
- template: "%s | PoliNetwork Admin",
- },
- description: desc,
- icons: [{ rel: "icon", url: "/favicon.ico" }],
-}
-
-const poppins = Poppins({ weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"], subsets: ["latin"] })
-
-export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
- return (
-
-
-
-
- {children}
-
-
-
-
-
- )
-}
diff --git a/src/app/page.tsx b/src/app/page.tsx
deleted file mode 100644
index a658b62..0000000
--- a/src/app/page.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { redirect } from "next/navigation"
-import { Header } from "@/components/header"
-import { getServerSession } from "@/server/auth"
-import { CanIAccess } from "./(auth)/login/can-i-access"
-import { WhatIs } from "./(auth)/login/what-is"
-
-export default async function IndexPage() {
- const session = await getServerSession()
- if (session.data?.user) redirect("/dashboard")
-
- return (
-
-
-
-
-
-
-
- )
-}
diff --git a/src/app/testtrpc/page.tsx b/src/app/testtrpc/page.tsx
deleted file mode 100644
index 7e284b7..0000000
--- a/src/app/testtrpc/page.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { testDb } from "@/server/actions/test"
-export const dynamic = "force-dynamic"
-
-export default async function TestTRPCPage() {
- const data = await testDb()
-
- return (
-
-
Test with tRPC. Get the table `test.tg`
-
- {data.map((r, i) => (
-
- {r}
-
- ))}
-
-
- )
-}
diff --git a/src/assets/logo.svg b/src/assets/logo.svg
deleted file mode 100644
index 1b0d8fc..0000000
--- a/src/assets/logo.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/assets/svg/account_disabled.svg b/src/assets/svg/account_disabled.svg
deleted file mode 100644
index e671b24..0000000
--- a/src/assets/svg/account_disabled.svg
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/assets/svg/account_inactive.svg b/src/assets/svg/account_inactive.svg
deleted file mode 100644
index 4df3d97..0000000
--- a/src/assets/svg/account_inactive.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/svg/login-1.svg b/src/assets/svg/login-1.svg
deleted file mode 100644
index 47c1c18..0000000
--- a/src/assets/svg/login-1.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/svg/login-2.svg b/src/assets/svg/login-2.svg
deleted file mode 100644
index 4be8d70..0000000
--- a/src/assets/svg/login-2.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/svg/login-3.svg b/src/assets/svg/login-3.svg
deleted file mode 100644
index f2e900e..0000000
--- a/src/assets/svg/login-3.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/svg/logo.svg b/src/assets/svg/logo.svg
deleted file mode 100644
index fe4f0fb..0000000
--- a/src/assets/svg/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/assets/svg/shapes/big-blue.svg b/src/assets/svg/shapes/big-blue.svg
deleted file mode 100644
index 354ec86..0000000
--- a/src/assets/svg/shapes/big-blue.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/svg/shapes/big-teal.svg b/src/assets/svg/shapes/big-teal.svg
deleted file mode 100644
index ffce061..0000000
--- a/src/assets/svg/shapes/big-teal.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/svg/shapes/looper.svg b/src/assets/svg/shapes/looper.svg
deleted file mode 100644
index 9b118d2..0000000
--- a/src/assets/svg/shapes/looper.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/svg/shapes/small-blue.svg b/src/assets/svg/shapes/small-blue.svg
deleted file mode 100644
index feedfff..0000000
--- a/src/assets/svg/shapes/small-blue.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/components/app-mark.tsx b/src/components/app-mark.tsx
new file mode 100644
index 0000000..62302ea
--- /dev/null
+++ b/src/components/app-mark.tsx
@@ -0,0 +1,44 @@
+import { Link } from "@tanstack/react-router"
+import logoUrl from "@/assets/logo.png"
+import { cn } from "@/lib/utils"
+
+export function AppMark({
+ compact = false,
+ sidebarResponsive = false,
+ className,
+ onClick,
+}: {
+ compact?: boolean
+ sidebarResponsive?: boolean
+ className?: string
+ onClick?: React.MouseEventHandler
+}) {
+ return (
+
+
+ {!compact && (
+
+ PoliNetwork
+
+ Admin dashboard
+
+
+ )}
+
+ )
+}
diff --git a/src/components/code.tsx b/src/components/code.tsx
deleted file mode 100644
index b0a60f8..0000000
--- a/src/components/code.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-"use client"
-
-import { type MouseEvent, type ReactNode, useCallback, useRef } from "react"
-import { toast } from "sonner"
-import { cn } from "@/lib/utils/shadcn"
-
-type Props = React.HTMLAttributes & {
- block?: boolean
- copyOnClick?: boolean
- children: ReactNode
-}
-
-export function Code({ block = false, copyOnClick = false, children, className, ...rest }: Props) {
- const ref = useRef(null)
-
- const copy = useCallback(async (e: MouseEvent) => {
- e.preventDefault()
- try {
- if (!ref.current) return
- await navigator.clipboard.writeText(ref.current.innerText)
- toast.success("Copied to clipboard")
- } catch (err) {
- console.error("Failed to copy code:", err)
- toast.error("Failed to copy code")
- }
- }, [])
-
- const InteractiveWrapper = copyOnClick
- ? ({ children: content }: { children: ReactNode }) => (
-
- {content}
-
- )
- : ({ children: content }: { children: ReactNode }) => <>{content}>
-
- if (block) {
- return (
-
-
-
- {children}
-
-
-
- )
- }
-
- return (
-
-
- {children}
-
-
- )
-}
diff --git a/src/components/dashboard-breadcrumb.tsx b/src/components/dashboard-breadcrumb.tsx
new file mode 100644
index 0000000..9c3f13e
--- /dev/null
+++ b/src/components/dashboard-breadcrumb.tsx
@@ -0,0 +1,78 @@
+import { Link, useRouterState } from "@tanstack/react-router"
+import { Fragment, useMemo } from "react"
+import {
+ Breadcrumb,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbList,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+} from "@/components/ui/breadcrumb"
+import { DASHBOARD_NAV_MAP, type DashboardPath, isDashboardPath } from "./dashboard-navigation"
+
+type DashboardBreadcrumbItem = {
+ title: string
+ url?: DashboardPath
+}
+
+export function DashboardBreadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
+ const pathname = useRouterState({ select: (state) => state.location.pathname })
+ const items = useMemo(() => getBreadcrumbs(pathname), [pathname])
+
+ return (
+
+
+ {items.map((item, index) => {
+ const current = index === items.length - 1
+
+ return (
+
+
+ {current ? (
+ {item.title}
+ ) : item.url ? (
+ }>{item.title}
+ ) : (
+ {item.title}
+ )}
+
+ {!current && }
+
+ )
+ })}
+
+
+ )
+}
+
+function getBreadcrumbs(pathname: string): DashboardBreadcrumbItem[] {
+ const segments = pathname.split("/").filter(Boolean)
+ const breadcrumbs: DashboardBreadcrumbItem[] = []
+ let currentPath = ""
+
+ for (const [index, segment] of segments.entries()) {
+ currentPath += `/${segment}`
+ const mappedTitle = isDashboardPath(currentPath) ? DASHBOARD_NAV_MAP.get(currentPath) : undefined
+ const title = mappedTitle ?? (isIdentifier(segment) ? "Details" : formatSegment(segment))
+
+ breadcrumbs.push({
+ title,
+ // Service categories are organizational labels rather than destinations.
+ url: index !== 1 && isDashboardPath(currentPath) ? currentPath : undefined,
+ })
+ }
+
+ return breadcrumbs
+}
+
+function formatSegment(segment: string) {
+ return segment
+ .split("-")
+ .filter(Boolean)
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
+ .join(" ")
+}
+
+function isIdentifier(segment: string) {
+ return /^\d+$/.test(segment) || segment.length > 20
+}
diff --git a/src/components/dashboard-frame.tsx b/src/components/dashboard-frame.tsx
new file mode 100644
index 0000000..a39941f
--- /dev/null
+++ b/src/components/dashboard-frame.tsx
@@ -0,0 +1,61 @@
+import { Outlet, useRouter } from "@tanstack/react-router"
+import { useEffect, useState } from "react"
+import { toast } from "sonner"
+import { DashboardBreadcrumb } from "@/components/dashboard-breadcrumb"
+import { DashboardSidebar } from "@/components/dashboard-sidebar"
+import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
+import type { AdminSession } from "@/lib/auth"
+import { auth, useSession } from "@/lib/auth"
+import { ThemeToggle } from "./theme-toggle"
+
+export function DashboardFrame({ initialSession }: { initialSession: AdminSession }) {
+ const [sidebarOpen, setSidebarOpen] = useState(true)
+ const [loggingOut, setLoggingOut] = useState(false)
+ const router = useRouter()
+ const sessionQuery = useSession()
+ const liveSession = sessionQuery.data
+ const session = liveSession === undefined ? initialSession : liveSession
+ const user = session?.user
+
+ useEffect(() => {
+ const storedState = document.cookie
+ .split("; ")
+ .find((cookie) => cookie.startsWith("sidebar_state="))
+ ?.split("=")[1]
+ if (storedState === "false") setSidebarOpen(false)
+ }, [])
+
+ async function logout() {
+ if (loggingOut) return
+ setLoggingOut(true)
+ try {
+ const result = await auth.signOut()
+ if (result.error) throw new Error(result.error.message)
+ await router.invalidate()
+ await router.navigate({ to: "/login", replace: true })
+ } catch {
+ toast.error("Could not sign out. Please try again.")
+ setLoggingOut(false)
+ }
+ }
+
+ return (
+
+ void logout()} />
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/dashboard-navigation.ts b/src/components/dashboard-navigation.ts
new file mode 100644
index 0000000..428c2ae
--- /dev/null
+++ b/src/components/dashboard-navigation.ts
@@ -0,0 +1,84 @@
+import {
+ BookOpen,
+ Database,
+ Globe,
+ LayoutDashboard,
+ type LucideIcon,
+ Settings,
+ ShieldCheck,
+ UsersRound,
+} from "lucide-react"
+import azureIcon from "@/assets/svg/azure.svg"
+import telegramIcon from "@/assets/svg/telegram.svg"
+
+export type DashboardNavigationItem = {
+ title: string
+ url: string
+ icon: LucideIcon
+}
+
+export type DashboardNavigationCategory = {
+ title: string
+ icon: LucideIcon
+ iconSrc?: string
+ items: readonly DashboardNavigationItem[]
+}
+
+export const overviewNavigation = {
+ title: "Overview",
+ url: "/dashboard",
+ icon: LayoutDashboard,
+} as const satisfies DashboardNavigationItem
+
+export const dashboardNavigation = [
+ {
+ title: "Telegram",
+ icon: UsersRound,
+ iconSrc: telegramIcon,
+ items: [
+ { title: "Users", url: "/dashboard/telegram/users", icon: UsersRound },
+ { title: "Groups", url: "/dashboard/telegram/groups", icon: Database },
+ { title: "Grants", url: "/dashboard/telegram/grants", icon: ShieldCheck },
+ ],
+ },
+ {
+ title: "Azure",
+ icon: UsersRound,
+ iconSrc: azureIcon,
+ items: [
+ { title: "Groups", url: "/dashboard/azure/groups", icon: Database },
+ { title: "Members", url: "/dashboard/azure/members", icon: UsersRound },
+ ],
+ },
+ {
+ title: "Web",
+ icon: Globe,
+ iconSrc: undefined,
+ items: [{ title: "Guides", url: "/dashboard/web/guides", icon: BookOpen }],
+ },
+] as const satisfies readonly DashboardNavigationCategory[]
+
+export const accountNavigation = {
+ title: "Account",
+ url: "/dashboard/account",
+ icon: Settings,
+} as const satisfies DashboardNavigationItem
+
+type CategoryNavigationItem = (typeof dashboardNavigation)[number]["items"][number]
+
+export type DashboardPath = typeof overviewNavigation.url | CategoryNavigationItem["url"] | typeof accountNavigation.url
+
+const navigationEntries: [DashboardPath, string][] = [
+ [overviewNavigation.url, overviewNavigation.title],
+ [accountNavigation.url, accountNavigation.title],
+]
+
+for (const category of dashboardNavigation) {
+ for (const item of category.items) navigationEntries.push([item.url, item.title])
+}
+
+export const DASHBOARD_NAV_MAP = new Map(navigationEntries)
+
+export function isDashboardPath(path: string): path is DashboardPath {
+ return DASHBOARD_NAV_MAP.has(path as DashboardPath)
+}
diff --git a/src/components/dashboard-sidebar.tsx b/src/components/dashboard-sidebar.tsx
new file mode 100644
index 0000000..4173632
--- /dev/null
+++ b/src/components/dashboard-sidebar.tsx
@@ -0,0 +1,264 @@
+import { Link, useRouterState } from "@tanstack/react-router"
+import { ChevronRight, LogOut } from "lucide-react"
+import { useEffect, useState } from "react"
+import type { AdminSession } from "@/lib/auth"
+import { cn } from "@/lib/utils"
+import { AppMark } from "./app-mark"
+import { accountNavigation, dashboardNavigation, overviewNavigation } from "./dashboard-navigation"
+import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "./ui/dropdown-menu"
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarHeader,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarRail,
+ useSidebar,
+} from "./ui/sidebar"
+
+const categoryStorageKey = "polinetwork-sidebar-categories"
+
+// Shared height and visual treatment for every primary nav row.
+const navItemClass = "h-10 gap-2.5 rounded-lg px-2.5"
+
+// The single strong "selected page" treatment: a controlled PoliNetwork-blue surface.
+const selectedPageClass =
+ "data-active:bg-sidebar-primary/15 data-active:font-medium data-active:text-sidebar-accent-foreground data-active:hover:bg-sidebar-primary/20"
+
+type DashboardSidebarProps = React.ComponentProps & {
+ user: AdminSession["user"] | undefined
+ loggingOut: boolean
+ onLogout: () => void
+}
+
+export function DashboardSidebar({ user, loggingOut, onLogout, ...props }: DashboardSidebarProps) {
+ const pathname = useRouterState({
+ select: (state) => state.location.pathname,
+ })
+ const { setOpenMobile } = useSidebar()
+ const [categoryState, setCategoryState] = useState>({})
+
+ useEffect(() => {
+ try {
+ const stored = window.localStorage.getItem(categoryStorageKey)
+ if (stored) setCategoryState(JSON.parse(stored))
+ } catch {
+ // Ignore malformed or unavailable local storage and use route-based defaults.
+ }
+ }, [])
+
+ function persistCategory(title: string, open: boolean) {
+ setCategoryState((current) => {
+ const next = { ...current, [title]: open }
+ try {
+ window.localStorage.setItem(categoryStorageKey, JSON.stringify(next))
+ } catch {
+ // The in-memory state still works when storage is unavailable.
+ }
+ return next
+ })
+ }
+
+ const accountActive = isPathActive(pathname, accountNavigation.url)
+
+ return (
+
+
+ setOpenMobile(false)} className="min-w-0" />
+
+
+
+
+
+
+ setOpenMobile(false)} />}
+ className={cn(navItemClass, selectedPageClass)}
+ >
+
+ {overviewNavigation.title}
+
+
+
+ {dashboardNavigation.map((category) => {
+ const itemActiveStates = category.items.map((item) => isPathActive(pathname, item.url))
+ const routeIsActive = itemActiveStates.some(Boolean)
+ const open = categoryState[category.title] ?? routeIsActive
+ const connectorHeight = category.items.length * 38 + 2
+ const itemCenters = category.items.map((_, index) => 20 + index * 38)
+ const lastItemIndex = category.items.length - 1
+ const lastItemCenter = itemCenters[lastItemIndex]
+ const inactiveBranches = itemCenters
+ .map((center, index) =>
+ !itemActiveStates[index] && index !== lastItemIndex ? `M 0 ${center} H 10` : ""
+ )
+ .filter(Boolean)
+ .join(" ")
+ const railPath = [
+ `M 0 2 V ${lastItemCenter}`,
+ !itemActiveStates[lastItemIndex] ? "H 10" : "",
+ inactiveBranches,
+ ]
+ .filter(Boolean)
+ .join(" ")
+
+ return (
+ persistCategory(category.title, nextOpen)}
+ className="group/collapsible"
+ >
+
+ }
+ className={cn(
+ navItemClass,
+ "text-sidebar-foreground/80",
+ routeIsActive && "bg-sidebar-accent/40 text-sidebar-accent-foreground"
+ )}
+ >
+ {category.iconSrc ? (
+
+ ) : (
+
+ )}
+ {category.title}
+
+
+
+
+
+
+ {itemCenters.map((center, index) =>
+ itemActiveStates[index] ? (
+
+
+
+
+
+ ) : null
+ )}
+
+ {category.items.map((item, index) => {
+ const itemIsActive = itemActiveStates[index]
+
+ return (
+
+ setOpenMobile(false)} />}
+ className={cn(
+ "h-9 gap-2.5 rounded-lg pl-2.5 text-sidebar-foreground/70",
+ "data-active:bg-sidebar-primary/15 data-active:font-medium data-active:text-sidebar-accent-foreground data-active:hover:bg-sidebar-primary/20",
+ "[&>svg]:text-sidebar-foreground/50 data-active:[&>svg]:text-sidebar-primary"
+ )}
+ >
+
+ {item.title}
+
+
+ )
+ })}
+
+
+
+
+ )
+ })}
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+ {user?.image && }
+
+ {initials(user?.name, user?.email)}
+
+
+
+ {user?.name || "PoliNetwork member"}
+
+
+
+
+ setOpenMobile(false)} />}
+ className="h-10 gap-2.5 px-2.5 py-0"
+ >
+
+ Settings
+
+
+
+ {loggingOut ? "Signing out…" : "Sign out"}
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function initials(name?: string | null, email?: string) {
+ const source = name?.trim() || email?.split("@")[0] || "User"
+ return source
+ .split(/[\s._-]+/)
+ .slice(0, 2)
+ .map((part) => part[0]?.toUpperCase())
+ .join("")
+}
+
+function isPathActive(pathname: string, url: string, exact = false) {
+ const normalizedPathname = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname
+ const normalizedUrl = url.length > 1 ? url.replace(/\/+$/, "") : url
+
+ return normalizedPathname === normalizedUrl || (!exact && normalizedPathname.startsWith(`${normalizedUrl}/`))
+}
diff --git a/src/components/dashboard-sidebar/data.tsx b/src/components/dashboard-sidebar/data.tsx
deleted file mode 100644
index 0fa52ae..0000000
--- a/src/components/dashboard-sidebar/data.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import Image from "next/image"
-import { FiBookOpen, FiGlobe, FiMessageSquare, FiUsers, FiZap } from "react-icons/fi"
-import azureSvg from "@/assets/svg/azure.svg"
-import telegramSvg from "@/assets/svg/telegram.svg"
-
-export const DSData = {
- mainNav: [
- {
- title: "Telegram",
- icon: ,
- items: [
- { title: "Grants", url: "/dashboard/telegram/grants", icon: },
- { title: "Groups", url: "/dashboard/telegram/groups", icon: },
- { title: "Users", url: "/dashboard/telegram/users", icon: },
- ],
- },
- {
- title: "Azure",
- icon: ,
- items: [
- { title: "Groups", url: "/dashboard/azure/groups", icon: },
- { title: "Members", url: "/dashboard/azure/members", icon: },
- ],
- },
- {
- title: "Web",
- icon: ,
- items: [{ title: "Guide", url: "/dashboard/web/guide", icon: }],
- },
- ],
-}
-
-const flattenNavigation = (): Map => {
- const map = new Map()
- const traverse = (list: { title: string; url?: string }[]) => {
- for (const item of list) {
- if (!item.url) continue
- map.set(item.url, item.title)
- }
- }
- Object.entries(DSData).forEach(([_k, nav]) => {
- nav.forEach((category) => {
- traverse(category.items)
- })
- })
- return map
-}
-
-export const NAV_MAP = flattenNavigation()
diff --git a/src/components/dashboard-sidebar/index.tsx b/src/components/dashboard-sidebar/index.tsx
deleted file mode 100644
index 4fb0080..0000000
--- a/src/components/dashboard-sidebar/index.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-"use client"
-
-import Link from "next/link"
-import type * as React from "react"
-import {
- Sidebar,
- SidebarContent,
- SidebarFooter,
- SidebarHeader,
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
-} from "@/components/ui/sidebar"
-import { Logo } from "../logo"
-import { DSMainNav } from "./main-nav"
-import { DSUserNav } from "./user-nav"
-
-export function DashboardSidebar({
- categoryState,
- ...props
-}: React.ComponentProps & { categoryState: Record }) {
- return (
-
-
-
-
- }>
-
-
- PoliNetwork APS
- Admin Dashboard
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/components/dashboard-sidebar/main-nav.tsx b/src/components/dashboard-sidebar/main-nav.tsx
deleted file mode 100644
index b61f719..0000000
--- a/src/components/dashboard-sidebar/main-nav.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-"use client"
-import Link from "next/link"
-import { usePathname } from "next/navigation"
-import { useState } from "react"
-import { FiChevronRight } from "react-icons/fi"
-import { COOKIES } from "@/constants"
-import { useCookieStorage } from "@/hooks/use-cookie-storage"
-import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible"
-import {
- SidebarGroup,
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
-} from "../ui/sidebar"
-import { DSData } from "./data"
-
-export function DSMainNav({ categoryState }: { categoryState: Record }) {
- const [_, setCategoryState] = useCookieStorage>(
- COOKIES.SIDEBAR_CATEGORY_STATE,
- {},
- { expires: 60 * 60 * 24 * 7 }
- )
-
- return (
-
-
- {DSData.mainNav.map((category) => (
- {
- setCategoryState((state) => ({ ...state, [category.title]: open }))
- }}
- />
- ))}
-
-
- )
-}
-
-function DSMenuCategory({
- category,
- initialOpen,
- onPersistOpen,
-}: {
- category: (typeof DSData)["mainNav"][0]
- initialOpen?: boolean
- onPersistOpen: (open: boolean) => void
-}) {
- const pathname = usePathname()
- const categoryUrl = category.items[0]?.url.split("/").slice(0, 3).join("/")
- const [open, setOpen] = useState(initialOpen ?? (categoryUrl ? pathname.startsWith(categoryUrl) : false))
-
- function handleOpenChange(open: boolean) {
- setOpen(open)
- onPersistOpen(open)
- }
-
- return (
- } open={open} onOpenChange={handleOpenChange} className="group/collapsible">
-
- {category.icon}
- {category.title}
-
-
- }
- />
-
- {category.items?.length ? (
-
- {category.items.map((item) => (
-
- ))}
-
- ) : null}
-
-
- )
-}
-
-function DSMenuItem({ item }: { item: (typeof DSData)["mainNav"][0]["items"][0] }) {
- const path = usePathname()
-
- // NOTE: as of now, we have only 1 level depth of submenu, so using startsWith to
- // match also subroutes is ok.
- // If we go with multiple levels of depth it should be changed accordingly.
- const isActive = path.startsWith(item.url)
-
- return (
-
- }>
- {item.icon}
- {item.title}
-
-
- )
-}
diff --git a/src/components/dashboard-sidebar/user-nav.tsx b/src/components/dashboard-sidebar/user-nav.tsx
deleted file mode 100644
index ca07cdc..0000000
--- a/src/components/dashboard-sidebar/user-nav.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-"use client"
-
-import Link from "next/link"
-import { useRouter } from "next/navigation"
-import { FiBell, FiChevronsDown, FiLogOut, FiUser } from "react-icons/fi"
-import { toast } from "sonner"
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from "@/components/ui/sidebar"
-import { signOut, useSession } from "@/lib/auth"
-import { getInitials } from "@/lib/utils"
-import { Skeleton } from "../ui/skeleton"
-
-export function DSUserNav() {
- const { data } = useSession()
- const { isMobile } = useSidebar()
- const router = useRouter()
-
- const user = data?.user
-
- if (!user) return
-
- return (
-
-
-
-
-
-
- {user.name ? getInitials(user.name) : }
-
-
- {user.name}
- {user.email}
-
-
-
- }
- />
-
-
-
-
-
-
- {user.name ? getInitials(user.name) : }
-
-
- {user.name}
- {user.email}
-
-
-
-
-
-
-
-
-
- Account
-
-
-
-
- Notifications
-
-
-
-
- signOut({
- fetchOptions: {
- onSuccess: () => {
- toast.success("Logged out!")
- router.refresh()
- },
- },
- })
- }
- >
-
- Log out
-
-
-
-
-
- )
-}
diff --git a/src/components/data-table.tsx b/src/components/data-table.tsx
deleted file mode 100644
index c97f7bc..0000000
--- a/src/components/data-table.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-"use client"
-
-import {
- type ColumnDef,
- flexRender,
- getCoreRowModel,
- getSortedRowModel,
- type RowData,
- useReactTable,
-} from "@tanstack/react-table"
-
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
-
-interface DataTableProps {
- // biome-ignore lint/suspicious/noExplicitAny: columns commonly mix accessor value types (string, number, group columns...)
- columns: ColumnDef[]
- data: TData[]
-}
-
-export function DataTable({ columns, data }: DataTableProps) {
- const table = useReactTable({
- data,
- columns,
- getCoreRowModel: getCoreRowModel(),
- getSortedRowModel: getSortedRowModel(),
- })
-
- return (
-
-
-
- {table.getHeaderGroups().map((headerGroup) => (
-
- {headerGroup.headers.map((header) => {
- return (
-
- {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
-
- )
- })}
-
- ))}
-
-
- {table.getRowModel().rows.length ? (
- table.getRowModel().rows.map((row) => (
-
- {row.getVisibleCells().map((cell) => (
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
- ))}
-
- ))
- ) : (
-
-
- No results.
-
-
- )}
-
-
-
- )
-}
diff --git a/src/components/data-toolbar.tsx b/src/components/data-toolbar.tsx
new file mode 100644
index 0000000..c61b4d0
--- /dev/null
+++ b/src/components/data-toolbar.tsx
@@ -0,0 +1,86 @@
+import { Search, X } from "lucide-react"
+import { type ReactNode, useDeferredValue, useEffect, useId, useRef, useState } from "react"
+import { PageHeader } from "@/components/page-header"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+
+export function DataToolbar({
+ title,
+ description,
+ count,
+ total,
+ onSearch,
+ searchPlaceholder,
+ action,
+ children,
+ eyebrow = "Directory",
+}: {
+ title: string
+ description: string
+ count: number
+ total?: number
+ onSearch?: (value: string) => void
+ searchPlaceholder?: string
+ action?: ReactNode
+ children?: ReactNode
+ eyebrow?: string
+}) {
+ const searchId = useId()
+ const [searchValue, setSearchValue] = useState("")
+ const deferredSearchValue = useDeferredValue(searchValue)
+ const onSearchRef = useRef(onSearch)
+ onSearchRef.current = onSearch
+
+ useEffect(() => {
+ onSearchRef.current?.(deferredSearchValue)
+ }, [deferredSearchValue])
+
+ const resultLabel =
+ total !== undefined && total !== count
+ ? `${count.toLocaleString()} of ${total.toLocaleString()}`
+ : count.toLocaleString()
+
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/src/components/empty-state.tsx b/src/components/empty-state.tsx
new file mode 100644
index 0000000..3e106b0
--- /dev/null
+++ b/src/components/empty-state.tsx
@@ -0,0 +1,28 @@
+import type { LucideIcon } from "lucide-react"
+import type { ReactNode } from "react"
+import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty"
+
+export function EmptyState({
+ icon: Icon,
+ title,
+ text,
+ action,
+}: {
+ icon: LucideIcon
+ title: string
+ text: string
+ action?: ReactNode
+}) {
+ return (
+
+
+
+
+
+ {title}
+ {text}
+
+ {action && {action} }
+
+ )
+}
diff --git a/src/components/header-login-button.tsx b/src/components/header-login-button.tsx
deleted file mode 100644
index 62057da..0000000
--- a/src/components/header-login-button.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-"use client"
-import Link from "next/link"
-import { usePathname, useRouter } from "next/navigation"
-import { FiLogIn, FiLogOut } from "react-icons/fi"
-import { useIsMobile } from "@/hooks/use-mobile"
-import { auth, useSession } from "@/lib/auth"
-import { Button } from "./ui/button"
-import { Skeleton } from "./ui/skeleton"
-
-export function HeaderLoginButton() {
- const { data, isPending } = useSession()
- const pathname = usePathname()
- const isMobile = useIsMobile()
- const router = useRouter()
-
- if (isPending) return
- if (data)
- return (
- {
- await auth.signOut()
- router.refresh()
- }}
- >
- Logout
-
- )
-
- return (
-
-
-
- Login
-
-
- )
-}
diff --git a/src/components/header.tsx b/src/components/header.tsx
deleted file mode 100644
index d007f74..0000000
--- a/src/components/header.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import Link from "next/link"
-import { FiGlobe } from "react-icons/fi"
-import { ThemeButton } from "@/components/theme-button"
-import { HeaderLoginButton } from "./header-login-button"
-import { Logo } from "./logo"
-import { Button } from "./ui/button"
-
-export const HEADER_HEIGHT = "3.3rem"
-
-export async function Header() {
- return (
-
- )
-}
diff --git a/src/components/input-prefix.tsx b/src/components/input-prefix.tsx
deleted file mode 100644
index cf3c422..0000000
--- a/src/components/input-prefix.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import * as React from "react"
-import { cn } from "@/lib/utils/shadcn"
-import { Input } from "./ui/input"
-
-export interface InputWithPrefixProps extends React.InputHTMLAttributes {
- prefix: string
- containerClassName?: string
-}
-
-const InputWithPrefix = React.forwardRef(
- ({ className, type, prefix, containerClassName, ...props }, ref) => {
- return (
-
- )
- }
-)
-InputWithPrefix.displayName = "InputWithPrefix"
-
-export { InputWithPrefix }
diff --git a/src/components/loading-skeleton.tsx b/src/components/loading-skeleton.tsx
new file mode 100644
index 0000000..5a187d3
--- /dev/null
+++ b/src/components/loading-skeleton.tsx
@@ -0,0 +1,93 @@
+import { Skeleton } from "@/components/ui/skeleton"
+
+export function DataPageSkeleton({
+ columns = 4,
+ rows = 7,
+ withTabs = false,
+}: {
+ columns?: number
+ rows?: number
+ withTabs?: boolean
+}) {
+ return (
+
+
Loading page data…
+
+
+
+
+
+
+
+
+
+ {withTabs && (
+
+
+
+
+
+ )}
+
+
+ {Array.from({ length: columns }, (_, index) => (
+
+ ))}
+
+ {Array.from({ length: rows }, (_, row) => (
+
+ {Array.from({ length: columns }, (_, column) => (
+
+ ))}
+
+ ))}
+
+
+ )
+}
+
+export function DetailPageSkeleton() {
+ return (
+
+
Loading user details…
+
+
+
+ {Array.from({ length: 3 }, (_, index) => (
+
+
+
+
+
+
+ ))}
+
+ {Array.from({ length: 3 }, (_, section) => (
+
+ ))}
+
+ )
+}
diff --git a/src/components/logo.tsx b/src/components/logo.tsx
deleted file mode 100644
index f51fb95..0000000
--- a/src/components/logo.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import Image, { type ImageProps } from "next/image"
-import logo from "@/assets/logo.svg"
-
-export function Logo({ size, ...props }: { size?: number } & Omit) {
- return
-}
diff --git a/src/components/logout-button.tsx b/src/components/logout-button.tsx
deleted file mode 100644
index de99534..0000000
--- a/src/components/logout-button.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { redirect } from "next/navigation"
-import { FiLogOut } from "react-icons/fi"
-import { Button } from "@/components/ui/button"
-import { signOut } from "@/lib/auth"
-
-export function LogoutButton() {
- return (
- {
- await signOut()
- redirect("/login")
- }}
- variant="destructive"
- type="submit"
- >
-
- Logout
-
- )
-}
diff --git a/src/components/navigate-back.tsx b/src/components/navigate-back.tsx
deleted file mode 100644
index febb551..0000000
--- a/src/components/navigate-back.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { FiArrowLeft } from "react-icons/fi"
-import { Button } from "./ui/button"
-
-export function NavigateBack() {
- const router = useRouter()
-
- return (
- {
- router.back()
- }}
- >
-
-
- )
-}
diff --git a/src/components/page-header.tsx b/src/components/page-header.tsx
new file mode 100644
index 0000000..eb93b99
--- /dev/null
+++ b/src/components/page-header.tsx
@@ -0,0 +1,26 @@
+import type { ReactNode } from "react"
+
+export function PageHeader({
+ eyebrow,
+ title,
+ description,
+ action,
+}: {
+ eyebrow: string
+ title: string
+ description: string
+ action?: ReactNode
+}) {
+ return (
+
+
+
{eyebrow}
+
+ {title}
+
+
{description}
+
+ {action && {action}
}
+
+ )
+}
diff --git a/src/components/pagination.tsx b/src/components/pagination.tsx
new file mode 100644
index 0000000..0985a02
--- /dev/null
+++ b/src/components/pagination.tsx
@@ -0,0 +1,131 @@
+import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, MoreHorizontal } from "lucide-react"
+import { Fragment } from "react"
+import { Button } from "@/components/ui/button"
+
+export function Pagination({
+ page,
+ pageCount,
+ onPageChange,
+ pageSize,
+ total,
+ pageSizeOptions = [10, 20, 25, 50, 100],
+ onPageSizeChange,
+}: {
+ page: number
+ pageCount: number
+ onPageChange: (page: number) => void
+ pageSize: number
+ total?: number
+ pageSizeOptions?: number[]
+ onPageSizeChange: (pageSize: number) => void
+}) {
+ const pages = [...new Set([1, page - 1, page, page + 1, pageCount])]
+ .filter((value) => value >= 1 && value <= pageCount)
+ .sort((a, b) => a - b)
+ const rangeStart = total ? (page - 1) * pageSize + 1 : 0
+ const rangeEnd = total ? Math.min(page * pageSize, total) : 0
+
+ return (
+
+
+
+ Rows per page
+ onPageSizeChange(Number(event.target.value))}
+ className="h-11 rounded-lg border border-input bg-card px-2 text-xs text-foreground outline-none focus:border-ring focus:ring-3 focus:ring-ring/20"
+ >
+ {pageSizeOptions.map((size) => (
+
+ {size}
+
+ ))}
+
+
+ {total !== undefined && (
+
+ Showing {rangeStart.toLocaleString()}–{rangeEnd.toLocaleString()} of {total.toLocaleString()}
+
+ )}
+
+ {pageCount > 1 && (
+
+
+
+ onPageChange(1)}>
+
+
+
+
+ onPageChange(page - 1)}>
+
+
+
+ {pages.map((value, index) => (
+
+ {index > 0 && pages[index - 1] !== value - 1 && (
+
+
+
+ )}
+
+ onPageChange(value)}
+ >
+ {value}
+
+
+
+ ))}
+
+ onPageChange(page + 1)}>
+
+
+
+
+ onPageChange(pageCount)}>
+
+
+
+
+
+ )}
+
+ )
+}
+
+function PageButton({
+ label,
+ disabled,
+ onClick,
+ children,
+}: {
+ label: string
+ disabled: boolean
+ onClick: () => void
+ children: React.ReactNode
+}) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/components/reui/stepper.tsx b/src/components/reui/stepper.tsx
new file mode 100644
index 0000000..720f4fc
--- /dev/null
+++ b/src/components/reui/stepper.tsx
@@ -0,0 +1,421 @@
+import { mergeProps } from "@base-ui/react/merge-props"
+import { useRender } from "@base-ui/react/use-render"
+import {
+ Children,
+ createContext,
+ type HTMLAttributes,
+ isValidElement,
+ type ReactElement,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react"
+
+import { cn } from "@/lib/utils"
+
+// Types
+type StepperOrientation = "horizontal" | "vertical"
+type StepState = "active" | "completed" | "inactive" | "loading"
+type StepIndicators = {
+ active?: React.ReactNode
+ completed?: React.ReactNode
+ inactive?: React.ReactNode
+ loading?: React.ReactNode
+}
+
+interface StepperContextValue {
+ activeStep: number
+ setActiveStep: (step: number) => void
+ stepsCount: number
+ orientation: StepperOrientation
+ registerTrigger: (node: HTMLButtonElement) => void
+ unregisterTrigger: (node: HTMLButtonElement) => void
+ triggerNodes: HTMLButtonElement[]
+ focusNext: (currentIdx: number) => void
+ focusPrev: (currentIdx: number) => void
+ focusFirst: () => void
+ focusLast: () => void
+ indicators: StepIndicators
+}
+
+interface StepItemContextValue {
+ step: number
+ state: StepState
+ isDisabled: boolean
+ isLoading: boolean
+}
+
+const StepperContext = createContext(undefined)
+const StepItemContext = createContext(undefined)
+
+function useStepper() {
+ const ctx = useContext(StepperContext)
+ if (!ctx) throw new Error("useStepper must be used within a Stepper")
+ return ctx
+}
+
+function useStepItem() {
+ const ctx = useContext(StepItemContext)
+ if (!ctx) throw new Error("useStepItem must be used within a StepperItem")
+ return ctx
+}
+
+interface StepperProps extends HTMLAttributes {
+ defaultValue?: number
+ value?: number
+ onValueChange?: (value: number) => void
+ orientation?: StepperOrientation
+ indicators?: StepIndicators
+}
+
+function Stepper({
+ defaultValue = 1,
+ value,
+ onValueChange,
+ orientation = "horizontal",
+ className,
+ children,
+ indicators = {},
+ ...props
+}: StepperProps) {
+ const [activeStep, setActiveStep] = useState(defaultValue)
+ const [triggerNodes, setTriggerNodes] = useState([])
+
+ // Register/unregister triggers
+ const registerTrigger = useCallback((node: HTMLButtonElement) => {
+ setTriggerNodes((prev) => {
+ if (prev.includes(node)) return prev
+ return [...prev, node]
+ })
+ }, [])
+ const unregisterTrigger = useCallback((node: HTMLButtonElement) => {
+ setTriggerNodes((prev) => prev.filter((trigger) => trigger !== node))
+ }, [])
+
+ const handleSetActiveStep = useCallback(
+ (step: number) => {
+ if (value === undefined) {
+ setActiveStep(step)
+ }
+ onValueChange?.(step)
+ },
+ [value, onValueChange]
+ )
+
+ const currentStep = value ?? activeStep
+
+ // Keyboard navigation logic
+ const focusTrigger = (idx: number) => {
+ if (triggerNodes[idx]) triggerNodes[idx].focus()
+ }
+ const focusNext = (currentIdx: number) => focusTrigger((currentIdx + 1) % triggerNodes.length)
+ const focusPrev = (currentIdx: number) => focusTrigger((currentIdx - 1 + triggerNodes.length) % triggerNodes.length)
+ const focusFirst = () => focusTrigger(0)
+ const focusLast = () => focusTrigger(triggerNodes.length - 1)
+
+ // Context value
+ const contextValue = useMemo(
+ () => ({
+ activeStep: currentStep,
+ setActiveStep: handleSetActiveStep,
+ stepsCount: Children.toArray(children).filter(
+ (child): child is ReactElement =>
+ isValidElement(child) && (child.type as { displayName?: string }).displayName === "StepperItem"
+ ).length,
+ orientation,
+ registerTrigger,
+ unregisterTrigger,
+ focusNext,
+ focusPrev,
+ focusFirst,
+ focusLast,
+ triggerNodes,
+ indicators,
+ }),
+ [currentStep, handleSetActiveStep, children, orientation, registerTrigger, unregisterTrigger, triggerNodes]
+ )
+
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+interface StepperItemProps extends React.HTMLAttributes {
+ step: number
+ completed?: boolean
+ disabled?: boolean
+ loading?: boolean
+}
+
+function StepperItem({
+ step,
+ completed = false,
+ disabled = false,
+ loading = false,
+ className,
+ children,
+ ...props
+}: StepperItemProps) {
+ const { activeStep } = useStepper()
+
+ const state: StepState = completed || step < activeStep ? "completed" : activeStep === step ? "active" : "inactive"
+
+ const isLoading = loading && step === activeStep
+
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+type StepperTriggerProps = useRender.ComponentProps<"button">
+
+function StepperTrigger({ className, children, tabIndex, render, ...props }: StepperTriggerProps) {
+ const { state, isLoading } = useStepItem()
+ const stepperCtx = useStepper()
+ const {
+ setActiveStep,
+ activeStep,
+ registerTrigger,
+ unregisterTrigger,
+ triggerNodes,
+ focusNext,
+ focusPrev,
+ focusFirst,
+ focusLast,
+ } = stepperCtx
+ const { step, isDisabled } = useStepItem()
+ const isSelected = activeStep === step
+ const id = `stepper-tab-${step}`
+ const panelId = `stepper-panel-${step}`
+
+ // Register this trigger for keyboard navigation
+ const btnRef = useRef(null)
+ useEffect(() => {
+ const node = btnRef.current
+ if (!node) return
+ registerTrigger(node)
+ return () => unregisterTrigger(node)
+ }, [registerTrigger, unregisterTrigger])
+
+ // Find our index among triggers for navigation
+ const myIdx = useMemo(() => triggerNodes.findIndex((n: HTMLButtonElement) => n === btnRef.current), [triggerNodes])
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ switch (e.key) {
+ case "ArrowRight":
+ case "ArrowDown":
+ e.preventDefault()
+ if (myIdx !== -1 && focusNext) focusNext(myIdx)
+ break
+ case "ArrowLeft":
+ case "ArrowUp":
+ e.preventDefault()
+ if (myIdx !== -1 && focusPrev) focusPrev(myIdx)
+ break
+ case "Home":
+ e.preventDefault()
+ if (focusFirst) focusFirst()
+ break
+ case "End":
+ e.preventDefault()
+ if (focusLast) focusLast()
+ break
+ case "Enter":
+ case " ":
+ e.preventDefault()
+ setActiveStep(step)
+ break
+ }
+ }
+
+ const defaultProps = {
+ role: "tab",
+ id,
+ "aria-selected": isSelected,
+ "aria-controls": panelId,
+ tabIndex: typeof tabIndex === "number" ? tabIndex : isSelected ? 0 : -1,
+ "data-slot": "stepper-trigger",
+ "data-state": state,
+ "data-loading": isLoading,
+ className: cn(
+ "focus-visible:border-ring focus-visible:ring-ring/50 inline-flex cursor-pointer items-center outline-none focus-visible:z-10 focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-60",
+ "gap-2.5 rounded-full",
+ className
+ ),
+ onClick: () => setActiveStep(step),
+ onKeyDown: handleKeyDown,
+ disabled: isDisabled,
+ children,
+ }
+
+ return useRender({
+ defaultTagName: "button",
+ render,
+ ref: btnRef,
+ props: mergeProps<"button">(defaultProps, props),
+ })
+}
+
+function StepperIndicator({ children, className }: React.ComponentProps<"div">) {
+ const { state, isLoading } = useStepItem()
+ const { indicators } = useStepper()
+
+ return (
+
+
+ {indicators &&
+ ((isLoading && indicators.loading) ||
+ (state === "completed" && indicators.completed) ||
+ (state === "active" && indicators.active) ||
+ (state === "inactive" && indicators.inactive))
+ ? (isLoading && indicators.loading) ||
+ (state === "completed" && indicators.completed) ||
+ (state === "active" && indicators.active) ||
+ (state === "inactive" && indicators.inactive)
+ : children}
+
+
+ )
+}
+
+function StepperSeparator({ className }: React.ComponentProps<"div">) {
+ const { state } = useStepItem()
+
+ return (
+
+ )
+}
+
+function StepperTitle({ children, className }: React.ComponentProps<"h3">) {
+ const { state } = useStepItem()
+
+ return (
+
+ {children}
+
+ )
+}
+
+function StepperDescription({ children, className }: React.ComponentProps<"div">) {
+ const { state } = useStepItem()
+
+ return (
+
+ {children}
+
+ )
+}
+
+function StepperNav({ children, className }: React.ComponentProps<"nav">) {
+ const { activeStep, orientation } = useStepper()
+
+ return (
+
+ {children}
+
+ )
+}
+
+function StepperPanel({ children, className }: React.ComponentProps<"div">) {
+ const { activeStep } = useStepper()
+
+ return (
+
+ {children}
+
+ )
+}
+
+interface StepperContentProps extends React.ComponentProps<"div"> {
+ value: number
+ forceMount?: boolean
+}
+
+function StepperContent({ value, forceMount, children, className }: StepperContentProps) {
+ const { activeStep } = useStepper()
+ const isActive = value === activeStep
+
+ if (!forceMount && !isActive) {
+ return null
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+export {
+ useStepper,
+ useStepItem,
+ Stepper,
+ StepperItem,
+ StepperTrigger,
+ StepperIndicator,
+ StepperSeparator,
+ StepperTitle,
+ StepperDescription,
+ StepperPanel,
+ StepperContent,
+ StepperNav,
+ type StepperProps,
+ type StepperItemProps,
+ type StepperTriggerProps,
+ type StepperContentProps,
+}
diff --git a/src/components/route-error.tsx b/src/components/route-error.tsx
new file mode 100644
index 0000000..30ce741
--- /dev/null
+++ b/src/components/route-error.tsx
@@ -0,0 +1,49 @@
+import { type ErrorComponentProps, Link, useRouter } from "@tanstack/react-router"
+import { AlertTriangle, RefreshCw } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent } from "@/components/ui/card"
+
+export function RouteError({ error, reset }: ErrorComponentProps) {
+ const router = useRouter()
+ const message =
+ import.meta.env.DEV && error instanceof Error
+ ? error.message
+ : "The requested data could not be loaded. Try again or return to the overview."
+
+ async function retry() {
+ reset()
+ await router.invalidate({ sync: true })
+ }
+
+ return (
+
+
+
+ This area could not be loaded
+ {message}
+
+ void retry()}>
+ Retry
+
+ } nativeButton={false} variant="outline">
+ Return to overview
+
+
+
+
+ )
+}
+
+export function RouteNotFound() {
+ return (
+
+
+ Page not found
+ The requested page or record does not exist.
+ } nativeButton={false}>
+ Return to overview
+
+
+
+ )
+}
diff --git a/src/components/search-input.tsx b/src/components/search-input.tsx
deleted file mode 100644
index 43b9009..0000000
--- a/src/components/search-input.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-"use client"
-
-import { useRouter, useSearchParams } from "next/navigation"
-import { useEffect, useState } from "react"
-import { FiRefreshCcw, FiSearch } from "react-icons/fi"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-
-export function SearchInput({ disabled }: { disabled?: boolean }) {
- const router = useRouter()
- const searchParams = useSearchParams()
- const q = searchParams.get("q") ?? ""
- const [value, setValue] = useState(q)
-
- const handleSearch = () => {
- const params = new URLSearchParams(searchParams.toString())
- if (value) {
- params.set("q", value)
- } else {
- params.delete("q")
- }
- router.replace(`?${params.toString()}`)
- }
- useEffect(() => {
- setValue(q)
- }, [q])
-
- return (
-
- setValue(e.target.value)}
- onKeyDown={(e) => e.key === "Enter" && handleSearch()}
- placeholder="Search..."
- />
-
-
- Search
-
- router.refresh()} variant="outline" disabled={disabled}>
-
- Refresh
-
-
- )
-}
diff --git a/src/components/shapes.tsx b/src/components/shapes.tsx
deleted file mode 100644
index 3841c6a..0000000
--- a/src/components/shapes.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import Image from "next/image"
-import bigBlueSvg from "@/assets/svg/shapes/big-blue.svg"
-import bigTealSvg from "@/assets/svg/shapes/big-teal.svg"
-import looperSvg from "@/assets/svg/shapes/looper.svg"
-import smallBlueSvg from "@/assets/svg/shapes/small-blue.svg"
-import { cn } from "@/lib/utils/shadcn"
-
-export type ShapeVariant = "big-blue" | "big-teal" | "small-blue" | "looper"
-
-export type ShapeProps = {
- variant: ShapeVariant
- className?: string
-}
-
-export const Shape: React.FC = ({ variant, className }) => {
- const getShapeSrc = () => {
- switch (variant) {
- case "big-blue":
- return bigBlueSvg
- case "big-teal":
- return bigTealSvg
- case "small-blue":
- return smallBlueSvg
- case "looper":
- return looperSvg
- default:
- return ""
- }
- }
-
- return
-}
diff --git a/src/components/spinner.tsx b/src/components/spinner.tsx
deleted file mode 100644
index 97e5baf..0000000
--- a/src/components/spinner.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { cn } from "@/lib/utils/shadcn"
-
-export function Spinner({ className }: React.HtmlHTMLAttributes) {
- return (
- // biome-ignore lint/a11y/useSemanticElements: incorrect change suggestion
-
- )
-}
diff --git a/src/components/telegram/create-grant-dialog.tsx b/src/components/telegram/create-grant-dialog.tsx
new file mode 100644
index 0000000..f101aa9
--- /dev/null
+++ b/src/components/telegram/create-grant-dialog.tsx
@@ -0,0 +1,462 @@
+import { useRouter } from "@tanstack/react-router"
+import { useServerFn } from "@tanstack/react-start"
+import { ArrowLeft, CalendarPlus, LoaderCircle, Search, UserRound, X } from "lucide-react"
+import { useState } from "react"
+import { toast } from "sonner"
+import {
+ Stepper,
+ StepperIndicator,
+ StepperItem,
+ StepperNav,
+ StepperTitle,
+ StepperTrigger,
+} from "@/components/reui/stepper"
+import { createTelegramGrant } from "@/features/telegram/grants.functions"
+import { findTelegramUser } from "@/features/telegram/users.functions"
+import type { TgUser } from "@/lib/api/types"
+import { Alert, AlertAction, AlertDescription, AlertTitle } from "../ui/alert"
+import { Button } from "../ui/button"
+import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog"
+import { Field, FieldError, FieldGroup, FieldLabel } from "../ui/field"
+import { Input } from "../ui/input"
+import { Textarea } from "../ui/textarea"
+import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"
+import { GrantDateTimeFields } from "./grant-date-time-fields"
+
+export type GrantDialogUser = Pick
+
+type CreateGrantDialogProps = {
+ user?: GrantDialogUser
+}
+
+const DISCARD_TOAST_ID = "discard-grant-dialog-changes"
+
+function toDateTimeInput(date: Date) {
+ const local = new Date(date.getTime() - date.getTimezoneOffset() * 60_000)
+ return local.toISOString().slice(0, 16)
+}
+
+function earliestGrantStart() {
+ const start = new Date()
+ start.setHours(0, 0, 0, 0)
+ return start
+}
+
+function displayName(user: GrantDialogUser) {
+ return [user.firstName, user.lastName].filter(Boolean).join(" ") || "Unnamed account"
+}
+
+function grantMutationError(error: string) {
+ if (error === "UNAUTHORIZED") return "You do not have permission to create grants."
+ if (error === "ALREADY_EXISTING") return "This user already has an ongoing grant."
+ return "The grant could not be created."
+}
+
+function UserInformation({
+ user,
+ clearable,
+ onClear,
+}: {
+ user: GrantDialogUser
+ clearable?: boolean
+ onClear?: () => void
+}) {
+ return (
+
+
+ {displayName(user)}
+
+ {user.username ? `@${user.username}` : "No Telegram username"} · Telegram ID {user.id}
+
+ {clearable && onClear && (
+
+
+
+
+
+ )}
+
+ )
+}
+
+function GrantStepper({
+ step,
+ canOpenDetails,
+ onStepChange,
+}: {
+ step: "user" | "details"
+ canOpenDetails: boolean
+ onStepChange: (step: "user" | "details") => void
+}) {
+ const steps = [
+ { title: "Target user", value: 1 },
+ { title: "Details", value: 2 },
+ ]
+
+ return (
+ {
+ if (value === 1) onStepChange("user")
+ if (value === 2 && canOpenDetails) onStepChange("details")
+ }}
+ className="p-5"
+ aria-label="Grant creation progress"
+ >
+
+ {steps.map((item) => (
+
+
+
+ Step {item.value}
+
+
+ {item.title}
+
+
+
+ ))}
+
+
+ )
+}
+
+export function CreateGrantDialog({ user: fixedUser }: CreateGrantDialogProps) {
+ const [open, setOpen] = useState(false)
+ const [step, setStep] = useState<"user" | "details">(fixedUser ? "details" : "user")
+ const [lookupBy, setLookupBy] = useState<"username" | "id">("username")
+ const [query, setQuery] = useState("")
+ const [selectedUser, setSelectedUser] = useState(fixedUser ?? null)
+ const [lookupStatus, setLookupStatus] = useState<"idle" | "loading" | "not-found" | "error">("idle")
+ const [lookupMessage, setLookupMessage] = useState("")
+ const [validSince, setValidSince] = useState("")
+ const [validUntil, setValidUntil] = useState("")
+ const [reason, setReason] = useState("")
+ const [dirty, setDirty] = useState(false)
+ const [pending, setPending] = useState(false)
+ const router = useRouter()
+ const createGrant = useServerFn(createTelegramGrant)
+ const findUser = useServerFn(findTelegramUser)
+
+ const resetForm = () => {
+ setStep(fixedUser ? "details" : "user")
+ setLookupBy("username")
+ setQuery("")
+ setSelectedUser(fixedUser ?? null)
+ setLookupStatus("idle")
+ setLookupMessage("")
+ setValidSince("")
+ setValidUntil("")
+ setReason("")
+ setDirty(false)
+ }
+
+ const closeAndReset = () => {
+ toast.dismiss(DISCARD_TOAST_ID)
+ setOpen(false)
+ resetForm()
+ }
+
+ const requestClose = () => {
+ if (pending) return
+ if (!dirty) {
+ closeAndReset()
+ return
+ }
+
+ toast.warning("Discard grant changes?", {
+ id: DISCARD_TOAST_ID,
+ description: "Your edits will be lost.",
+ position: "bottom-center",
+ duration: Number.POSITIVE_INFINITY,
+ className: "grant-discard-toast",
+ action: {
+ label: "Discard",
+ onClick: closeAndReset,
+ },
+ cancel: {
+ label: "Keep editing",
+ onClick: () => undefined,
+ },
+ })
+ }
+
+ const handleOpenChange = (nextOpen: boolean) => {
+ if (nextOpen) {
+ resetForm()
+ setOpen(true)
+ return
+ }
+ requestClose()
+ }
+
+ const changeLookupBy = (value: string[]) => {
+ const next = value[0]
+ if (next !== "username" && next !== "id") return
+ setLookupBy(next)
+ setQuery("")
+ setSelectedUser(null)
+ setLookupStatus("idle")
+ setLookupMessage("")
+ setDirty(true)
+ }
+
+ const lookup = async () => {
+ const normalized = query.trim()
+ const numericId = Number(normalized)
+ if (!normalized || (lookupBy === "id" && (!Number.isInteger(numericId) || numericId <= 0))) {
+ setLookupStatus("error")
+ setLookupMessage(lookupBy === "id" ? "Enter a positive numeric Telegram ID." : "Enter a Telegram username.")
+ return
+ }
+
+ setSelectedUser(null)
+ setLookupStatus("loading")
+ setLookupMessage("")
+ try {
+ const result = await findUser({
+ data: lookupBy === "username" ? { by: "username", username: normalized } : { by: "id", userId: numericId },
+ })
+ if (result.status === "found" && result.user) {
+ setSelectedUser(result.user)
+ setLookupStatus("idle")
+ } else {
+ setLookupStatus(result.status === "not-found" ? "not-found" : "error")
+ setLookupMessage(
+ result.status === "not-found"
+ ? `No Telegram user was found for this ${lookupBy === "id" ? "ID" : "username"}.`
+ : (result.message ?? "Telegram user lookup failed.")
+ )
+ }
+ } catch (error) {
+ console.error(error)
+ setLookupStatus("error")
+ setLookupMessage("Telegram user lookup failed. Check your connection and try again.")
+ }
+ }
+
+ const submit = async (event: React.FormEvent) => {
+ event.preventDefault()
+ const since = new Date(validSince)
+ const until = new Date(validUntil)
+ if (
+ !selectedUser ||
+ !validSince ||
+ !validUntil ||
+ Number.isNaN(since.getTime()) ||
+ Number.isNaN(until.getTime()) ||
+ since < earliestGrantStart() ||
+ until <= since ||
+ pending
+ )
+ return
+
+ setPending(true)
+ try {
+ const result = await createGrant({
+ data: { userId: selectedUser.id, since, until, reason: reason.trim() || undefined },
+ })
+ if (result.error) {
+ toast.error(grantMutationError(result.error))
+ return
+ }
+ toast.success(`Grant created for ${displayName(selectedUser)}.`)
+ closeAndReset()
+ try {
+ await router.invalidate({ sync: true })
+ } catch {
+ toast.warning("The grant was created, but the latest grants could not be refreshed.")
+ }
+ } catch (error) {
+ console.error(error)
+ toast.error("The grant could not be created. Check your permissions and try again.")
+ } finally {
+ setPending(false)
+ }
+ }
+
+ const sinceTime = new Date(validSince).getTime()
+ const untilTime = new Date(validUntil).getTime()
+ const minimumSince = toDateTimeInput(earliestGrantStart())
+ const invalidStart = Boolean(validSince) && (Number.isNaN(sinceTime) || sinceTime < earliestGrantStart().getTime())
+ const invalidEnd = Boolean(validUntil) && (Number.isNaN(untilTime) || (Boolean(validSince) && untilTime <= sinceTime))
+ const detailsUser = fixedUser ?? selectedUser
+
+ return (
+
+ }>
+ Add grant
+
+
+
+ New Grant
+
+
+ {!fixedUser && }
+
+ {step === "user" ? (
+
+ ) : (
+
+ )}
+
+
+ )
+}
diff --git a/src/components/telegram/grant-date-time-fields.tsx b/src/components/telegram/grant-date-time-fields.tsx
new file mode 100644
index 0000000..5f2bc83
--- /dev/null
+++ b/src/components/telegram/grant-date-time-fields.tsx
@@ -0,0 +1,373 @@
+import { CalendarIcon, Clock3Icon } from "lucide-react"
+import { useEffect, useMemo, useState } from "react"
+import { Button } from "@/components/ui/button"
+import { Calendar } from "@/components/ui/calendar"
+import { Field, FieldLabel } from "@/components/ui/field"
+import { Input } from "@/components/ui/input"
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
+import { Separator } from "@/components/ui/separator"
+import { WheelPicker, WheelPickerColumn } from "@/components/ui/wheel-picker"
+
+const LOCAL_DATE_TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/
+
+function parseLocalDateTime(value: string) {
+ const match = LOCAL_DATE_TIME_PATTERN.exec(value)
+ if (!match) return undefined
+
+ const [, year, month, day, hours, minutes] = match
+ const date = new Date(Number(year), Number(month) - 1, Number(day), Number(hours), Number(minutes))
+ return Number.isNaN(date.getTime()) ? undefined : date
+}
+
+function localDatePart(date: Date) {
+ const year = date.getFullYear()
+ const month = String(date.getMonth() + 1).padStart(2, "0")
+ const day = String(date.getDate()).padStart(2, "0")
+ return `${year}-${month}-${day}`
+}
+
+function localDateTimeValue(date: Date) {
+ const hours = String(date.getHours()).padStart(2, "0")
+ const minutes = String(date.getMinutes()).padStart(2, "0")
+ return `${localDatePart(date)}T${hours}:${minutes}`
+}
+
+function sameLocalDay(left: Date, right: Date) {
+ return (
+ left.getFullYear() === right.getFullYear() &&
+ left.getMonth() === right.getMonth() &&
+ left.getDate() === right.getDate()
+ )
+}
+
+function clampToMinimum(date: Date, minimum?: Date) {
+ return minimum && date < minimum ? new Date(minimum) : date
+}
+
+function isMobileDateTimePickerDevice() {
+ const userAgent = navigator.userAgent
+ const iPadOSDesktopMode = /Macintosh/.test(userAgent) && navigator.maxTouchPoints > 1
+ return /Android|iPhone|iPad|iPod|Mobile/i.test(userAgent) || iPadOSDesktopMode
+}
+
+function useNativeDateTimePicker() {
+ const [nativePicker, setNativePicker] = useState(false)
+
+ useEffect(() => {
+ setNativePicker(isMobileDateTimePickerDevice())
+ }, [])
+
+ return nativePicker
+}
+
+export type AdaptiveDateTimeFieldProps = {
+ id: string
+ label: string
+ value: string
+ onChange: (value: string) => void
+ min?: string
+ invalid?: boolean
+ error?: React.ReactNode
+ required?: boolean
+ disabled?: boolean
+}
+
+type DesktopDateTimePickerProps = Pick<
+ AdaptiveDateTimeFieldProps,
+ "id" | "label" | "value" | "onChange" | "invalid" | "disabled"
+> & {
+ minimum?: Date
+}
+
+const HOUR_VALUES = Array.from({ length: 24 }, (_, hour) => String(hour).padStart(2, "0"))
+const MINUTE_VALUES = Array.from({ length: 60 }, (_, minute) => String(minute).padStart(2, "0"))
+
+function DesktopDateTimePicker({
+ id,
+ label,
+ value,
+ onChange,
+ minimum,
+ invalid = false,
+ disabled = false,
+}: DesktopDateTimePickerProps) {
+ const selected = parseLocalDateTime(value)
+ const [dateOpen, setDateOpen] = useState(false)
+ const [timeOpen, setTimeOpen] = useState(false)
+ const [draft, setDraft] = useState(() => clampToMinimum(selected ?? new Date(), minimum))
+
+ const updateTimeOpen = (nextOpen: boolean) => {
+ if (nextOpen) {
+ setDraft(clampToMinimum(selected ?? new Date(), minimum))
+ }
+ setTimeOpen(nextOpen)
+ }
+
+ const chooseDate = (date: Date | undefined) => {
+ if (!date) return
+
+ const next = new Date(date)
+ next.setHours(selected?.getHours() ?? 0, selected?.getMinutes() ?? 0, 0, 0)
+ onChange(localDateTimeValue(clampToMinimum(next, minimum)))
+ setDateOpen(false)
+ }
+
+ const chooseHour = (hour: string) => {
+ const next = new Date(draft)
+ next.setHours(Number(hour))
+ setDraft(clampToMinimum(next, minimum))
+ }
+
+ const chooseMinute = (minute: string) => {
+ const next = new Date(draft)
+ next.setMinutes(Number(minute))
+ setDraft(clampToMinimum(next, minimum))
+ }
+
+ const hourOptions = useMemo(
+ () =>
+ HOUR_VALUES.map((hour) => {
+ const endOfHour = new Date(draft)
+ endOfHour.setHours(Number(hour), 59, 59, 999)
+ return {
+ value: hour,
+ disabled: Boolean(minimum && sameLocalDay(draft, minimum) && endOfHour < minimum),
+ }
+ }),
+ [draft, minimum]
+ )
+
+ const minuteOptions = useMemo(
+ () =>
+ MINUTE_VALUES.map((minute) => {
+ const candidate = new Date(draft)
+ candidate.setMinutes(Number(minute), 0, 0)
+ return {
+ value: minute,
+ disabled: Boolean(minimum && sameLocalDay(draft, minimum) && candidate < minimum),
+ }
+ }),
+ [draft, minimum]
+ )
+
+ return (
+
+
+
+ }
+ >
+
+ {selected
+ ? selected.toLocaleDateString(undefined, { dateStyle: "medium" })
+ : `Choose ${label.toLocaleLowerCase()}`}
+
+
+
+
+
+
+
+ }
+ >
+
+ {selected
+ ? `${String(selected.getHours()).padStart(2, "0")}:${String(selected.getMinutes()).padStart(2, "0")}`
+ : "--:--"}
+
+
+ 24-hour time
+
+
+
+ :
+
+
+
+
+ {
+ onChange(localDateTimeValue(draft))
+ setTimeOpen(false)
+ }}
+ >
+ Done
+
+
+
+
+ )
+}
+
+export function AdaptiveDateTimeField({
+ id,
+ label,
+ value,
+ onChange,
+ min,
+ invalid = false,
+ error,
+ required = false,
+ disabled = false,
+}: AdaptiveDateTimeFieldProps) {
+ const nativePicker = useNativeDateTimePicker()
+ const minimum = min ? parseLocalDateTime(min) : undefined
+
+ return (
+
+ {label}
+ {nativePicker ? (
+ onChange(event.target.value)}
+ aria-invalid={invalid}
+ required={required}
+ disabled={disabled}
+ className="max-w-full [inline-size:-webkit-fill-available]"
+ />
+ ) : (
+
+ )}
+ {invalid && error}
+
+ )
+}
+
+export type GrantDateTimeFieldsProps = {
+ validSince: string
+ validUntil: string
+ onValidSinceChange: (value: string) => void
+ onValidUntilChange: (value: string) => void
+ minimumSince: string
+ minimumUntil: string
+ invalidStart: boolean
+ invalidEnd: boolean
+}
+
+export function GrantDateTimeFields({
+ validSince,
+ validUntil,
+ onValidSinceChange,
+ onValidUntilChange,
+ minimumSince,
+ minimumUntil,
+ invalidStart,
+ invalidEnd,
+}: GrantDateTimeFieldsProps) {
+ const setDuration = (hours: number) => {
+ const start = parseLocalDateTime(validSince)
+ if (!start) return
+ onValidUntilChange(localDateTimeValue(new Date(start.getTime() + hours * 60 * 60 * 1000)))
+ }
+
+ return (
+ <>
+ Choose today or a future date.}
+ required
+ />
+ onValidSinceChange(localDateTimeValue(new Date()))}
+ >
+ Now
+
+ Choose a time after the grant starts.}
+ required
+ disabled={!validSince}
+ />
+
+ {[
+ { label: "2h", hours: 2 },
+ { label: "6h", hours: 6 },
+ { label: "12h", hours: 12 },
+ { label: "1d", hours: 24 },
+ ].map(({ label, hours }) => (
+ setDuration(hours)}
+ aria-label={`Set grant duration to ${label}`}
+ >
+ {label}
+
+ ))}
+
+ >
+ )
+}
diff --git a/src/components/theme-button.tsx b/src/components/theme-button.tsx
deleted file mode 100644
index 2ddc817..0000000
--- a/src/components/theme-button.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-"use client"
-import { useTheme } from "next-themes"
-import { FiMoon, FiSun } from "react-icons/fi"
-import { Button } from "./ui/button"
-
-export function ThemeButton() {
- const { resolvedTheme, setTheme } = useTheme()
-
- // TODO: remove disabled when light theme is ready
- return (
- setTheme(resolvedTheme === "light" ? "dark" : "light")}>
-
-
-
- )
-}
diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx
deleted file mode 100644
index c655c82..0000000
--- a/src/components/theme-provider.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-"use client"
-
-import { ThemeProvider as NextThemesProvider } from "next-themes"
-import type * as React from "react"
-
-export function ThemeProvider({ children, ...props }: React.ComponentProps) {
- return {children}
-}
diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx
new file mode 100644
index 0000000..aa75aa6
--- /dev/null
+++ b/src/components/theme-toggle.tsx
@@ -0,0 +1,41 @@
+import { Moon, Sun } from "lucide-react"
+import { useEffect, useState } from "react"
+import { Button } from "@/components/ui/button"
+
+const storageKey = "polinetwork-theme"
+
+function applyTheme(theme: "light" | "dark") {
+ document.documentElement.classList.toggle("dark", theme === "dark")
+ document.documentElement.style.colorScheme = theme
+}
+
+export function ThemeToggle() {
+ const [dark, setDark] = useState(false)
+
+ useEffect(() => {
+ const stored = window.localStorage.getItem(storageKey)
+ const isDark = stored === "dark" || (!stored && window.matchMedia("(prefers-color-scheme: dark)").matches)
+ setDark(isDark)
+ applyTheme(isDark ? "dark" : "light")
+ }, [])
+
+ function toggle() {
+ const next = !dark
+ setDark(next)
+ window.localStorage.setItem(storageKey, next ? "dark" : "light")
+ applyTheme(next ? "dark" : "light")
+ }
+
+ return (
+
+ {dark ? : }
+
+ )
+}
diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx
index 75f5309..af7a99f 100644
--- a/src/components/ui/alert-dialog.tsx
+++ b/src/components/ui/alert-dialog.tsx
@@ -1,31 +1,21 @@
-"use client"
-
-import * as React from "react"
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
-
-import { cn } from "@/lib/utils/shadcn"
+import type * as React from "react"
import { Button } from "@/components/ui/button"
+import { cn } from "@/lib/utils"
function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
return
}
function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
- return (
-
- )
+ return
}
function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
- return (
-
- )
+ return
}
-function AlertDialogOverlay({
- className,
- ...props
-}: AlertDialogPrimitive.Backdrop.Props) {
+function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) {
return (
) {
+function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
) {
+function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
) {
+function AlertDialogMedia({ className, ...props }: React.ComponentProps<"div">) {
return (
) {
+function AlertDialogTitle({ className, ...props }: React.ComponentProps
) {
return (
) {
- return (
-
- )
+function AlertDialogAction({ className, ...props }: React.ComponentProps) {
+ return
}
function AlertDialogCancel({
@@ -159,8 +128,7 @@ function AlertDialogCancel({
variant = "outline",
size = "default",
...props
-}: AlertDialogPrimitive.Close.Props &
- Pick, "variant" | "size">) {
+}: AlertDialogPrimitive.Close.Props & Pick, "variant" | "size">) {
return (
svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
@@ -9,7 +9,6 @@ const alertVariants = cva(
variants: {
variant: {
default: "bg-card text-card-foreground",
- info: "bg-primary/20 text-primary-foreground",
destructive:
"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
},
@@ -20,19 +19,8 @@ const alertVariants = cva(
}
)
-function Alert({
- className,
- variant,
- ...props
-}: React.ComponentProps<"div"> & VariantProps) {
- return (
-
- )
+function Alert({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps) {
+ return
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
@@ -40,7 +28,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
className
)}
{...props}
@@ -48,10 +36,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
)
}
-function AlertDescription({
- className,
- ...props
-}: React.ComponentProps<"div">) {
+function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
) {
- return (
-
- )
+ return
}
export { Alert, AlertTitle, AlertDescription, AlertAction }
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
index 64ce76b..ebdf3e6 100644
--- a/src/components/ui/avatar.tsx
+++ b/src/components/ui/avatar.tsx
@@ -1,9 +1,7 @@
-"use client"
-
-import * as React from "react"
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
+import type * as React from "react"
-import { cn } from "@/lib/utils/shadcn"
+import { cn } from "@/lib/utils"
function Avatar({
className,
@@ -29,19 +27,13 @@ function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
return (
)
}
-function AvatarFallback({
- className,
- ...props
-}: AvatarPrimitive.Fallback.Props) {
+function AvatarFallback({ className, ...props }: AvatarPrimitive.Fallback.Props) {
return (
) {
)
}
-function AvatarGroupCount({
- className,
- ...props
-}: React.ComponentProps<"div">) {
+function AvatarGroupCount({ className, ...props }: React.ComponentProps<"div">) {
return (
svg]:pointer-events-none [&>svg]:size-3!",
+ "group/badge inline-flex h-6 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
- secondary:
- "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
destructive:
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
- outline:
- "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
- ghost:
- "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
+ outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
+ ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline",
},
},
diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx
index 28b736e..42a7fc5 100644
--- a/src/components/ui/breadcrumb.tsx
+++ b/src/components/ui/breadcrumb.tsx
@@ -1,49 +1,28 @@
-import * as React from "react"
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
-
-import { cn } from "@/lib/utils/shadcn"
-import { FiChevronRight, FiMoreHorizontal } from "react-icons/fi"
+import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
+import type * as React from "react"
+import { cn } from "@/lib/utils"
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
- return (
-
- )
+ return
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
return (
)
}
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
- return (
-
- )
+ return
}
-function BreadcrumbLink({
- className,
- render,
- ...props
-}: useRender.ComponentProps<"a">) {
+function BreadcrumbLink({ className, render, ...props }: useRender.ComponentProps<"a">) {
return useRender({
defaultTagName: "a",
props: mergeProps<"a">(
@@ -63,8 +42,6 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return (
) {
)
}
-function BreadcrumbSeparator({
- children,
- className,
- ...props
-}: React.ComponentProps<"li">) {
+function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<"li">) {
return (
svg]:size-3.5", className)}
{...props}
>
- {children ?? (
-
- )}
+ {children ?? }
)
}
-function BreadcrumbEllipsis({
- className,
- ...props
-}: React.ComponentProps<"span">) {
+function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span">) {
return (
svg]:size-4",
- className
- )}
+ className={cn("flex size-5 items-center justify-center [&>svg]:size-4", className)}
{...props}
>
-
+
More
)
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index e696922..4cb387c 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -1,36 +1,35 @@
import { Button as ButtonPrimitive } from "@base-ui/react/button"
import { cva, type VariantProps } from "class-variance-authority"
-import { cn } from "@/lib/utils/shadcn"
+import { cn } from "@/lib/utils"
const buttonVariants = cva(
- "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 cursor-pointer",
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
- default: "bg-primary text-primary-foreground hover:bg-primary/80",
+ default:
+ "bg-primary text-primary-foreground shadow-[0_1px_2px_rgb(15_23_42/8%)] hover:bg-primary/90 dark:shadow-none",
outline:
- "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
+ "border-border bg-card text-foreground shadow-[0_1px_2px_rgb(15_23_42/3%)] hover:border-input hover:bg-muted aria-expanded:bg-muted aria-expanded:text-foreground dark:shadow-none",
secondary:
- "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
+ "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost:
- "hover:bg-accent hover:text-foreground aria-expanded:bg-accent aria-expanded:text-foreground",
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
destructive:
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
- default:
- "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
+ default: "h-10 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
- sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
- lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
- icon: "size-8 [&_svg:not([class*='size-'])]:size-4.5",
+ sm: "h-9 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
+ lg: "h-11 gap-1.5 px-3.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
+ icon: "size-10",
"icon-xs":
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
- "icon-sm":
- "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-4",
- "icon-lg": "size-9 [&_svg:not([class*='size-'])]:size-5",
+ "icon-sm": "size-9 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
+ "icon-lg": "size-11",
},
},
defaultVariants: {
@@ -46,13 +45,7 @@ function Button({
size = "default",
...props
}: ButtonPrimitive.Props & VariantProps) {
- return (
-
- )
+ return
}
export { Button, buttonVariants }
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
index a39b65e..7f487ee 100644
--- a/src/components/ui/calendar.tsx
+++ b/src/components/ui/calendar.tsx
@@ -1,16 +1,8 @@
-"use client"
-
+import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
import * as React from "react"
-import {
- DayPicker,
- getDefaultClassNames,
- type DayButton,
- type Locale,
-} from "react-day-picker"
-
-import { cn } from "@/lib/utils/shadcn"
+import { type DayButton, DayPicker, getDefaultClassNames, type Locale } from "react-day-picker"
import { Button, buttonVariants } from "@/components/ui/button"
-import { FiChevronLeft, FiChevronRight, FiChevronDown } from "react-icons/fi"
+import { cn } from "@/lib/utils"
function Calendar({
className,
@@ -39,21 +31,14 @@ function Calendar({
captionLayout={captionLayout}
locale={locale}
formatters={{
- formatMonthDropdown: (date) =>
- date.toLocaleString(locale?.code, { month: "short" }),
+ formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: "short" }),
...formatters,
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
- months: cn(
- "relative flex flex-col gap-4 md:flex-row",
- defaultClassNames.months
- ),
+ months: cn("relative flex flex-col gap-4 md:flex-row", defaultClassNames.months),
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
- nav: cn(
- "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
- defaultClassNames.nav
- ),
+ nav: cn("absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", defaultClassNames.nav),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
@@ -72,14 +57,8 @@ function Calendar({
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
defaultClassNames.dropdowns
),
- dropdown_root: cn(
- "relative rounded-(--cell-radius)",
- defaultClassNames.dropdown_root
- ),
- dropdown: cn(
- "absolute inset-0 bg-popover opacity-0",
- defaultClassNames.dropdown
- ),
+ dropdown_root: cn("relative rounded-(--cell-radius)", defaultClassNames.dropdown_root),
+ dropdown: cn("absolute inset-0 bg-popover opacity-0", defaultClassNames.dropdown),
caption_label: cn(
"font-medium select-none",
captionLayout === "label"
@@ -87,21 +66,15 @@ function Calendar({
: "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
defaultClassNames.caption_label
),
- table: "w-full border-collapse",
+ month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none",
defaultClassNames.weekday
),
week: cn("mt-2 flex w-full", defaultClassNames.week),
- week_number_header: cn(
- "w-(--cell-size) select-none",
- defaultClassNames.week_number_header
- ),
- week_number: cn(
- "text-[0.8rem] text-muted-foreground select-none",
- defaultClassNames.week_number
- ),
+ week_number_header: cn("w-(--cell-size) select-none", defaultClassNames.week_number_header),
+ week_number: cn("text-[0.8rem] text-muted-foreground select-none", defaultClassNames.week_number),
day: cn(
"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
props.showWeekNumber
@@ -122,54 +95,31 @@ function Calendar({
"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
defaultClassNames.today
),
- outside: cn(
- "text-muted-foreground aria-selected:text-muted-foreground",
- defaultClassNames.outside
- ),
- disabled: cn(
- "text-muted-foreground opacity-50",
- defaultClassNames.disabled
- ),
+ outside: cn("text-muted-foreground aria-selected:text-muted-foreground", defaultClassNames.outside),
+ disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
Root: ({ className, rootRef, ...props }) => {
- return (
-
- )
+ return
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
- return (
-
- )
+ return
}
if (orientation === "right") {
- return (
-
- )
+ return
}
- return (
-
- )
+ return
},
- DayButton: ({ ...props }) => (
-
- ),
+ DayButton: ({ ...props }) => ,
WeekNumber: ({ children, ...props }) => {
return (
-
- {children}
-
+ {children}
)
},
@@ -200,10 +150,7 @@ function CalendarDayButton({
size="icon"
data-day={day.date.toLocaleDateString(locale?.code)}
data-selected-single={
- modifiers.selected &&
- !modifiers.range_start &&
- !modifiers.range_end &&
- !modifiers.range_middle
+ modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
index 0c2e676..e215d2d 100644
--- a/src/components/ui/card.tsx
+++ b/src/components/ui/card.tsx
@@ -1,18 +1,14 @@
-import * as React from "react"
+import type * as React from "react"
-import { cn } from "@/lib/utils/shadcn"
+import { cn } from "@/lib/utils"
-function Card({
- className,
- size = "default",
- ...props
-}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
+function Card({ className, size = "default", ...props }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return (
img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
+ "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl border border-border bg-card py-(--card-spacing) text-sm text-card-foreground shadow-[0_1px_2px_rgb(15_23_42/4%)] [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl dark:shadow-none",
className
)}
{...props}
@@ -25,7 +21,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
) {
return (
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
+ return
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
+ return
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
)
}
-export {
- Card,
- CardHeader,
- CardFooter,
- CardTitle,
- CardAction,
- CardDescription,
- CardContent,
-}
+export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent }
diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx
index 488fb33..4c03327 100644
--- a/src/components/ui/collapsible.tsx
+++ b/src/components/ui/collapsible.tsx
@@ -1,5 +1,3 @@
-"use client"
-
import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible"
function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
@@ -7,15 +5,11 @@ function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
}
function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
- return (
-
- )
+ return
}
function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
- return (
-
- )
+ return
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/src/components/ui/combobox.tsx b/src/components/ui/combobox.tsx
new file mode 100644
index 0000000..5abcbe2
--- /dev/null
+++ b/src/components/ui/combobox.tsx
@@ -0,0 +1,111 @@
+import { Combobox as ComboboxPrimitive } from "@base-ui/react/combobox"
+import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react"
+import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group"
+import { cn } from "@/lib/utils"
+
+const Combobox = ComboboxPrimitive.Root
+
+function ComboboxInput({
+ className,
+ disabled = false,
+ showClear = false,
+ ...props
+}: ComboboxPrimitive.Input.Props & { showClear?: boolean }) {
+ return (
+
+ } {...props} />
+
+ }
+ disabled={disabled}
+ aria-label="Show groups"
+ className="data-pressed:bg-transparent"
+ >
+
+
+ {showClear && (
+ }
+ disabled={disabled}
+ aria-label="Clear group"
+ >
+
+
+ )}
+
+
+ )
+}
+
+function ComboboxContent({
+ className,
+ side = "bottom",
+ sideOffset = 6,
+ align = "start",
+ alignOffset = 0,
+ ...props
+}: ComboboxPrimitive.Popup.Props &
+ Pick
) {
+ return (
+
+
+
+
+
+ )
+}
+
+function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
+ return (
+
+ )
+}
+
+function ComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item.Props) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
+ return (
+
+ )
+}
+
+export { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList }
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
index 7b8b0ef..13ada83 100644
--- a/src/components/ui/command.tsx
+++ b/src/components/ui/command.tsx
@@ -1,26 +1,12 @@
-"use client"
-
-import * as React from "react"
import { Command as CommandPrimitive } from "cmdk"
+import { Check, Search } from "lucide-react"
+import type * as React from "react"
-import { cn } from "@/lib/utils/shadcn"
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog"
-import {
- InputGroup,
- InputGroupAddon,
-} from "@/components/ui/input-group"
-import { FiSearch, FiCheck } from "react-icons/fi"
+import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
+import { InputGroup, InputGroupAddon } from "@/components/ui/input-group"
+import { cn } from "@/lib/utils"
-function Command({
- className,
- ...props
-}: React.ComponentProps) {
+function Command({ className, ...props }: React.ComponentProps) {
return (
{description}
{children}
@@ -66,49 +49,34 @@ function CommandDialog({
)
}
-function CommandInput({
- className,
- ...props
-}: React.ComponentProps) {
+function CommandInput({ className, ...props }: React.ComponentProps) {
return (
-
+
)
}
-function CommandList({
- className,
- ...props
-}: React.ComponentProps) {
+function CommandList({ className, ...props }: React.ComponentProps) {
return (
)
}
-function CommandEmpty({
- className,
- ...props
-}: React.ComponentProps) {
+function CommandEmpty({ className, ...props }: React.ComponentProps) {
return (
) {
+function CommandGroup({ className, ...props }: React.ComponentProps) {
return (
) {
+function CommandSeparator({ className, ...props }: React.ComponentProps) {
return (
) {
+function CommandItem({ className, children, ...props }: React.ComponentProps) {
return (
{children}
-
+
)
}
-function CommandShortcut({
- className,
- ...props
-}: React.ComponentProps<"span">) {
+function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
return (
@@ -23,15 +20,12 @@ function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
return
}
-function DialogOverlay({
- className,
- ...props
-}: DialogPrimitive.Backdrop.Props) {
+function DialogOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props) {
return (
- }
+ data-slot="dialog-close"
+ render={ }
>
-
+
Close
)}
@@ -79,13 +74,7 @@ function DialogContent({
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
+ return
}
function DialogFooter({
@@ -106,11 +95,7 @@ function DialogFooter({
{...props}
>
{children}
- {showCloseButton && (
- }>
- Close
-
- )}
+ {showCloseButton && }>Close}
)
}
@@ -119,19 +104,13 @@ function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
return (
)
}
-function DialogDescription({
- className,
- ...props
-}: DialogPrimitive.Description.Props) {
+function DialogDescription({ className, ...props }: DialogPrimitive.Description.Props) {
return (
@@ -25,11 +22,7 @@ function DropdownMenuContent({
sideOffset = 4,
className,
...props
-}: MenuPrimitive.Popup.Props &
- Pick<
- MenuPrimitive.Positioner.Props,
- "align" | "alignOffset" | "side" | "sideOffset"
- >) {
+}: MenuPrimitive.Popup.Props & Pick
) {
return (
@@ -64,10 +60,7 @@ function DropdownMenuLabel({
)
@@ -88,7 +81,7 @@ function DropdownMenuItem({
data-inset={inset}
data-variant={variant}
className={cn(
- "cursor-pointer group/dropdown-menu-item relative flex items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
+ "group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
className
)}
{...props}
@@ -119,7 +112,7 @@ function DropdownMenuSubTrigger({
{...props}
>
{children}
-
+
)
}
@@ -135,7 +128,10 @@ function DropdownMenuSubContent({
return (
-
+
{children}
@@ -179,12 +175,7 @@ function DropdownMenuCheckboxItem({
}
function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
- return (
-
- )
+ return
}
function DropdownMenuRadioItem({
@@ -210,7 +201,7 @@ function DropdownMenuRadioItem({
data-slot="dropdown-menu-radio-item-indicator"
>
-
+
{children}
@@ -218,10 +209,7 @@ function DropdownMenuRadioItem({
)
}
-function DropdownMenuSeparator({
- className,
- ...props
-}: MenuPrimitive.Separator.Props) {
+function DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {
return (
) {
+function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
return (
) {
+ return (
+
+ )
+}
+
+function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+const emptyMediaVariants = cva(
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ icon: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function EmptyMedia({
+ className,
+ variant = "default",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
+ return (
+ a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia }
diff --git a/src/components/ui/field.tsx b/src/components/ui/field.tsx
index 50a5514..6dcc6cd 100644
--- a/src/components/ui/field.tsx
+++ b/src/components/ui/field.tsx
@@ -1,238 +1,60 @@
-"use client"
-
-import { useMemo } from "react"
import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils/shadcn"
import { Label } from "@/components/ui/label"
-import { Separator } from "@/components/ui/separator"
-
-function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
- return (
-
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
- className
- )}
- {...props}
- />
- )
-}
-
-function FieldLegend({
- className,
- variant = "legend",
- ...props
-}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
- return (
-
- )
-}
+import { cn } from "@/lib/utils"
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
-
+
)
}
-const fieldVariants = cva(
- "group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
- {
- variants: {
- orientation: {
- vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
- horizontal:
- "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
- responsive:
- "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
- },
+const fieldVariants = cva("group/field flex w-full gap-2 data-[invalid=true]:text-destructive", {
+ variants: {
+ orientation: {
+ vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
+ horizontal: "flex-row items-center",
+ responsive: "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto",
},
- defaultVariants: {
- orientation: "vertical",
- },
- }
-)
+ },
+ defaultVariants: { orientation: "vertical" },
+})
function Field({
className,
orientation = "vertical",
...props
-}: React.ComponentProps<"div"> & VariantProps) {
+}: React.ComponentProps<"fieldset"> & VariantProps) {
return (
-
)
}
-function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function FieldLabel({
- className,
- ...props
-}: React.ComponentProps) {
- return (
- [data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
- "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
- className
- )}
- {...props}
- />
- )
-}
-
-function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
+function FieldLabel({ className, ...props }: React.ComponentProps) {
+ return
}
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
- className
- )}
+ className={cn("text-sm leading-normal text-muted-foreground", className)}
{...props}
/>
)
}
-function FieldSeparator({
- children,
- className,
- ...props
-}: React.ComponentProps<"div"> & {
- children?: React.ReactNode
-}) {
- return (
-
-
- {children && (
-
- {children}
-
- )}
-
- )
-}
-
-function FieldError({
- className,
- children,
- errors,
- ...props
-}: React.ComponentProps<"div"> & {
- errors?: Array<{ message?: string } | undefined>
-}) {
- const content = useMemo(() => {
- if (children) {
- return children
- }
-
- if (!errors?.length) {
- return null
- }
-
- const uniqueErrors = [
- ...new Map(errors.map((error) => [error?.message, error])).values(),
- ]
-
- if (uniqueErrors?.length == 1) {
- return uniqueErrors[0]?.message
- }
-
- return (
-
- {uniqueErrors.map(
- (error, index) =>
- error?.message && {error.message}
- )}
-
- )
- }, [children, errors])
-
- if (!content) {
- return null
- }
-
+function FieldError({ className, children, ...props }: React.ComponentProps<"div">) {
+ if (!children) return null
return (
-
- {content}
+
+ {children}
)
}
-export {
- Field,
- FieldLabel,
- FieldDescription,
- FieldError,
- FieldGroup,
- FieldLegend,
- FieldSeparator,
- FieldSet,
- FieldContent,
- FieldTitle,
-}
+export { Field, FieldDescription, FieldError, FieldGroup, FieldLabel }
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx
deleted file mode 100644
index cd03ec1..0000000
--- a/src/components/ui/form.tsx
+++ /dev/null
@@ -1,179 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { type Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui";
-
-import {
- Controller,
- type ControllerProps,
- type FieldPath,
- type FieldValues,
- FormProvider,
- useFormContext,
-} from "react-hook-form";
-
-import { cn } from "@/lib/utils/shadcn";
-import { Label } from "@/components/ui/label";
-
-const Form = FormProvider;
-
-type FormFieldContextValue<
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath
= FieldPath,
-> = {
- name: TName;
-};
-
-const FormFieldContext = React.createContext(
- {} as FormFieldContextValue,
-);
-
-const FormField = <
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath,
->({
- ...props
-}: ControllerProps) => {
- return (
-
-
-
- );
-};
-
-const useFormField = () => {
- const fieldContext = React.useContext(FormFieldContext);
- const itemContext = React.useContext(FormItemContext);
- const { getFieldState, formState } = useFormContext();
-
- const fieldState = getFieldState(fieldContext.name, formState);
-
- if (!fieldContext) {
- throw new Error("useFormField should be used within ");
- }
-
- const { id } = itemContext;
-
- return {
- id,
- name: fieldContext.name,
- formItemId: `${id}-form-item`,
- formDescriptionId: `${id}-form-item-description`,
- formMessageId: `${id}-form-item-message`,
- ...fieldState,
- };
-};
-
-type FormItemContextValue = {
- id: string;
-};
-
-const FormItemContext = React.createContext(
- {} as FormItemContextValue,
-);
-
-const FormItem = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => {
- const id = React.useId();
-
- return (
-
-
-
- );
-});
-FormItem.displayName = "FormItem";
-
-const FormLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => {
- const { error, formItemId } = useFormField();
-
- return (
-
- );
-});
-FormLabel.displayName = "FormLabel";
-
-const FormControl = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ ...props }, ref) => {
- const { error, formItemId, formDescriptionId, formMessageId } =
- useFormField();
-
- return (
-
- );
-});
-FormControl.displayName = "FormControl";
-
-const FormDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => {
- const { formDescriptionId } = useFormField();
-
- return (
-
- );
-});
-FormDescription.displayName = "FormDescription";
-
-const FormMessage = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, children, ...props }, ref) => {
- const { error, formMessageId } = useFormField();
- const body = error ? String(error?.message) : children;
-
- if (!body) {
- return null;
- }
-
- return (
-
- {body}
-
- );
-});
-FormMessage.displayName = "FormMessage";
-
-export {
- useFormField,
- Form,
- FormItem,
- FormLabel,
- FormControl,
- FormDescription,
- FormMessage,
- FormField,
-};
diff --git a/src/components/ui/input-group.tsx b/src/components/ui/input-group.tsx
index 487a742..c1aa007 100644
--- a/src/components/ui/input-group.tsx
+++ b/src/components/ui/input-group.tsx
@@ -1,15 +1,14 @@
-"use client"
-
-import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
+import type * as React from "react"
-import { cn } from "@/lib/utils/shadcn"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
+import { cn } from "@/lib/utils"
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
+ // biome-ignore lint/a11y/useSemanticElements: This composite control is not a form fieldset.
button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]",
- "inline-end":
- "order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]",
+ "inline-start": "order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]",
+ "inline-end": "order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]",
"block-start":
"order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
- "block-end":
- "order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2",
+ "block-end": "order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2",
},
},
- defaultVariants: {
- align: "inline-start",
- },
+ defaultVariants: { align: "inline-start" },
}
)
@@ -50,38 +44,25 @@ function InputGroupAddon({
}: React.ComponentProps<"div"> & VariantProps
) {
return (
{
- if ((e.target as HTMLElement).closest("button")) {
- return
- }
- e.currentTarget.parentElement?.querySelector("input")?.focus()
- }}
{...props}
/>
)
}
-const inputGroupButtonVariants = cva(
- "flex items-center gap-2 text-sm shadow-none",
- {
- variants: {
- size: {
- xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
- sm: "",
- "icon-xs":
- "size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0",
- "icon-sm": "size-8 p-0 has-[>svg]:p-0",
- },
- },
- defaultVariants: {
- size: "xs",
+const inputGroupButtonVariants = cva("flex items-center gap-2 text-sm shadow-none", {
+ variants: {
+ size: {
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
+ sm: "",
+ "icon-xs": "size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0",
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0",
},
- }
-)
+ },
+ defaultVariants: { size: "xs" },
+})
function InputGroupButton({
className,
@@ -116,10 +97,7 @@ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
)
}
-function InputGroupInput({
- className,
- ...props
-}: React.ComponentProps<"input">) {
+function InputGroupInput({ className, ...props }: React.ComponentProps<"input">) {
return (
) {
+function InputGroupTextarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
& {
- containerClassName?: string
-}) {
- return (
-
- )
-}
-
-function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function InputOTPSlot({
- index,
- className,
- ...props
-}: React.ComponentProps<"div"> & {
- index: number
-}) {
- const inputOTPContext = React.useContext(OTPInputContext)
- const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
-
- return (
-
- {char}
- {hasFakeCaret && (
-
- )}
-
- )
-}
-
-function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
- return (
-
-
-
- )
-}
-
-export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
index 8e7a2ca..2099dd3 100644
--- a/src/components/ui/input.tsx
+++ b/src/components/ui/input.tsx
@@ -1,29 +1,18 @@
-import * as React from "react"
import { Input as InputPrimitive } from "@base-ui/react/input"
+import type * as React from "react"
-import { cn } from "@/lib/utils/shadcn"
+import { cn } from "@/lib/utils"
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
-const ignoreManagerAttrs =
- props.autoComplete === "off"
- ? {
- "data-1p-ignore": true, // 1Password
- "data-lpignore": true, // LastPass
- "data-bwignore": true, // Bitwarden
- "data-form-type": "other", // Dashlane
- }
- : {};
-
return (
)
}
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
index 2366f82..b803ce8 100644
--- a/src/components/ui/label.tsx
+++ b/src/components/ui/label.tsx
@@ -1,12 +1,14 @@
-"use client"
+import type * as React from "react"
-import * as React from "react"
+import { cn } from "@/lib/utils"
-import { cn } from "@/lib/utils/shadcn"
+type LabelProps = Omit, "htmlFor"> & { htmlFor: string }
-function Label({ className, ...props }: React.ComponentProps<"label">) {
+function Label({ className, htmlFor, ...props }: LabelProps) {
return (
+ // biome-ignore lint/a11y/noLabelWithoutControl: Label associates its control through the required htmlFor prop.
) {
+ return (
+
+ )
+}
+
+function PaginationContent({ className, ...props }: React.ComponentProps<"ul">) {
+ return
+}
+
+function PaginationItem({ ...props }: React.ComponentProps<"li">) {
+ return
+}
+
+type PaginationLinkProps = {
+ isActive?: boolean
+} & Pick, "size"> &
+ React.ComponentProps<"a">
+
+function PaginationLink({ className, isActive, size = "icon", ...props }: PaginationLinkProps) {
+ return (
+
+ }
+ />
+ )
+}
+
+function PaginationPrevious({
+ className,
+ text = "Previous",
+ ...props
+}: React.ComponentProps & { text?: string }) {
+ return (
+
+
+ {text}
+
+ )
+}
+
+function PaginationNext({
+ className,
+ text = "Next",
+ ...props
+}: React.ComponentProps & { text?: string }) {
+ return (
+
+ {text}
+
+
+ )
+}
+
+function PaginationEllipsis({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+
+ More pages
+
+ )
+}
+
+export {
+ Pagination,
+ PaginationContent,
+ PaginationEllipsis,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+}
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
index e0f0707..4ef46ec 100644
--- a/src/components/ui/popover.tsx
+++ b/src/components/ui/popover.tsx
@@ -1,9 +1,7 @@
-"use client"
-
-import * as React from "react"
import { Popover as PopoverPrimitive } from "@base-ui/react/popover"
+import type * as React from "react"
-import { cn } from "@/lib/utils/shadcn"
+import { cn } from "@/lib/utils"
function Popover({ ...props }: PopoverPrimitive.Root.Props) {
return
@@ -21,10 +19,7 @@ function PopoverContent({
sideOffset = 4,
...props
}: PopoverPrimitive.Popup.Props &
- Pick<
- PopoverPrimitive.Positioner.Props,
- "align" | "alignOffset" | "side" | "sideOffset"
- >) {
+ Pick) {
return (
) {
- return (
-
- )
+ return
}
function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
- return (
-
- )
+ return
}
-function PopoverDescription({
- className,
- ...props
-}: PopoverPrimitive.Description.Props) {
+function PopoverDescription({ className, ...props }: PopoverPrimitive.Description.Props) {
return (
- {children}
-
- )
-}
-
-function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
- return (
-
- )
-}
-
-function ProgressIndicator({
- className,
- ...props
-}: ProgressPrimitive.Indicator.Props) {
- return (
-
- )
-}
-
-function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
- return (
-
- )
-}
-
-function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
- return (
-
- )
-}
-
-export {
- Progress,
- ProgressTrack,
- ProgressIndicator,
- ProgressLabel,
- ProgressValue,
-}
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
deleted file mode 100644
index efd2cb8..0000000
--- a/src/components/ui/select.tsx
+++ /dev/null
@@ -1,199 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { Select as SelectPrimitive } from "@base-ui/react/select"
-
-import { cn } from "@/lib/utils/shadcn"
-import { FiChevronDown, FiCheck, FiChevronUp } from "react-icons/fi"
-
-const Select = SelectPrimitive.Root
-
-function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
- return (
-
- )
-}
-
-function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
- return (
-
- )
-}
-
-function SelectTrigger({
- className,
- size = "default",
- children,
- ...props
-}: SelectPrimitive.Trigger.Props & {
- size?: "sm" | "default"
-}) {
- return (
-
- {children}
-
- }
- />
-
- )
-}
-
-function SelectContent({
- className,
- children,
- side = "bottom",
- sideOffset = 4,
- align = "center",
- alignOffset = 0,
- alignItemWithTrigger = true,
- ...props
-}: SelectPrimitive.Popup.Props &
- Pick<
- SelectPrimitive.Positioner.Props,
- "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
- >) {
- return (
-
-
-
-
- {children}
-
-
-
-
- )
-}
-
-function SelectLabel({
- className,
- ...props
-}: SelectPrimitive.GroupLabel.Props) {
- return (
-
- )
-}
-
-function SelectItem({
- className,
- children,
- ...props
-}: SelectPrimitive.Item.Props) {
- return (
-
-
- {children}
-
-
- }
- >
-
-
-
- )
-}
-
-function SelectSeparator({
- className,
- ...props
-}: SelectPrimitive.Separator.Props) {
- return (
-
- )
-}
-
-function SelectScrollUpButton({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- )
-}
-
-function SelectScrollDownButton({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- )
-}
-
-export {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectLabel,
- SelectScrollDownButton,
- SelectScrollUpButton,
- SelectSeparator,
- SelectTrigger,
- SelectValue,
-}
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
index 0616473..740c9da 100644
--- a/src/components/ui/separator.tsx
+++ b/src/components/ui/separator.tsx
@@ -1,14 +1,8 @@
-"use client"
-
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
-import { cn } from "@/lib/utils/shadcn"
+import { cn } from "@/lib/utils"
-function Separator({
- className,
- orientation = "horizontal",
- ...props
-}: SeparatorPrimitive.Props) {
+function Separator({ className, orientation = "horizontal", ...props }: SeparatorPrimitive.Props) {
return (
@@ -53,7 +50,7 @@ function SheetContent({
data-slot="sheet-content"
data-side={side}
className={cn(
- "fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem]",
+ "fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
className
)}
{...props}
@@ -62,15 +59,9 @@ function SheetContent({
{showCloseButton && (
- }
+ render={ }
>
-
+
Close
)}
@@ -80,42 +71,24 @@ function SheetContent({
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
+ return
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
+ return
}
function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
return (
)
}
-function SheetDescription({
- className,
- ...props
-}: SheetPrimitive.Description.Props) {
+function SheetDescription({ className, ...props }: SheetPrimitive.Description.Props) {
return (
(
- COOKIES.SIDEBAR_OPEN,
- false,
- { expires: 60 * 60 * 24 * 7 }
- )
const [_open, _setOpen] = React.useState(defaultOpen)
const open = openProp ?? _open
const setOpen = React.useCallback(
@@ -87,7 +69,9 @@ function SidebarProvider({
_setOpen(openState)
}
- setOpenCookie(openState)
+ // This sets the cookie to keep the sidebar state.
+ // biome-ignore lint/suspicious/noDocumentCookie: The server-readable cookie preserves shadcn sidebar state.
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
},
[setOpenProp, open]
)
@@ -100,10 +84,7 @@ function SidebarProvider({
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
- if (
- event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
- (event.metaKey || event.ctrlKey)
- ) {
+ if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
event.preventDefault()
toggleSidebar()
}
@@ -141,10 +122,7 @@ function SidebarProvider({
...style,
} as React.CSSProperties
}
- className={cn(
- "group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",
- className
- )}
+ className={cn("group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar", className)}
{...props}
>
{children}
@@ -172,10 +150,7 @@ function Sidebar({
return (
{children}
@@ -223,8 +198,8 @@ function Sidebar({
data-slot="sidebar-gap"
className={cn(
"relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
- "group-data-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180",
+ state === "collapsed" && collapsible === "offcanvas" && "w-0",
variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
@@ -234,7 +209,10 @@ function Sidebar({
data-slot="sidebar-container"
data-side={side}
className={cn(
- "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex",
+ "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[translate,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=right]:right-0 md:flex",
+ state === "collapsed" &&
+ collapsible === "offcanvas" &&
+ (side === "left" ? "-translate-x-full" : "translate-x-full"),
// Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
@@ -255,11 +233,7 @@ function Sidebar({
)
}
-function SidebarTrigger({
- className,
- onClick,
- ...props
-}: React.ComponentProps
) {
+function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps) {
const { toggleSidebar } = useSidebar()
return (
@@ -275,7 +249,7 @@ function SidebarTrigger({
}}
{...props}
>
-
+
Toggle Sidebar
)
@@ -319,10 +293,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
)
}
-function SidebarInput({
- className,
- ...props
-}: React.ComponentProps) {
+function SidebarInput({ className, ...props }: React.ComponentProps) {
return (
) {
)
}
-function SidebarSeparator({
- className,
- ...props
-}: React.ComponentProps) {
+function SidebarSeparator({ className, ...props }: React.ComponentProps) {
return (
) {
+function SidebarGroupContent({ className, ...props }: React.ComponentProps<"div">) {
return (
{comp}
-
+
)
}
@@ -584,10 +544,7 @@ function SidebarMenuAction({
})
}
-function SidebarMenuBadge({
- className,
- ...props
-}: React.ComponentProps<"div">) {
+function SidebarMenuBadge({ className, ...props }: React.ComponentProps<"div">) {
return (
- {showIcon && (
-
- )}
+ {showIcon &&
}
) {
)
}
-function SidebarMenuSubItem({
- className,
- ...props
-}: React.ComponentProps<"li">) {
+function SidebarMenuSubItem({ className, ...props }: React.ComponentProps<"li">) {
return (
) {
- return (
-
- )
+ return
}
export { Skeleton }
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
index ddee39b..9b787af 100644
--- a/src/components/ui/sonner.tsx
+++ b/src/components/ui/sonner.tsx
@@ -1,44 +1,24 @@
-"use client"
-
-import { useTheme } from "next-themes"
+import { useEffect, useState } from "react"
import { Toaster as Sonner, type ToasterProps } from "sonner"
-import { FiCheckCircle, FiInfo, FiAlertTriangle, FiAlertOctagon, FiLoader } from "react-icons/fi"
const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme()
+ const [theme, setTheme] = useState<"light" | "dark">("light")
+
+ useEffect(() => {
+ const syncTheme = () => setTheme(document.documentElement.classList.contains("dark") ? "dark" : "light")
+ syncTheme()
+ const observer = new MutationObserver(syncTheme)
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] })
+ return () => observer.disconnect()
+ }, [])
return (
- ),
- info: (
-
- ),
- warning: (
-
- ),
- error: (
-
- ),
- loading: (
-
- ),
- }}
- style={
- {
- "--normal-bg": "var(--popover)",
- "--normal-text": "var(--popover-foreground)",
- "--normal-border": "var(--border)",
- "--border-radius": "var(--radius)",
- } as React.CSSProperties
- }
toastOptions={{
classNames: {
- toast: "cn-toast",
+ toast: "sonner-toast",
},
}}
{...props}
diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx
index f71e80e..9e99e4c 100644
--- a/src/components/ui/table.tsx
+++ b/src/components/ui/table.tsx
@@ -1,52 +1,41 @@
-"use client"
+import type * as React from "react"
-import * as React from "react"
+import { cn } from "@/lib/utils"
-import { cn } from "@/lib/utils/shadcn"
+function TableSurface({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
function Table({ className, ...props }: React.ComponentProps<"table">) {
return (
-
-
+
)
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
- return (
-
- )
+ return
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
- return (
-
- )
+ return
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
return (
tr]:last:border-b-0",
- className
- )}
+ className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
{...props}
/>
)
@@ -57,7 +46,7 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
) {
)
}
-function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+function DataTableHead({ className, ...props }: React.ComponentProps<"th">) {
return (
- ) {
)
}
-function TableCaption({
- className,
- ...props
-}: React.ComponentProps<"caption">) {
+function TableCell({ className, ...props }: React.ComponentProps<"td">) {
return (
-
)
}
+function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
+ return (
+
+ )
+}
+
export {
Table,
+ TableSurface,
TableHeader,
TableBody,
TableFooter,
TableHead,
+ DataTableHead,
TableRow,
TableCell,
TableCaption,
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
deleted file mode 100644
index c298661..0000000
--- a/src/components/ui/tabs.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-"use client"
-
-import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils/shadcn"
-
-function Tabs({
- className,
- orientation = "horizontal",
- ...props
-}: TabsPrimitive.Root.Props) {
- return (
-
- )
-}
-
-const tabsListVariants = cva(
- "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
- {
- variants: {
- variant: {
- default: "bg-background border",
- line: "gap-1 bg-transparent",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- }
-)
-
-function TabsList({
- className,
- variant = "default",
- ...props
-}: TabsPrimitive.List.Props & VariantProps) {
- return (
-
- )
-}
-
-function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
- return (
-
- )
-}
-
-function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
- return (
-
- )
-}
-
-export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
index c0eacbf..83974ed 100644
--- a/src/components/ui/textarea.tsx
+++ b/src/components/ui/textarea.tsx
@@ -1,6 +1,6 @@
-import * as React from "react"
+import type * as React from "react"
-import { cn } from "@/lib/utils/shadcn"
+import { cn } from "@/lib/utils"
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
diff --git a/src/components/ui/toggle-group.tsx b/src/components/ui/toggle-group.tsx
index 34d955a..86342fe 100644
--- a/src/components/ui/toggle-group.tsx
+++ b/src/components/ui/toggle-group.tsx
@@ -1,12 +1,9 @@
-"use client"
-
-import * as React from "react"
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group"
-import { type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils/shadcn"
+import type { VariantProps } from "class-variance-authority"
+import * as React from "react"
import { toggleVariants } from "@/components/ui/toggle"
+import { cn } from "@/lib/utils"
const ToggleGroupContext = React.createContext<
VariantProps & {
@@ -16,7 +13,7 @@ const ToggleGroupContext = React.createContext<
>({
size: "default",
variant: "default",
- spacing: 0,
+ spacing: 2,
orientation: "horizontal",
})
@@ -24,7 +21,7 @@ function ToggleGroup({
className,
variant,
size,
- spacing = 0,
+ spacing = 2,
orientation = "horizontal",
children,
...props
@@ -47,9 +44,7 @@ function ToggleGroup({
)}
{...props}
>
-
+
{children}
diff --git a/src/components/ui/toggle.tsx b/src/components/ui/toggle.tsx
index 9ecf9c8..1d47b60 100644
--- a/src/components/ui/toggle.tsx
+++ b/src/components/ui/toggle.tsx
@@ -1,12 +1,10 @@
-"use client"
-
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
import { cva, type VariantProps } from "class-variance-authority"
-import { cn } from "@/lib/utils/shadcn"
+import { cn } from "@/lib/utils"
const toggleVariants = cva(
- "group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-primary data-[state=on]:bg-primary dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+ "group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
@@ -14,8 +12,7 @@ const toggleVariants = cva(
outline: "border border-input bg-transparent hover:bg-muted",
},
size: {
- default:
- "h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
+ default: "h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
sm: "h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: "h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
},
@@ -33,13 +30,7 @@ function Toggle({
size = "default",
...props
}: TogglePrimitive.Props & VariantProps) {
- return (
-
- )
+ return
}
export { Toggle, toggleVariants }
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
index aac35fa..7824c7d 100644
--- a/src/components/ui/tooltip.tsx
+++ b/src/components/ui/tooltip.tsx
@@ -1,20 +1,9 @@
-"use client"
-
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
-import { cn } from "@/lib/utils/shadcn"
+import { cn } from "@/lib/utils"
-function TooltipProvider({
- delay = 0,
- ...props
-}: TooltipPrimitive.Provider.Props) {
- return (
-
- )
+function TooltipProvider({ delay = 0, ...props }: TooltipPrimitive.Provider.Props) {
+ return
}
function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
@@ -34,10 +23,7 @@ function TooltipContent({
children,
...props
}: TooltipPrimitive.Popup.Props &
- Pick<
- TooltipPrimitive.Positioner.Props,
- "align" | "alignOffset" | "side" | "sideOffset"
- >) {
+ Pick) {
return (
{children}
- {/* */}
diff --git a/src/components/ui/wheel-picker.tsx b/src/components/ui/wheel-picker.tsx
new file mode 100644
index 0000000..6ae73f1
--- /dev/null
+++ b/src/components/ui/wheel-picker.tsx
@@ -0,0 +1,678 @@
+import {
+ type AnimationPlaybackControls,
+ animate,
+ type MotionValue,
+ motion,
+ useMotionTemplate,
+ useMotionValue,
+ useReducedMotion,
+ useTransform,
+} from "motion/react"
+import {
+ type CSSProperties,
+ createContext,
+ type KeyboardEvent as ReactKeyboardEvent,
+ type ReactNode,
+ type PointerEvent as ReactPointerEvent,
+ useCallback,
+ useContext,
+ useEffect,
+ useId,
+ useMemo,
+ useRef,
+ useState,
+} from "react"
+
+import { cn } from "@/lib/utils"
+
+const HIDE_ANGLE = Math.PI / 2 - 0.04
+const MAX_ANGLE = Math.PI / 2
+const RUBBER_BAND = 0.32
+const FLICK_POWER = 0.22
+
+export type WheelPickerOption = string | { value: string; label?: ReactNode; disabled?: boolean }
+
+type NormalizedOption = {
+ value: string
+ label: ReactNode
+ disabled: boolean
+}
+
+export interface WheelPickerProps {
+ children: ReactNode
+ className?: string
+ itemHeight?: number
+ visibleCount?: 3 | 5 | 7
+ lens?: boolean
+ "aria-label"?: string
+}
+
+export interface WheelPickerColumnProps {
+ options: WheelPickerOption[]
+ value?: string
+ defaultValue?: string
+ onChange?: (value: string, option: { value: string; label: ReactNode }) => void
+ onValueCommit?: (value: string, option: { value: string; label: ReactNode }) => void
+ loop?: boolean
+ disabled?: boolean
+ name?: string
+ className?: string
+ id?: string
+ "aria-label"?: string
+}
+
+type WheelPickerContextValue = {
+ itemHeight: number
+ visibleCount: number
+ lens: boolean
+}
+
+const WheelPickerContext = createContext({
+ itemHeight: 44,
+ visibleCount: 5,
+ lens: true,
+})
+
+function clamp(value: number, min: number, max: number) {
+ return Math.min(Math.max(value, min), max)
+}
+
+function normalizeOption(option: WheelPickerOption): NormalizedOption {
+ if (typeof option === "string") {
+ return { value: option, label: option, disabled: false }
+ }
+
+ return {
+ value: option.value,
+ label: option.label ?? option.value,
+ disabled: option.disabled ?? false,
+ }
+}
+
+function WheelPickerItem({
+ index,
+ itemHeight,
+ itemId,
+ label,
+ loopSize,
+ offset,
+ optionDisabled,
+ selected,
+ visibleCount,
+}: {
+ index: number
+ itemHeight: number
+ itemId: string
+ label: ReactNode
+ loopSize: number
+ offset: MotionValue
+ optionDisabled: boolean
+ selected: boolean
+ visibleCount: number
+}) {
+ const step = Math.PI / (visibleCount + 2)
+ const radius = itemHeight / step
+
+ const distance = useTransform(offset, (current: number) => {
+ let delta = index * itemHeight - current
+
+ if (loopSize > 0) {
+ delta = ((delta % loopSize) + loopSize) % loopSize
+ if (delta > loopSize / 2) {
+ delta -= loopSize
+ }
+ }
+
+ return delta / itemHeight
+ })
+
+ const angle = useTransform(distance, (d) => clamp(d * step, -MAX_ANGLE, MAX_ANGLE))
+ const y = useTransform(angle, (a) => radius * Math.sin(a))
+ const rotateX = useTransform(angle, (a) => `${(-a * 180) / Math.PI}deg`)
+ const opacity = useTransform(distance, (d) => {
+ const a = Math.abs(d) * step
+
+ if (a >= HIDE_ANGLE) {
+ return 0
+ }
+
+ return Math.cos(a) ** 1.15
+ })
+ const visibility = useTransform(distance, (d) => (Math.abs(d) * step >= HIDE_ANGLE ? "hidden" : "visible"))
+ const emphasis = useTransform(distance, (d) => clamp(1 - Math.abs(d), 0, 1))
+ const mixPercent = useTransform(emphasis, (e) => (e * 100).toFixed(1))
+ const color = useMotionTemplate`color-mix(in oklab, var(--foreground) ${mixPercent}%, var(--muted-foreground))`
+
+ return (
+
+
+ {label}
+
+
+ )
+}
+
+export function WheelPickerColumn({
+ options,
+ value,
+ defaultValue,
+ onChange,
+ onValueCommit,
+ loop = false,
+ disabled = false,
+ name,
+ className,
+ id: idProp,
+ "aria-label": ariaLabel,
+}: WheelPickerColumnProps) {
+ const { itemHeight, visibleCount, lens } = useContext(WheelPickerContext)
+ const generatedId = useId()
+ const id = idProp ?? `wheel-column-${generatedId.replace(/[«»:]/g, "")}`
+ const prefersReducedMotion = useReducedMotion()
+
+ const items = useMemo(() => options.map(normalizeOption), [options])
+ const count = items.length
+ const canLoop = loop && count >= visibleCount + 2
+ const loopSize = canLoop ? count * itemHeight : 0
+ const maxOffset = Math.max(0, (count - 1) * itemHeight)
+ const step = Math.PI / (visibleCount + 2)
+ const radius = itemHeight / step
+
+ const initialIndexRef = useRef(null)
+
+ if (initialIndexRef.current === null) {
+ const initial = value ?? defaultValue
+ const initialIndex = initial == null ? -1 : items.findIndex((item) => item.value === initial)
+ const firstEnabled = items.findIndex((item) => !item.disabled)
+ initialIndexRef.current = initialIndex >= 0 ? initialIndex : Math.max(0, firstEnabled)
+ }
+
+ const offset = useMotionValue(initialIndexRef.current * itemHeight)
+ const [activeIndex, setActiveIndex] = useState(initialIndexRef.current)
+
+ const activeIndexRef = useRef(activeIndex)
+ const committedIndexRef = useRef(activeIndex)
+ const itemsRef = useRef(items)
+ const onChangeRef = useRef(onChange)
+ const onValueCommitRef = useRef(onValueCommit)
+ const animationRef = useRef(null)
+ const wheelTimerRef = useRef | null>(null)
+ const containerRef = useRef(null)
+ const dragRef = useRef<{
+ pointerId: number
+ startClientY: number
+ startOffset: number
+ moved: boolean
+ } | null>(null)
+
+ useEffect(() => {
+ itemsRef.current = items
+ onChangeRef.current = onChange
+ onValueCommitRef.current = onValueCommit
+ })
+
+ const wrapIndex = useCallback(
+ (index: number) => {
+ if (count === 0) {
+ return 0
+ }
+
+ if (canLoop) {
+ return ((index % count) + count) % count
+ }
+
+ return clamp(index, 0, count - 1)
+ },
+ [canLoop, count]
+ )
+
+ const nearestEnabled = useCallback(
+ (index: number, direction: number) => {
+ if (count === 0 || !items[wrapIndex(index)]?.disabled) {
+ return index
+ }
+
+ const preferred = direction === 0 ? [1, -1] : [direction, -direction]
+
+ for (let span = 1; span < count; span += 1) {
+ for (const sign of preferred) {
+ const candidate = index + sign * span
+
+ if (!canLoop && (candidate < 0 || candidate > count - 1)) {
+ continue
+ }
+
+ if (!items[wrapIndex(candidate)]?.disabled) {
+ return candidate
+ }
+ }
+ }
+
+ return index
+ },
+ [canLoop, count, items, wrapIndex]
+ )
+
+ const stopAnimation = useCallback(() => {
+ animationRef.current?.stop()
+ animationRef.current = null
+ }, [])
+
+ const commit = useCallback(() => {
+ const index = activeIndexRef.current
+
+ if (committedIndexRef.current === index) {
+ return
+ }
+
+ committedIndexRef.current = index
+ const item = itemsRef.current[index]
+
+ if (item) {
+ onValueCommitRef.current?.(item.value, {
+ value: item.value,
+ label: item.label,
+ })
+ }
+ }, [])
+
+ const animateTo = useCallback(
+ (target: number, velocity = 0) => {
+ stopAnimation()
+
+ if (prefersReducedMotion) {
+ animationRef.current = animate(offset, target, {
+ duration: 0.16,
+ ease: "easeOut",
+ onComplete: commit,
+ })
+ return
+ }
+
+ animationRef.current = animate(offset, target, {
+ type: "spring",
+ stiffness: 200,
+ damping: 24,
+ mass: 0.9,
+ velocity,
+ restDelta: 0.25,
+ restSpeed: 2,
+ onComplete: commit,
+ })
+ },
+ [commit, offset, prefersReducedMotion, stopAnimation]
+ )
+
+ const snapToIndex = useCallback(
+ (index: number, velocity = 0) => {
+ if (count === 0) {
+ return
+ }
+
+ const safeIndex = clamp(wrapIndex(index), 0, count - 1)
+ let target: number
+
+ if (canLoop) {
+ const current = Math.round(offset.get() / itemHeight)
+ let diff = (((safeIndex - wrapIndex(current)) % count) + count) % count
+
+ if (diff > count / 2) {
+ diff -= count
+ }
+
+ target = (current + diff) * itemHeight
+ } else {
+ target = safeIndex * itemHeight
+ }
+
+ animateTo(target, velocity)
+ },
+ [animateTo, canLoop, count, itemHeight, offset, wrapIndex]
+ )
+
+ const settle = useCallback(() => {
+ if (count === 0) {
+ return
+ }
+
+ const velocity = offset.getVelocity()
+ let projected = offset.get() + velocity * FLICK_POWER
+
+ if (!canLoop) {
+ projected = clamp(projected, 0, maxOffset)
+ }
+
+ const direction = velocity > 20 ? 1 : velocity < -20 ? -1 : 0
+ const index = nearestEnabled(Math.round(projected / itemHeight), direction)
+ snapToIndex(wrapIndex(index), velocity)
+ }, [canLoop, count, itemHeight, maxOffset, nearestEnabled, offset, snapToIndex, wrapIndex])
+
+ useEffect(
+ () =>
+ offset.on("change", (current) => {
+ const index = wrapIndex(Math.round(current / itemHeight))
+
+ if (index === activeIndexRef.current) {
+ return
+ }
+
+ activeIndexRef.current = index
+ setActiveIndex(index)
+ const item = itemsRef.current[index]
+
+ if (item) {
+ onChangeRef.current?.(item.value, {
+ value: item.value,
+ label: item.label,
+ })
+ }
+ }),
+ [itemHeight, offset, wrapIndex]
+ )
+
+ useEffect(() => {
+ if (value == null) {
+ return
+ }
+
+ const index = items.findIndex((item) => item.value === value)
+
+ if (index < 0 || index === activeIndexRef.current || dragRef.current) {
+ return
+ }
+
+ snapToIndex(index)
+ }, [items, snapToIndex, value])
+
+ useEffect(() => {
+ if (count === 0 || dragRef.current || animationRef.current) {
+ return
+ }
+
+ const maxIndex = count - 1
+
+ if (activeIndexRef.current > maxIndex) {
+ snapToIndex(maxIndex)
+ }
+ }, [count, snapToIndex])
+
+ useEffect(() => {
+ if (dragRef.current || animationRef.current) {
+ return
+ }
+
+ offset.set(activeIndexRef.current * itemHeight)
+ }, [itemHeight, offset])
+
+ useEffect(() => {
+ if (canLoop || count === 0 || dragRef.current) {
+ return
+ }
+
+ if (offset.get() > maxOffset) {
+ snapToIndex(count - 1)
+ }
+ }, [canLoop, count, maxOffset, offset, snapToIndex])
+
+ useEffect(() => {
+ const node = containerRef.current
+
+ if (!node || disabled) {
+ return
+ }
+
+ const handleWheel = (event: WheelEvent) => {
+ event.preventDefault()
+ stopAnimation()
+
+ const delta = event.deltaMode === 1 ? event.deltaY * (itemHeight / 2) : event.deltaY
+ let next = offset.get() + delta * 0.55
+
+ if (!canLoop) {
+ next = clamp(next, 0, maxOffset)
+ }
+
+ offset.set(next)
+
+ if (wheelTimerRef.current) {
+ clearTimeout(wheelTimerRef.current)
+ }
+
+ wheelTimerRef.current = setTimeout(() => {
+ wheelTimerRef.current = null
+ settle()
+ }, 130)
+ }
+
+ node.addEventListener("wheel", handleWheel, { passive: false })
+
+ return () => {
+ node.removeEventListener("wheel", handleWheel)
+ }
+ }, [canLoop, disabled, itemHeight, maxOffset, offset, settle, stopAnimation])
+
+ useEffect(
+ () => () => {
+ animationRef.current?.stop()
+
+ if (wheelTimerRef.current) {
+ clearTimeout(wheelTimerRef.current)
+ }
+ },
+ []
+ )
+
+ const handlePointerDown = (event: ReactPointerEvent) => {
+ if (disabled || event.button > 0) {
+ return
+ }
+
+ stopAnimation()
+ event.currentTarget.setPointerCapture(event.pointerId)
+ dragRef.current = {
+ pointerId: event.pointerId,
+ startClientY: event.clientY,
+ startOffset: offset.get(),
+ moved: false,
+ }
+ }
+
+ const handlePointerMove = (event: ReactPointerEvent) => {
+ const drag = dragRef.current
+
+ if (!drag || drag.pointerId !== event.pointerId) {
+ return
+ }
+
+ const delta = drag.startClientY - event.clientY
+
+ if (Math.abs(delta) > 3) {
+ drag.moved = true
+ }
+
+ let next = drag.startOffset + delta
+
+ if (!canLoop) {
+ if (next < 0) {
+ next *= RUBBER_BAND
+ } else if (next > maxOffset) {
+ next = maxOffset + (next - maxOffset) * RUBBER_BAND
+ }
+ }
+
+ offset.set(next)
+ }
+
+ const handlePointerEnd = (event: ReactPointerEvent) => {
+ const drag = dragRef.current
+
+ if (!drag || drag.pointerId !== event.pointerId) {
+ return
+ }
+
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
+ event.currentTarget.releasePointerCapture(event.pointerId)
+ }
+
+ dragRef.current = null
+
+ if (drag.moved || event.type === "pointercancel") {
+ settle()
+ return
+ }
+
+ const node = containerRef.current
+
+ if (!node) {
+ return
+ }
+
+ const rect = node.getBoundingClientRect()
+ const tapY = event.clientY - (rect.top + rect.height / 2)
+ const steps = Math.round(Math.asin(clamp(tapY / radius, -1, 1)) / step)
+
+ if (steps === 0) {
+ commit()
+ return
+ }
+
+ const current = Math.round(offset.get() / itemHeight)
+ const index = nearestEnabled(current + steps, steps > 0 ? 1 : -1)
+ snapToIndex(wrapIndex(index))
+ }
+
+ const moveBy = useCallback(
+ (delta: number) => {
+ const current = Math.round(offset.get() / itemHeight)
+ const index = nearestEnabled(current + delta, delta > 0 ? 1 : -1)
+ snapToIndex(wrapIndex(index))
+ },
+ [itemHeight, nearestEnabled, offset, snapToIndex, wrapIndex]
+ )
+
+ const handleKeyDown = (event: ReactKeyboardEvent) => {
+ if (disabled) {
+ return
+ }
+
+ switch (event.key) {
+ case "ArrowDown":
+ event.preventDefault()
+ moveBy(1)
+ break
+ case "ArrowUp":
+ event.preventDefault()
+ moveBy(-1)
+ break
+ case "PageDown":
+ event.preventDefault()
+ moveBy(visibleCount)
+ break
+ case "PageUp":
+ event.preventDefault()
+ moveBy(-visibleCount)
+ break
+ case "Home":
+ event.preventDefault()
+ snapToIndex(nearestEnabled(0, 1))
+ break
+ case "End":
+ event.preventDefault()
+ snapToIndex(nearestEnabled(count - 1, -1))
+ break
+ default:
+ break
+ }
+ }
+
+ return (
+
+ {lens ? (
+
+ ) : null}
+
+ {items.map((item, index) => (
+
+ ))}
+
+ {name ?
: null}
+
+ )
+}
+
+export function WheelPicker({
+ children,
+ className,
+ itemHeight = 44,
+ visibleCount = 5,
+ lens = true,
+ "aria-label": ariaLabel,
+}: WheelPickerProps) {
+ const context = useMemo(() => ({ itemHeight, visibleCount, lens }), [itemHeight, lens, visibleCount])
+
+ return (
+
+ {/* biome-ignore lint/a11y/useSemanticElements: fieldset chrome would fight the barrel layout; a grouped div matches the listbox columns. */}
+
+ {children}
+
+
+ )
+}
diff --git a/src/components/user-select.tsx b/src/components/user-select.tsx
deleted file mode 100644
index bcb03bd..0000000
--- a/src/components/user-select.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-"use client"
-
-import { useState } from "react"
-import { FiSearch, FiX } from "react-icons/fi"
-import { toast } from "sonner"
-import { fmtUser } from "@/lib/utils/telegram"
-import { getUserInfo, searchUserInfo } from "@/server/actions/users"
-import type { TgUser } from "@/server/trpc/types"
-import { Button } from "./ui/button"
-import { Input } from "./ui/input"
-import { Label } from "./ui/label"
-import { ToggleGroup, ToggleGroupItem } from "./ui/toggle-group"
-
-type Props = {
- onUser(user: TgUser): void
- onReset(): void
-}
-
-export function UserSelect({ onUser, onReset }: Props) {
- const [user, setUser] = useState(null)
- const [searchType, setSearchType] = useState<"username" | "id">("username")
- const [username, setUsername] = useState("")
- const [id, setId] = useState("")
-
- async function search() {
- const user = searchType === "username" ? await searchUserInfo(username) : await getUserInfo(parseInt(id, 10))
- if (!user) return toast.error(`User with this ${searchType === "id" ? "ID" : "username"} not found`)
- setUser(user)
- onUser(user)
- return
- }
-
- async function reset() {
- setUser(null)
- setUsername("")
- setId("")
- onReset()
- }
-
- async function submit(e: React.FormEvent) {
- e.preventDefault()
- await search()
- }
-
- return (
-
-
-
Search User by
-
v[0] && setSearchType(v[0] as "username" | "id")}
- >
-
- @ Username
-
-
- # ID
-
-
-
-
-
- {searchType === "username" && (
-
- Username
- setUsername(v.target.value)}
- autoComplete="off"
- />
-
- )}
-
- {searchType === "id" && (
-
- ID
- setId(v.target.value)}
- autoComplete="off"
- />
-
- )}
-
- {user ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
{user ? fmtUser(user) : ""}
-
- )
-}
diff --git a/src/constants.ts b/src/constants.ts
deleted file mode 100644
index b0d438f..0000000
--- a/src/constants.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export const COOKIES = {
- SIDEBAR_OPEN: "sidebar_open",
- SIDEBAR_CATEGORY_STATE: "sidebar_category_state",
-} as const
diff --git a/src/env.ts b/src/env.ts
index dfb523b..e987ebf 100644
--- a/src/env.ts
+++ b/src/env.ts
@@ -1,43 +1,27 @@
-import { createEnv } from "@t3-oss/env-nextjs"
+import { createEnv } from "@t3-oss/env-core"
import { z } from "zod"
+import { resolveBackendUrl } from "@/server/runtime-env"
export const env = createEnv({
- /**
- * Specify your server-side environment variables schema here. This way you can ensure the app
- * isn't built with invalid env vars.
- */
server: {
- BACKEND_URL: z.string().default("http://localhost:3000"),
- ADMIN_ORG_EMAIL: z.string().describe("Email address of admin Azure account."),
- NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
+ AGENT_MODE: z
+ .enum(["true", "false"])
+ .default("false")
+ .transform((value) => value === "true"),
+ NODE_ENV: z.enum(["development", "test", "production"]),
+ BACKEND_URL: z.url(),
},
- /**
- * Specify your client-side environment variables schema here. This way you can ensure the app
- * isn't built with invalid env vars. To expose them to the client, prefix them with
- * `NEXT_PUBLIC_`.
- */
- client: {
- // NEXT_PUBLIC_CLIENTVAR: z.string(),
- },
+ clientPrefix: "VITE_",
+
+ client: {},
+
+ shared: {},
- /**
- * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
- * middlewares) or client-side so we need to destruct manually.
- */
runtimeEnv: {
- ADMIN_ORG_EMAIL: process.env.ADMIN_ORG_EMAIL,
- BACKEND_URL: process.env.BACKEND_URL,
- NODE_ENV: process.env.NODE_ENV,
+ ...process.env,
+ BACKEND_URL: resolveBackendUrl(process.env.BACKEND_URL, process.env.NODE_ENV, process.env.npm_lifecycle_event),
},
- /**
- * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
- * useful for Docker builds.
- */
- skipValidation: !!process.env.SKIP_ENV_VALIDATION,
- /**
- * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
- * `SOME_VAR=''` will throw an error.
- */
+
emptyStringAsUndefined: true,
})
diff --git a/src/features/account/account-page.tsx b/src/features/account/account-page.tsx
new file mode 100644
index 0000000..8d978c1
--- /dev/null
+++ b/src/features/account/account-page.tsx
@@ -0,0 +1,85 @@
+import { LoaderCircle, RefreshCw } from "lucide-react"
+import { PageHeader } from "@/components/page-header"
+import { Alert, AlertAction, AlertDescription } from "@/components/ui/alert"
+import { Button } from "@/components/ui/button"
+import type { AdminSession } from "@/lib/auth"
+import { ProfileDetailsCard, ProfileSummaryCard, TelegramIdentityCard } from "./profile-sections"
+import { PasskeysCard, SessionsCard } from "./security-sections"
+import { useAccount } from "./use-account"
+
+export function AccountPage({
+ initialSession,
+ telegramRoles,
+}: {
+ initialSession: AdminSession
+ telegramRoles: string[]
+}) {
+ const account = useAccount(initialSession)
+
+ return (
+
+
+ {account.notice && (
+
+ {account.notice.text}
+
+ )}
+ {account.securityError && (
+
+ {account.securityError}
+
+ void account.refreshSecurityData(true)}
+ disabled={account.securityRefreshing}
+ >
+ {account.securityRefreshing ? (
+
+ ) : (
+
+ )}
+ Retry security data
+
+
+
+ )}
+
+
+
void account.uploadImage(file)}
+ onRemove={() => void account.removeImage()}
+ />
+ void account.updateName(event)}
+ />
+
+ void account.addPasskey()}
+ onDelete={(id) => void account.deletePasskey(id)}
+ />
+ void account.revokeOtherSessions()}
+ onLogout={() => void account.logout()}
+ />
+
+
+ )
+}
diff --git a/src/features/account/account.functions.ts b/src/features/account/account.functions.ts
new file mode 100644
index 0000000..18c1df1
--- /dev/null
+++ b/src/features/account/account.functions.ts
@@ -0,0 +1,15 @@
+import { createServerFn } from "@tanstack/react-start"
+import { authenticatedMiddleware } from "@/server/auth.middleware"
+import { parseProfilePictureForm } from "./account.validation"
+
+export const uploadProfilePicture = createServerFn({ method: "POST" })
+ .middleware([authenticatedMiddleware])
+ .validator(parseProfilePictureForm)
+ .handler(async ({ data: image, context }) => {
+ const formData = new FormData()
+ formData.set("userId", context.session.user.id)
+ formData.set("image", image)
+ const result = await context.backend.auth.updateProfilePic.mutate(formData)
+ if (!result.success) throw new Error("PROFILE_PICTURE_NOT_UPDATED")
+ return result
+ })
diff --git a/src/features/account/account.validation.ts b/src/features/account/account.validation.ts
new file mode 100644
index 0000000..3f262f3
--- /dev/null
+++ b/src/features/account/account.validation.ts
@@ -0,0 +1,7 @@
+export function parseProfilePictureForm(data: FormData) {
+ const image = data.get("image")
+ if (!(image instanceof File)) throw new Error("INVALID_IMAGE")
+ if (image.size > 1024 * 1024) throw new Error("IMAGE_TOO_LARGE")
+ if (!["image/png", "image/jpeg"].includes(image.type)) throw new Error("INVALID_IMAGE_TYPE")
+ return image
+}
diff --git a/src/features/account/confirmation-dialog.tsx b/src/features/account/confirmation-dialog.tsx
new file mode 100644
index 0000000..41b3f1a
--- /dev/null
+++ b/src/features/account/confirmation-dialog.tsx
@@ -0,0 +1,52 @@
+import { type ReactElement, type ReactNode, useState } from "react"
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/components/ui/alert-dialog"
+
+export function ConfirmationDialog({
+ trigger,
+ title,
+ description,
+ actionLabel,
+ onConfirm,
+}: {
+ trigger: ReactElement
+ title: string
+ description: ReactNode
+ actionLabel: string
+ onConfirm: () => void
+}) {
+ const [open, setOpen] = useState(false)
+
+ return (
+
+
+
+
+ {title}
+ {description}
+
+
+ Cancel
+ {
+ setOpen(false)
+ onConfirm()
+ }}
+ >
+ {actionLabel}
+
+
+
+
+ )
+}
diff --git a/src/features/account/profile-sections.tsx b/src/features/account/profile-sections.tsx
new file mode 100644
index 0000000..4d75c8d
--- /dev/null
+++ b/src/features/account/profile-sections.tsx
@@ -0,0 +1,179 @@
+import { Camera, LoaderCircle, ShieldCheck, UserRound } from "lucide-react"
+import { useRef } from "react"
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
+import { Input } from "@/components/ui/input"
+import type { AdminSession } from "@/lib/auth"
+import { ConfirmationDialog } from "./confirmation-dialog"
+
+type User = AdminSession["user"]
+
+export function ProfileSummaryCard({
+ user,
+ busy,
+ onUpload,
+ onRemove,
+}: {
+ user: User
+ busy: string | null
+ onUpload: (file?: File) => void
+ onRemove: () => void
+}) {
+ const fileInput = useRef(null)
+
+ return (
+
+
+
+
+ {user.image && }
+
+ {avatarText(user.name, user.email)}
+
+
+
fileInput.current?.click()}
+ disabled={busy === "image"}
+ aria-label="Upload profile picture"
+ >
+
+
+
onUpload(event.target.files?.[0])}
+ />
+
+
+
{user.name || "Complete your profile"}
+
{user.email}
+
+ {user.telegramUsername
+ ? `@${user.telegramUsername}`
+ : user.telegramId
+ ? `Telegram ID ${user.telegramId}`
+ : "Telegram not linked"}
+
+
+ {user.image && (
+
+ Remove picture
+
+ }
+ title="Remove profile picture?"
+ description="Your current profile picture will be removed from this account."
+ actionLabel="Remove picture"
+ onConfirm={onRemove}
+ />
+ )}
+
+
+ )
+}
+
+export function ProfileDetailsCard({
+ user,
+ name,
+ busy,
+ onNameChange,
+ onSubmit,
+}: {
+ user: User
+ name: string
+ busy: string | null
+ onNameChange: (name: string) => void
+ onSubmit: (event: React.FormEvent) => void
+}) {
+ return (
+
+
+
+
+ Profile details
+ Displayed throughout the admin console.
+
+
+
+
+
+
+ Full name
+ onNameChange(event.target.value)}
+ autoComplete="name"
+ required
+ />
+
+
+ Email address
+
+
+
+ {busy === "name" && } Save profile
+
+
+
+
+
+ )
+}
+
+export function TelegramIdentityCard({ user, roles }: { user: User; roles: readonly string[] }) {
+ return (
+
+
+
+
+ Telegram identity
+ Used to determine roles and permissions.
+
+
+
+
+
+
Username
+ {user.telegramUsername ? `@${user.telegramUsername}` : "Not available"}
+
+
+
Telegram ID
+ {user.telegramId ?? "Not linked"}
+
+
+
Roles
+
+ {roles.length ? (
+ roles.map((role) => (
+
+ {role}
+
+ ))
+ ) : (
+ No assigned roles
+ )}
+
+
+
+
+
+ )
+}
+
+function avatarText(name?: string | null, email?: string) {
+ return (name || email || "U")
+ .split(/[\s.@_-]+/)
+ .slice(0, 2)
+ .map((part) => part[0]?.toUpperCase())
+ .join("")
+}
diff --git a/src/features/account/security-sections.tsx b/src/features/account/security-sections.tsx
new file mode 100644
index 0000000..cf7c47c
--- /dev/null
+++ b/src/features/account/security-sections.tsx
@@ -0,0 +1,211 @@
+import { Calendar, KeyRound, LogOut, MonitorSmartphone, Shield, Trash2 } from "lucide-react"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
+import { Skeleton } from "@/components/ui/skeleton"
+import { ConfirmationDialog } from "./confirmation-dialog"
+import type { ActiveSession, Passkey } from "./types"
+
+export function PasskeysCard({
+ passkeys,
+ busy,
+ loading,
+ onAdd,
+ onDelete,
+}: {
+ passkeys: Passkey[]
+ busy: string | null
+ loading: boolean
+ onAdd: () => void
+ onDelete: (id: string) => void
+}) {
+ return (
+
+
+
+
+
+ Passkeys
+ Phishing-resistant access from your trusted devices.
+
+
+
+ Add passkey
+
+
+
+ {loading ? (
+
+ ) : (
+
+ {passkeys.map((passkey) => (
+
+ Added{" "}
+ {passkey.createdAt ? new Date(passkey.createdAt).toLocaleDateString() : "recently"}
+ {passkey.deviceType ? ` · ${passkey.deviceType}` : ""}
+ >
+ }
+ action={
+
+
+
+ }
+ title="Delete passkey?"
+ description={`The passkey ${passkey.name || "Unnamed passkey"} will no longer sign in to this account.`}
+ actionLabel="Delete passkey"
+ onConfirm={() => onDelete(passkey.id)}
+ />
+ }
+ />
+ ))}
+ {!passkeys.length && (
+ No passkeys registered yet.
+ )}
+
+ )}
+
+
+ )
+}
+
+export function SessionsCard({
+ sessions,
+ currentSessionId,
+ busy,
+ loading,
+ onRevokeOthers,
+ onLogout,
+}: {
+ sessions: ActiveSession[]
+ currentSessionId: string
+ busy: string | null
+ loading: boolean
+ onRevokeOthers: () => void
+ onLogout: () => void
+}) {
+ return (
+
+
+
+
+
+ Active sessions
+ Devices currently signed in to your account.
+
+
+ {sessions.length > 1 && (
+
+ Sign out other sessions
+
+ }
+ title="Sign out other sessions?"
+ description={`This will sign out ${sessions.length - 1} other active ${sessions.length === 2 ? "session" : "sessions"}. Your current session will stay signed in.`}
+ actionLabel="Sign out sessions"
+ onConfirm={onRevokeOthers}
+ />
+ )}
+
+
+ {loading ? (
+
+ ) : (
+
+ {sessions.map((activeSession) => (
+
+ {activeSession.ipAddress || "Unknown IP"}
+ {activeSession.createdAt
+ ? ` · Since ${new Date(activeSession.createdAt).toLocaleDateString()}`
+ : ""}
+ >
+ }
+ action={
+ activeSession.id === currentSessionId ? (
+ Current
+ ) : undefined
+ }
+ />
+ ))}
+
+ )}
+
+
+
+ Sign out of this device
+
+
+
+ )
+}
+
+function SecurityList({ children }: { children: React.ReactNode }) {
+ return {children}
+}
+
+function SecurityLoading({ rows }: { rows: number }) {
+ return (
+
+
Loading security information
+ {Array.from({ length: rows }, (_, index) => (
+
+ ))}
+
+ )
+}
+
+function SecurityItem({
+ icon: Icon,
+ title,
+ description,
+ action,
+}: {
+ icon: typeof KeyRound
+ title: string
+ description: React.ReactNode
+ action?: React.ReactNode
+}) {
+ return (
+
+
+
+
+
+
{title}
+
{description}
+
+ {action}
+
+ )
+}
diff --git a/src/features/account/types.ts b/src/features/account/types.ts
new file mode 100644
index 0000000..21dab5f
--- /dev/null
+++ b/src/features/account/types.ts
@@ -0,0 +1,16 @@
+export type Passkey = {
+ id: string
+ name?: string | null
+ createdAt?: Date | string
+ deviceType?: string
+}
+
+export type ActiveSession = {
+ id: string
+ token: string
+ userAgent?: string | null
+ ipAddress?: string | null
+ createdAt?: Date | string
+}
+
+export type AccountNotice = { type: "success" | "error"; text: string } | null
diff --git a/src/features/account/use-account.ts b/src/features/account/use-account.ts
new file mode 100644
index 0000000..bf4dd02
--- /dev/null
+++ b/src/features/account/use-account.ts
@@ -0,0 +1,208 @@
+import { useRouter } from "@tanstack/react-router"
+import { useServerFn } from "@tanstack/react-start"
+import { useCallback, useEffect, useMemo, useState } from "react"
+import { type AdminSession, auth, useSession } from "@/lib/auth"
+import { uploadProfilePicture } from "./account.functions"
+import type { AccountNotice, ActiveSession, Passkey } from "./types"
+
+export function useAccount(initialSession: AdminSession) {
+ const router = useRouter()
+ const uploadProfilePictureFn = useServerFn(uploadProfilePicture)
+ const sessionQuery = useSession()
+ const session = sessionQuery.data ?? initialSession
+ const user = session.user
+ const [name, setName] = useState(user.name ?? "")
+ const [passkeys, setPasskeys] = useState([])
+ const [sessions, setSessions] = useState([])
+ const [securityLoading, setSecurityLoading] = useState(true)
+ const [securityRefreshing, setSecurityRefreshing] = useState(false)
+ const [securityError, setSecurityError] = useState("")
+ const [busy, setBusy] = useState(null)
+ const [notice, setNotice] = useState(null)
+
+ const sortedSessions = useMemo(
+ () => sessions.toSorted((item) => (item.id === session.session.id ? -1 : 0)),
+ [session.session.id, sessions]
+ )
+
+ const refreshSecurityData = useCallback(async (isRetry = false) => {
+ if (isRetry) setSecurityRefreshing(true)
+ try {
+ const [passkeyResult, sessionResult] = await Promise.all([auth.passkey.listUserPasskeys(), auth.listSessions()])
+ if (passkeyResult.error || sessionResult.error) {
+ setSecurityError("Could not load passkeys and active sessions. Your existing security data is still shown.")
+ return false
+ }
+ setPasskeys(passkeyResult.data ?? [])
+ setSessions(sessionResult.data ?? [])
+ setSecurityError("")
+ return true
+ } catch {
+ setSecurityError("Could not load passkeys and active sessions. Your existing security data is still shown.")
+ return false
+ } finally {
+ setSecurityLoading(false)
+ if (isRetry) setSecurityRefreshing(false)
+ }
+ }, [])
+
+ useEffect(() => {
+ void refreshSecurityData()
+ }, [refreshSecurityData])
+
+ useEffect(() => setName(user.name ?? ""), [user.name])
+
+ async function updateName(event: React.FormEvent) {
+ event.preventDefault()
+ setBusy("name")
+ setNotice(null)
+ try {
+ const result = await auth.updateUser({ name: name.trim() })
+ if (result.error) setNotice({ type: "error", text: result.error.message ?? "Could not update your name." })
+ else {
+ await sessionQuery.refetch()
+ setNotice({ type: "success", text: "Profile name updated." })
+ }
+ } catch {
+ setNotice({ type: "error", text: "Could not update your name." })
+ } finally {
+ setBusy(null)
+ }
+ }
+
+ async function uploadImage(file?: File) {
+ if (!file) return
+ setNotice(null)
+ if (file.size > 1024 * 1024 || !["image/png", "image/jpeg"].includes(file.type)) {
+ setNotice({ type: "error", text: "Use a PNG or JPEG image smaller than 1 MB." })
+ return
+ }
+
+ setBusy("image")
+ const formData = new FormData()
+ formData.set("image", file)
+ try {
+ await uploadProfilePictureFn({ data: formData })
+ await sessionQuery.refetch()
+ setNotice({ type: "success", text: "Profile picture updated." })
+ } catch {
+ setNotice({ type: "error", text: "Could not update your profile picture." })
+ }
+ setBusy(null)
+ }
+
+ async function removeImage() {
+ setBusy("image")
+ setNotice(null)
+ try {
+ const result = await auth.updateUser({ image: null })
+ if (result.error) setNotice({ type: "error", text: result.error.message ?? "Could not remove the picture." })
+ else {
+ await sessionQuery.refetch()
+ setNotice({ type: "success", text: "Profile picture removed." })
+ }
+ } catch {
+ setNotice({ type: "error", text: "Could not remove the picture." })
+ } finally {
+ setBusy(null)
+ }
+ }
+
+ async function addPasskey() {
+ setBusy("passkey")
+ setNotice(null)
+ try {
+ const result = await auth.passkey.addPasskey({ name: `Passkey ${passkeys.length + 1}` })
+ if (result.error) setNotice({ type: "error", text: result.error.message ?? "Could not create the passkey." })
+ else {
+ const refreshed = await refreshSecurityData()
+ setNotice({
+ type: "success",
+ text: refreshed ? "Passkey created." : "Passkey created. Refresh security data to see the updated list.",
+ })
+ }
+ } catch {
+ setNotice({ type: "error", text: "Could not create the passkey." })
+ } finally {
+ setBusy(null)
+ }
+ }
+
+ async function deletePasskey(id: string) {
+ setBusy(id)
+ setNotice(null)
+ try {
+ const result = await auth.passkey.deletePasskey({ id })
+ if (result.error) setNotice({ type: "error", text: result.error.message ?? "Could not delete the passkey." })
+ else {
+ const refreshed = await refreshSecurityData()
+ setNotice({
+ type: "success",
+ text: refreshed ? "Passkey deleted." : "Passkey deleted. Refresh security data to see the updated list.",
+ })
+ }
+ } catch {
+ setNotice({ type: "error", text: "Could not delete the passkey." })
+ } finally {
+ setBusy(null)
+ }
+ }
+
+ async function revokeOtherSessions() {
+ setBusy("sessions")
+ setNotice(null)
+ try {
+ const result = await auth.revokeOtherSessions()
+ if (result.error) setNotice({ type: "error", text: result.error.message ?? "Could not revoke other sessions." })
+ else {
+ const refreshed = await refreshSecurityData()
+ setNotice({
+ type: "success",
+ text: refreshed
+ ? "Other sessions signed out."
+ : "Sessions were signed out. Refresh security data to see the updated list.",
+ })
+ }
+ } catch {
+ setNotice({ type: "error", text: "Could not revoke other sessions." })
+ } finally {
+ setBusy(null)
+ }
+ }
+
+ async function logout() {
+ setBusy("logout")
+ setNotice(null)
+ try {
+ const result = await auth.signOut()
+ if (result.error) throw new Error(result.error.message)
+ await router.invalidate()
+ await router.navigate({ to: "/login", replace: true })
+ } catch {
+ setNotice({ type: "error", text: "Could not sign out. Please try again." })
+ setBusy(null)
+ }
+ }
+
+ return {
+ user,
+ currentSessionId: session.session.id,
+ name,
+ setName,
+ passkeys,
+ sortedSessions,
+ securityLoading,
+ securityRefreshing,
+ securityError,
+ busy,
+ notice,
+ updateName,
+ uploadImage,
+ removeImage,
+ addPasskey,
+ deletePasskey,
+ revokeOtherSessions,
+ refreshSecurityData,
+ logout,
+ }
+}
diff --git a/src/features/auth/auth.functions.ts b/src/features/auth/auth.functions.ts
new file mode 100644
index 0000000..8b801f7
--- /dev/null
+++ b/src/features/auth/auth.functions.ts
@@ -0,0 +1,21 @@
+import { createServerFn } from "@tanstack/react-start"
+import { backendMiddleware, dashboardAccessMiddleware, sessionMiddleware } from "@/server/auth.middleware"
+
+export const getSessionState = createServerFn()
+ .middleware([sessionMiddleware])
+ .handler(({ context }) => ({ session: context.session, agentMode: context.agentMode }))
+
+export const getDashboardAccess = createServerFn()
+ .middleware([dashboardAccessMiddleware])
+ .handler(({ context }) => context.dashboardAccess)
+
+export const testBackend = createServerFn()
+ .middleware([backendMiddleware])
+ .handler(async ({ context }) => {
+ try {
+ await context.backend.test.dbQuery.query({ dbName: "web" })
+ return true
+ } catch {
+ return false
+ }
+ })
diff --git a/src/features/auth/login-page.tsx b/src/features/auth/login-page.tsx
new file mode 100644
index 0000000..193380f
--- /dev/null
+++ b/src/features/auth/login-page.tsx
@@ -0,0 +1,191 @@
+import { useRouter } from "@tanstack/react-router"
+import { ArrowRight, KeyRound, LoaderCircle, Mail, ShieldCheck } from "lucide-react"
+import { useEffect, useState } from "react"
+import { toast } from "sonner"
+import { AppMark } from "@/components/app-mark"
+import { ThemeToggle } from "@/components/theme-toggle"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Field, FieldDescription, FieldGroup, FieldLabel } from "@/components/ui/field"
+import { Input } from "@/components/ui/input"
+import { Separator } from "@/components/ui/separator"
+import { auth } from "@/lib/auth"
+import { testBackend } from "./auth.functions"
+
+export function LoginPage() {
+ const [email, setEmail] = useState("")
+ const [otp, setOtp] = useState("")
+ const [sent, setSent] = useState(false)
+ const [busy, setBusy] = useState(false)
+ const [notice, setNotice] = useState("")
+ const router = useRouter()
+
+ async function sendCode() {
+ setBusy(true)
+ setNotice("")
+ try {
+ const { data, error } = await auth.emailOtp.sendVerificationOtp({ type: "sign-in", email: email.trim() })
+ if (data?.success) setSent(true)
+ else setNotice(error?.message ?? "We could not send a code. Check your email and try again.")
+ } catch {
+ setNotice("We could not reach the authentication service. Please try again.")
+ } finally {
+ setBusy(false)
+ }
+ }
+
+ async function verify() {
+ setBusy(true)
+ setNotice("")
+ try {
+ const { data, error } = await auth.signIn.emailOtp({ email: email.trim(), otp })
+ if (data) await router.navigate({ to: "/dashboard" })
+ else setNotice(error?.message ?? "That code is not valid. Please try again.")
+ } catch {
+ setNotice("We could not verify the code. Check your connection and try again.")
+ } finally {
+ setBusy(false)
+ }
+ }
+
+ async function passkey() {
+ setBusy(true)
+ setNotice("")
+ try {
+ const { data, error } = await auth.signIn.passkey()
+ if (data) await router.navigate({ to: "/dashboard" })
+ else setNotice(error?.message ?? "Passkey sign in was cancelled or unavailable.")
+ } catch {
+ setNotice("Passkey sign in is unavailable right now. Please try again.")
+ } finally {
+ setBusy(false)
+ }
+ }
+
+ useEffect(() => {
+ void testBackend().then((result) => {
+ if (!result) toast.error("Backend is offline or unavailable")
+ })
+ }, [])
+
+ return (
+
+
+
+
+
+ Internal operations
+
+
+ The work behind the network.
+
+
+ Protected access for managing association members, Telegram communities and administrative permissions.
+
+
+ © PoliNetwork APS
+
+
+
+
+ )
+}
diff --git a/src/features/azure/azure.functions.ts b/src/features/azure/azure.functions.ts
new file mode 100644
index 0000000..3f4ca55
--- /dev/null
+++ b/src/features/azure/azure.functions.ts
@@ -0,0 +1,58 @@
+import { createServerFn } from "@tanstack/react-start"
+import { z } from "zod"
+import { adminMiddleware } from "@/server/auth.middleware"
+
+export const getAzureMembers = createServerFn()
+ .middleware([adminMiddleware])
+ .handler(({ context }) => context.backend.azure.members.getAll.query())
+
+export const getAzureDirectory = createServerFn()
+ .middleware([adminMiddleware])
+ .handler(async ({ context }) => {
+ const [groups, members] = await Promise.all([
+ context.backend.azure.groups.getAll.query(),
+ context.backend.azure.members.getAll.query(),
+ ])
+ return { groups, members }
+ })
+
+export const createAzureMember = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(
+ z.object({
+ firstName: z.string().trim().min(1),
+ lastName: z.string().trim().min(1),
+ assocNumber: z.number().int().positive(),
+ sendEmailTo: z.email(),
+ })
+ )
+ .handler(async ({ data, context }) => {
+ const result = await context.backend.azure.members.create.mutate(data)
+ if (result.error) throw new Error(result.error)
+ return result
+ })
+
+export const setAzureMemberNumber = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(z.object({ userId: z.string().min(1), assocNumber: z.number().int().positive() }))
+ .handler(async ({ data, context }) => {
+ const result = await context.backend.azure.members.setAssocNumber.mutate(data)
+ if (result.error) throw new Error(result.error)
+ return result
+ })
+
+const azureGroupMembershipInput = z.object({ groupId: z.string().min(1), userId: z.string().min(1) })
+
+export const addAzureGroupMember = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(azureGroupMembershipInput)
+ .handler(async ({ data, context }) => ({
+ error: (await context.backend.azure.groups.addMember.mutate(data)) ? null : ("INTERNAL_SERVER_ERROR" as const),
+ }))
+
+export const removeAzureGroupMember = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(azureGroupMembershipInput)
+ .handler(async ({ data, context }) => ({
+ error: (await context.backend.azure.groups.removeMember.mutate(data)) ? null : ("INTERNAL_SERVER_ERROR" as const),
+ }))
diff --git a/src/features/azure/group-membership.tsx b/src/features/azure/group-membership.tsx
new file mode 100644
index 0000000..4d381dd
--- /dev/null
+++ b/src/features/azure/group-membership.tsx
@@ -0,0 +1,275 @@
+import { useRouter } from "@tanstack/react-router"
+import { useServerFn } from "@tanstack/react-start"
+import { LoaderCircle, UserMinus, UserPlus } from "lucide-react"
+import { useMemo, useState } from "react"
+import { toast } from "sonner"
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
+import { Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount } from "@/components/ui/avatar"
+import { Button } from "@/components/ui/button"
+import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog"
+import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
+import { addAzureGroupMember, removeAzureGroupMember } from "@/features/azure/azure.functions"
+import type { AzureGroup, AzureMember } from "@/lib/api/types"
+
+const MAX_VISIBLE_MEMBERS = 7
+
+type MemberChoice = Pick
+
+export function GroupMembership({ group, directoryMembers }: { group: AzureGroup; directoryMembers: AzureMember[] }) {
+ const sortedMembers = [...group.members].sort((a, b) => a.displayName.localeCompare(b.displayName))
+ const visibleMembers = sortedMembers.slice(0, MAX_VISIBLE_MEMBERS)
+ const hiddenMembers = sortedMembers.slice(MAX_VISIBLE_MEMBERS)
+
+ return (
+
+ {group.members.length > 0 ? (
+
+ {visibleMembers.map((member) => (
+
+ ))}
+ {hiddenMembers.length > 0 && }
+
+ ) : (
+
No members yet
+ )}
+
+
+
+
+
+ )
+}
+
+function MemberAvatar({ member }: { member: AzureGroup["members"][number] }) {
+ return (
+
+
+
+ {initials(member.displayName)}
+
+
+ {member.displayName}
+
+ )
+}
+
+function HiddenMembersCount({ members }: { members: AzureGroup["members"] }) {
+ return (
+
+
+ +{members.length}
+
+
+ {members.map((member) => (
+
+
+ {initials(member.displayName)}
+
+
{member.displayName}
+
+ ))}
+
+
+ )
+}
+
+function MembershipDialog({
+ group,
+ directoryMembers,
+ mode,
+}: {
+ group: AzureGroup
+ directoryMembers: AzureMember[]
+ mode: "add" | "remove"
+}) {
+ const [open, setOpen] = useState(false)
+ const [pending, setPending] = useState(false)
+ const [selectedMember, setSelectedMember] = useState(null)
+ const [confirmRemoval, setConfirmRemoval] = useState(false)
+ const router = useRouter()
+ const addGroupMember = useServerFn(addAzureGroupMember)
+ const removeGroupMember = useServerFn(removeAzureGroupMember)
+ const adding = mode === "add"
+ const Icon = adding ? UserPlus : UserMinus
+
+ const choices = useMemo(() => {
+ const currentIds = new Set(group.members.map((member) => member.id))
+ const members = adding
+ ? directoryMembers.filter((member) => !currentIds.has(member.id))
+ : group.members.map((member) => {
+ const directoryMember = directoryMembers.find((candidate) => candidate.id === member.id)
+ return {
+ id: member.id,
+ displayName: member.displayName,
+ mail: directoryMember?.mail ?? null,
+ }
+ })
+ return members.sort((a, b) => (a.displayName ?? "").localeCompare(b.displayName ?? ""))
+ }, [adding, directoryMembers, group.members])
+
+ function handleOpenChange(nextOpen: boolean) {
+ if (pending) return
+ setOpen(nextOpen)
+ if (!nextOpen) {
+ setSelectedMember(null)
+ setConfirmRemoval(false)
+ }
+ }
+
+ async function updateMembership() {
+ if (!selectedMember || pending) return
+ setPending(true)
+ try {
+ const input = { data: { groupId: group.id, userId: selectedMember.id } }
+ const result = adding ? await addGroupMember(input) : await removeGroupMember(input)
+ if (result.error) {
+ toast.error(mutationErrorMessage(result.error))
+ return
+ }
+
+ toast.success(
+ `${selectedMember.displayName ?? "Member"} ${adding ? "added to" : "removed from"} ${group.displayName}.`
+ )
+ setOpen(false)
+ setSelectedMember(null)
+ setConfirmRemoval(false)
+ try {
+ await router.invalidate({ sync: true })
+ } catch {
+ toast.warning("The membership was updated, but the latest group data could not be refreshed.")
+ }
+ } catch (error) {
+ console.error(error)
+ toast.error(`There was an unexpected error while ${adding ? "adding" : "removing"} the member.`)
+ } finally {
+ setPending(false)
+ }
+ }
+
+ const actionLabel = adding ? "Add member" : "Remove member"
+
+ return (
+
+
+
+ }
+ />
+ }
+ >
+
+
+ {actionLabel}
+
+
+
+
+ {adding ? "Add a group member" : "Remove a group member"}
+
+ Choose {adding ? "a directory user to add to" : "a member to remove from"}{" "}
+ {group.displayName} .
+ {!adding && " Their Microsoft 365 account will not be deleted."}
+
+
+
+
+
+
+ {adding ? "No available users found." : "No group members found."}
+
+ {choices.map((member) => (
+ {
+ setSelectedMember(member)
+ setConfirmRemoval(false)
+ }}
+ >
+
+ {initials(member.displayName)}
+
+
+ {member.displayName ?? "Unnamed user"}
+ {member.mail && {member.mail} }
+
+
+ ))}
+
+
+
+
+ {confirmRemoval && selectedMember && (
+
+ Remove {selectedMember.displayName ?? "this member"}?
+
+ They will lose access to this Microsoft 365 group. Their account will not be deleted.
+
+
+ )}
+
+
+ {confirmRemoval ? (
+ setConfirmRemoval(false)}>
+ Keep member
+
+ ) : (
+ }>Cancel
+ )}
+ {
+ if (!adding && !confirmRemoval) {
+ setConfirmRemoval(true)
+ return
+ }
+ void updateMembership()
+ }}
+ disabled={!selectedMember || pending}
+ >
+ {pending ? (
+
+ ) : (
+
+ )}
+ {pending ? (adding ? "Adding…" : "Removing…") : confirmRemoval ? "Remove member" : actionLabel}
+
+
+
+
+ )
+}
+
+function initials(name: string | null | undefined) {
+ const parts = name?.trim().split(/\s+/).filter(Boolean) ?? []
+ if (parts.length === 0) return "?"
+ return parts
+ .slice(0, 2)
+ .map((part) => part[0])
+ .join("")
+ .toUpperCase()
+}
+
+function mutationErrorMessage(error: string) {
+ if (error === "UNAUTHORIZED") return "You don't have permission to manage this group."
+ return "Microsoft 365 could not complete the change. Please try again."
+}
diff --git a/src/features/azure/groups-page.tsx b/src/features/azure/groups-page.tsx
new file mode 100644
index 0000000..50eeff6
--- /dev/null
+++ b/src/features/azure/groups-page.tsx
@@ -0,0 +1,190 @@
+import { ChevronDown, Info, Mail, UsersRound } from "lucide-react"
+import { useMemo } from "react"
+import { EmptyState } from "@/components/empty-state"
+import { PageHeader } from "@/components/page-header"
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader } from "@/components/ui/card"
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
+import { Separator } from "@/components/ui/separator"
+import { Skeleton } from "@/components/ui/skeleton"
+import type { AzureGroup, AzureMember } from "@/lib/api/types"
+import { GroupMembership } from "./group-membership"
+
+export function AzureGroupsPage({
+ groups,
+ directoryMembers,
+}: {
+ groups: AzureGroup[]
+ directoryMembers: AzureMember[]
+}) {
+ return (
+
+
+ {groups.length ? (
+
+ ) : (
+
+ )}
+
+ )
+}
+
+function GroupsHeader({ groups }: { groups: AzureGroup[] }) {
+ const memberships = groups.reduce((count, group) => count + group.members.length, 0)
+
+ return (
+ <>
+
+ {groups.length} groups
+ {memberships} memberships
+
+ }
+ />
+
+
+ Looking for groups with zero or one member?
+ They are collected in the collapsed section at the bottom of the page.
+
+ >
+ )
+}
+
+function GroupsList({ groups, directoryMembers }: { groups: AzureGroup[]; directoryMembers: AzureMember[] }) {
+ const sortedGroups = useMemo(() => [...groups].sort((a, b) => a.displayName.localeCompare(b.displayName)), [groups])
+ const multiMemberGroups = sortedGroups.filter((group) => group.members.length > 1)
+ const singleMemberGroups = sortedGroups.filter((group) => group.members.length <= 1)
+
+ return (
+
+ {multiMemberGroups.length > 0 && (
+
+ )}
+ {singleMemberGroups.length > 0 && (
+
+ )}
+
+ )
+}
+
+function GroupSection({
+ title,
+ groups,
+ directoryMembers,
+ defaultOpen = false,
+}: {
+ title: string
+ groups: AzureGroup[]
+ directoryMembers: AzureMember[]
+ defaultOpen?: boolean
+}) {
+ return (
+
+
+
+ }
+ >
+
+ {title}
+
+ {groups.length} {groups.length === 1 ? "group" : "groups"}
+
+
+
+
+
+ }>
+ {groups.map((group) => (
+
+
+
+
+ ))}
+
+
+
+ )
+}
+
+function GroupRow({ group, directoryMembers }: { group: AzureGroup; directoryMembers: AzureMember[] }) {
+ return (
+
+
+
+
{group.displayName}
+ {group.mailAddress && (
+
+
+ {group.mailAddress}
+
+ )}
+
+
+ {group.members.length} {group.members.length === 1 ? "member" : "members"}
+
+
+
+
+
+ )
+}
+
+export function AzureGroupsSkeleton() {
+ return (
+
+
Loading Microsoft 365 groups
+
+
+
+
+
+
+
+
+ {Array.from({ length: 4 }, (_, index) => (
+
+
+
+
+
+
+
+
+
+ {Array.from({ length: 5 }, (_, avatarIndex) => (
+
+ ))}
+
+
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/features/azure/member-dialog.tsx b/src/features/azure/member-dialog.tsx
new file mode 100644
index 0000000..368d863
--- /dev/null
+++ b/src/features/azure/member-dialog.tsx
@@ -0,0 +1,162 @@
+import { useServerFn } from "@tanstack/react-start"
+import { LoaderCircle } from "lucide-react"
+import { useState } from "react"
+import { Button } from "@/components/ui/button"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
+import { Input } from "@/components/ui/input"
+import { createAzureMember, setAzureMemberNumber } from "@/features/azure/azure.functions"
+import type { AzureMember } from "@/lib/api/types"
+
+export type MemberDialogState = { mode: "create" } | { mode: "edit"; member: AzureMember }
+
+export function MemberDialog({
+ dialog,
+ onClose,
+ onOptimisticUpdate,
+ onSaved,
+}: {
+ dialog: MemberDialogState
+ onClose: () => void
+ onOptimisticUpdate: (member: AzureMember) => () => void
+ onSaved: (mode: "create" | "edit") => Promise
+}) {
+ const editing = dialog.mode === "edit"
+ const [firstName, setFirstName] = useState("")
+ const [lastName, setLastName] = useState("")
+ const [email, setEmail] = useState("")
+ const [memberId, setMemberId] = useState(editing ? (dialog.member.employeeId ?? "") : "")
+ const [pending, setPending] = useState(false)
+ const [error, setError] = useState("")
+ const createMember = useServerFn(createAzureMember)
+ const setMemberNumber = useServerFn(setAzureMemberNumber)
+
+ async function submit(event: React.FormEvent) {
+ event.preventDefault()
+ setPending(true)
+ setError("")
+ const assocNumber = Number.parseInt(memberId, 10)
+ if (!Number.isInteger(assocNumber) || assocNumber <= 0) {
+ setError("Enter a valid positive member ID.")
+ setPending(false)
+ return
+ }
+ let rollback: (() => void) | undefined
+ try {
+ if (editing) {
+ rollback = onOptimisticUpdate({ ...dialog.member, employeeId: String(assocNumber), isMember: true })
+ await setMemberNumber({ data: { userId: dialog.member.id, assocNumber } })
+ await onSaved("edit")
+ } else {
+ await createMember({ data: { firstName, lastName, assocNumber, sendEmailTo: email } })
+ await onSaved("create")
+ }
+ } catch {
+ rollback?.()
+ setError("The member could not be saved. Check the values and your permissions.")
+ setPending(false)
+ }
+ }
+
+ return (
+ {
+ if (!open) onClose()
+ }}
+ >
+
+
+
+ AZURE MEMBERS
+
+
+ {editing ? "Set member ID" : "Create a member"}
+
+
+ {editing
+ ? "Update the association number linked to this Azure account."
+ : "Create a member association and send a welcome email."}
+
+
+ void submit(event)}>
+
+ {!editing && (
+ <>
+
+
+ First name
+
+ setFirstName(event.target.value)}
+ required
+ autoFocus
+ />
+
+
+
+ Last name
+
+ setLastName(event.target.value)}
+ required
+ />
+
+
+
+ Welcome email recipient
+
+ setEmail(event.target.value)}
+ required
+ />
+
+ >
+ )}
+
+
+ Member ID
+
+ setMemberId(event.target.value.replace(/\D/g, ""))}
+ required
+ autoFocus={editing}
+ />
+
+ {error && {error}
}
+
+
+
+ Cancel
+
+
+ {pending && }
+ {editing ? "Save member ID" : "Create member"}
+
+
+
+
+
+ )
+}
diff --git a/src/features/azure/members-page.tsx b/src/features/azure/members-page.tsx
new file mode 100644
index 0000000..60e6f69
--- /dev/null
+++ b/src/features/azure/members-page.tsx
@@ -0,0 +1,282 @@
+import { useRouter } from "@tanstack/react-router"
+import type { Column } from "@tanstack/react-table"
+import { ArrowDown, ArrowUp, Building2, Check, ChevronsUpDown, Plus, UsersRound } from "lucide-react"
+import { useEffect, useMemo, useState } from "react"
+import { toast } from "sonner"
+import { DataToolbar } from "@/components/data-toolbar"
+import { EmptyState } from "@/components/empty-state"
+import { Pagination } from "@/components/pagination"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { DataTableHead, Table, TableBody, TableCell, TableHeader, TableRow, TableSurface } from "@/components/ui/table"
+import type { AzureMember } from "@/lib/api/types"
+import { createAppColumnHelper, type dashboardFeatures, useAppTable } from "@/lib/table"
+import { cn } from "@/lib/utils"
+import { MemberDialog, type MemberDialogState } from "./member-dialog"
+
+const memberColumnHelper = createAppColumnHelper()
+
+type MemberFilter = { query: string; membersOnly: boolean }
+
+function memberFilterFrom(value: unknown): MemberFilter {
+ if (!value || typeof value !== "object") return { query: "", membersOnly: false }
+ return {
+ query: "query" in value && typeof value.query === "string" ? value.query : "",
+ membersOnly: "membersOnly" in value && value.membersOnly === true,
+ }
+}
+
+export function AzureMembersPage({ initialMembers }: { initialMembers: AzureMember[] }) {
+ const router = useRouter()
+ const [dialog, setDialog] = useState(null)
+ const [members, setMembers] = useState(initialMembers)
+
+ useEffect(() => setMembers(initialMembers), [initialMembers])
+
+ const columns = useMemo(() => {
+ const header = (
+ label: string,
+ column: Pick, "getIsSorted" | "getToggleSortingHandler">
+ ) => {
+ const sorted = column.getIsSorted()
+ const Icon = !sorted ? ChevronsUpDown : sorted === "asc" ? ArrowUp : ArrowDown
+ return (
+
+ {label}
+
+
+ )
+ }
+ return memberColumnHelper.columns([
+ memberColumnHelper.accessor((member) => member.employeeId ?? undefined, {
+ id: "employeeId",
+ header: ({ column }) => header("Member ID", column),
+ sortUndefined: "last",
+ sortFn: (rowA, rowB, columnId) => {
+ const a = Number(rowA.getValue(columnId))
+ const b = Number(rowB.getValue(columnId))
+ return a - b
+ },
+ cell: ({ getValue }) => getValue() ?? "—",
+ }),
+ memberColumnHelper.accessor("displayName", {
+ header: ({ column }) => header("Member", column),
+ cell: ({ row }) => {
+ const member = row.original
+ return (
+
+
+ {member.displayName?.[0] ?? "?"}
+
+
+ {member.displayName ?? "Unnamed member"}
+ {member.isMember && (
+ Association member
+ )}
+
+
+ )
+ },
+ }),
+ memberColumnHelper.accessor("mail", {
+ header: ({ column }) => header("Email", column),
+ cell: ({ getValue }) =>
+ getValue() ?? Not assigned ,
+ }),
+ memberColumnHelper.accessor((member) => member.assignedLicensesIds?.length ?? 0, {
+ id: "licenses",
+ header: ({ column }) => header("Licenses", column),
+ cell: ({ row }) => (
+
+ {row.original.assignedLicensesIds?.length ? (
+ row.original.assignedLicensesIds.map((license) => (
+
+ {license.replaceAll("_", " ")}
+
+ ))
+ ) : (
+ No licenses
+ )}
+
+ ),
+ }),
+ memberColumnHelper.display({
+ id: "actions",
+ header: "",
+ cell: ({ row }) => (
+ setDialog({ mode: "edit", member: row.original })}
+ >
+ Manage
+
+ ),
+ }),
+ ])
+ }, [])
+ const table = useAppTable({
+ key: "azure-members",
+ columns,
+ data: members,
+ initialState: {
+ sorting: [{ id: "employeeId", desc: false }],
+ pagination: { pageIndex: 0, pageSize: 25 },
+ globalFilter: { query: "", membersOnly: false },
+ },
+ autoResetPageIndex: false,
+ globalFilterFn: (row, _columnId, value) => {
+ const filter = memberFilterFrom(value)
+ const member = row.original
+ return (
+ (!filter.membersOnly || !!member.isMember) &&
+ `${member.displayName ?? ""} ${member.mail ?? ""} ${member.employeeId ?? ""}`
+ .toLocaleLowerCase()
+ .includes(filter.query.toLocaleLowerCase())
+ )
+ },
+ })
+ const memberFilter = memberFilterFrom(table.state.globalFilter)
+ const membersOnly = memberFilter.membersOnly
+ const filteredRows = table.getFilteredRowModel().rows
+
+ return (
+
+
{
+ table.setGlobalFilter({ ...memberFilter, query: value })
+ table.setPageIndex(0)
+ }}
+ action={
+ setDialog({ mode: "create" })}>
+ Add member
+
+ }
+ >
+ {
+ table.setGlobalFilter({ ...memberFilter, membersOnly: !membersOnly })
+ table.setPageIndex(0)
+ }}
+ >
+ {membersOnly && } Members only
+
+
+
+
+
+
+ {members.filter((member) => member.isMember).length} association members
+
+
+
+
+
+
+ {members.filter((member) => member.assignedLicensesIds?.includes("OFFICE_365")).length}
+ {" "}
+ Office 365 licenses
+
+
+
+ {filteredRows.length ? (
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {header.isPlaceholder ? null : }
+
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getAllCells().map((cell) => (
+
+
+
+ ))}
+
+ ))}
+
+
+
+ ) : (
+
+ )}
+ {filteredRows.length > 0 && (
+
table.setPageIndex(page - 1)}
+ onPageSizeChange={(pageSize) => table.setPageSize(pageSize)}
+ />
+ )}
+ {dialog && (
+ setDialog(null)}
+ onOptimisticUpdate={(member) => {
+ const previous = members.find((current) => current.id === member.id)
+ setMembers((current) => current.map((item) => (item.id === member.id ? member : item)))
+ return () => {
+ if (previous) setMembers((current) => current.map((item) => (item.id === member.id ? previous : item)))
+ }
+ }}
+ onSaved={async (mode) => {
+ setDialog(null)
+ toast.success(mode === "create" ? "Member created." : "Member ID updated.")
+ if (mode === "create") {
+ try {
+ await router.invalidate({ sync: true })
+ } catch {
+ toast.warning("The member was created, but the latest directory data could not be refreshed.")
+ }
+ }
+ }}
+ />
+ )}
+
+ )
+}
diff --git a/src/features/dashboard/overview-page.tsx b/src/features/dashboard/overview-page.tsx
new file mode 100644
index 0000000..bd06232
--- /dev/null
+++ b/src/features/dashboard/overview-page.tsx
@@ -0,0 +1,119 @@
+import { Link } from "@tanstack/react-router"
+import { ArrowRight, BookOpen, Database, ShieldCheck, UsersRound } from "lucide-react"
+import { PageHeader } from "@/components/page-header"
+import { Card, CardContent } from "@/components/ui/card"
+
+const areas = [
+ {
+ title: "Telegram users",
+ description: "Search people and inspect membership details, roles, messages, and audit history.",
+ to: "/dashboard/telegram/users",
+ icon: UsersRound,
+ service: "Telegram",
+ },
+ {
+ title: "Telegram groups",
+ description: "Review community metadata, invite links, and public visibility.",
+ to: "/dashboard/telegram/groups",
+ icon: Database,
+ service: "Telegram",
+ },
+ {
+ title: "Grants",
+ description: "Review active and scheduled Telegram grants authorized by the association board.",
+ to: "/dashboard/telegram/grants",
+ icon: ShieldCheck,
+ service: "Telegram",
+ },
+ {
+ title: "Microsoft 365 groups",
+ description: "Review directory groups and manage their Microsoft 365 memberships.",
+ to: "/dashboard/azure/groups",
+ icon: Database,
+ service: "Azure",
+ },
+ {
+ title: "Azure members",
+ description: "Manage association numbers and Microsoft 365 license information.",
+ to: "/dashboard/azure/members",
+ icon: UsersRound,
+ service: "Azure",
+ },
+ {
+ title: "Web guides",
+ description: "Read the operational notes for maintaining PoliNetwork public content.",
+ to: "/dashboard/web/guides",
+ icon: BookOpen,
+ service: "Web",
+ },
+] as const
+
+export function DashboardOverviewPage() {
+ return (
+
+
+
+
+
+
+
Administrator workspace
+
Everything starts with a real workflow.
+
+ Every destination below opens a working view backed by the existing PoliNetwork services. Availability and
+ errors are reported inside each area instead of being represented by decorative status indicators.
+
+
+
+
+ Search and inspect records
+
+
+ Manage authorized changes
+
+
+ Review account security
+
+
+
+
+
+
+
+
+ Administrative areas
+
+
Open a focused workspace for the task at hand.
+
+
+ {areas.map(({ title, description, to, icon: Icon, service }) => (
+
+
+
+
+
+
+
+
+ {service}
+
+ {title}
+ {description}
+
+
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/features/guides/guide-dialogs.tsx b/src/features/guides/guide-dialogs.tsx
new file mode 100644
index 0000000..90f1e19
--- /dev/null
+++ b/src/features/guides/guide-dialogs.tsx
@@ -0,0 +1,251 @@
+import { useServerFn } from "@tanstack/react-start"
+import { format } from "date-fns"
+import { ChevronDownIcon, LoaderCircle, OctagonX, Upload } from "lucide-react"
+import { useRef, useState } from "react"
+import { toast } from "sonner"
+import { Alert, AlertAction, AlertDescription, AlertTitle } from "@/components/ui/alert"
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogMedia,
+ AlertDialogTitle,
+} from "@/components/ui/alert-dialog"
+import { Button } from "@/components/ui/button"
+import { Calendar } from "@/components/ui/calendar"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field"
+import { Input } from "@/components/ui/input"
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
+import { createGuide, deleteGuide } from "./guides.functions"
+import type { Guide } from "./types"
+
+export function CreateGuideDialog({
+ existingVersions,
+ suggestedVersion,
+ onClose,
+ onCreated,
+}: {
+ existingVersions: string[]
+ suggestedVersion?: string
+ onClose: () => void
+ onCreated: (guide: Guide) => void
+}) {
+ const [version, setVersion] = useState(suggestedVersion ?? "")
+ const [date, setDate] = useState(new Date())
+ const [datePickerOpen, setDatePickerOpen] = useState(false)
+ const [file, setFile] = useState(null)
+ const [pending, setPending] = useState(false)
+ const [error, setError] = useState("")
+ const [confirmDiscard, setConfirmDiscard] = useState(false)
+ const fileInput = useRef(null)
+ const initialVersion = useRef(suggestedVersion ?? "")
+ const initialDate = useRef(date)
+ const createGuideFn = useServerFn(createGuide)
+ const trimmedVersion = version.trim()
+ const duplicate = Boolean(trimmedVersion && existingVersions.includes(trimmedVersion))
+
+ async function submit(event: React.FormEvent) {
+ event.preventDefault()
+ if (!file || duplicate) return
+ if (file.type !== "application/pdf" || file.size > 2 * 1024 * 1024) {
+ setError("Choose a PDF file no larger than 2 MB.")
+ return
+ }
+
+ setPending(true)
+ setError("")
+ try {
+ const formData = new FormData()
+ formData.set("version", trimmedVersion)
+ formData.set("date", date.toISOString())
+ formData.set("file", file)
+
+ onCreated(await createGuideFn({ data: formData }))
+ } catch (cause) {
+ const message = cause instanceof Error ? cause.message : ""
+
+ setError(
+ message.includes("DUPLICATE_VERSION")
+ ? "This version already exists."
+ : message.includes("UNAUTHORIZED")
+ ? "You do not have permission to publish guides."
+ : "The guide could not be published. Check the file and try again."
+ )
+ } finally {
+ setPending(false)
+ }
+ }
+
+ const dirty = version !== initialVersion.current || Boolean(file) || date.getTime() !== initialDate.current.getTime()
+
+ function requestClose() {
+ if (pending) return
+ if (dirty) setConfirmDiscard(true)
+ else onClose()
+ }
+
+ return (
+ !open && requestClose()}>
+
+
+ WEB · GUIDES
+ Publish a new edition
+ Upload a dated PDF version of the Guida della Matricola.
+
+ void submit(event)}>
+
+
+ Version
+ setVersion(event.target.value)}
+ placeholder="e.g. 2.0"
+ aria-invalid={duplicate}
+ required
+ autoFocus
+ />
+ {duplicate ? "This version already exists." : undefined}
+
+
+ Date
+
+
+ {date ? format(date, "dd/MM/yyyy") : "Select date"}
+
+
+ }
+ />
+
+ {
+ setDate(selectedDate)
+ setDatePickerOpen(false)
+ }}
+ />
+
+
+
+
+ PDF file
+ setFile(event.target.files?.[0] ?? null)}
+ />
+ fileInput.current?.click()}
+ >
+ {" "}
+ {file?.name ?? "Choose a PDF file…"}
+
+ PDF only, up to 2 MB.
+
+ {error && {error} }
+
+ {confirmDiscard && (
+
+ Discard guide changes?
+ Your unsaved edition details and selected file will be lost.
+
+ setConfirmDiscard(false)}>
+ Keep editing
+
+
+ Discard changes
+
+
+
+ )}
+
+
+ Cancel
+
+
+ {pending && } Publish guide
+
+
+
+
+
+ )
+}
+
+export function DeleteGuideDialog({
+ guide,
+ onClose,
+ onDeleted,
+}: {
+ guide: Guide
+ onClose: () => void
+ onDeleted: (id: number) => void
+}) {
+ const [pending, setPending] = useState(false)
+ const deleteGuideFn = useServerFn(deleteGuide)
+
+ async function remove() {
+ setPending(true)
+ try {
+ await deleteGuideFn({ data: { id: guide.id } })
+ onDeleted(guide.id)
+ } catch {
+ toast.error("The guide could not be deleted. Check your permissions and try again.")
+ } finally {
+ setPending(false)
+ }
+ }
+
+ return (
+ !open && onClose()}>
+
+
+
+
+
+ Delete Guide
+
+ Are you sure you want to delete version {guide.version} ?
+ This action cannot be undone.
+
+
+
+
+
+ Cancel
+
+ void remove()}>
+ {pending ? : "Confirm"}
+
+
+
+
+ )
+}
diff --git a/src/features/guides/guides-page.tsx b/src/features/guides/guides-page.tsx
new file mode 100644
index 0000000..4eb858c
--- /dev/null
+++ b/src/features/guides/guides-page.tsx
@@ -0,0 +1,145 @@
+import { useRouter } from "@tanstack/react-router"
+import { BookOpen, Download, FileText, Plus, Trash2 } from "lucide-react"
+import { useEffect, useMemo, useState } from "react"
+import { toast } from "sonner"
+import { DataToolbar } from "@/components/data-toolbar"
+import { EmptyState } from "@/components/empty-state"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { DataTableHead, Table, TableBody, TableCell, TableHeader, TableRow, TableSurface } from "@/components/ui/table"
+import { CreateGuideDialog, DeleteGuideDialog } from "./guide-dialogs"
+import type { Guide } from "./types"
+
+function displayDate(value: string) {
+ return new Intl.DateTimeFormat("en-GB", { day: "2-digit", month: "short", year: "numeric" }).format(new Date(value))
+}
+
+export function GuidesPage({ loadedGuides }: { loadedGuides: Guide[] }) {
+ const router = useRouter()
+ const [guides, setGuides] = useState(loadedGuides)
+ const [query, setQuery] = useState("")
+ const [creating, setCreating] = useState(false)
+ const [deleting, setDeleting] = useState(null)
+
+ useEffect(() => setGuides(loadedGuides), [loadedGuides])
+
+ const filteredGuides = useMemo(() => {
+ const normalized = query.trim().toLocaleLowerCase()
+ return normalized ? guides.filter((guide) => guide.version.toLocaleLowerCase().includes(normalized)) : guides
+ }, [guides, query])
+
+ async function refresh() {
+ try {
+ await router.invalidate({ sync: true })
+ } catch {
+ toast.warning("Your change was saved, but the latest guide list could not be refreshed.")
+ }
+ }
+
+ return (
+
+
setCreating(true)}>
+ Add guide
+
+ }
+ />
+ {filteredGuides.length ? (
+
+
+
+
+ Edition
+ Published
+ File
+ Actions
+
+
+
+ {filteredGuides.map((guide, index) => (
+
+
+
+
+
+
+ Version {guide.version}
+ {index === 0 && !query && Latest }
+
+
+
+ {displayDate(guide.date)}
+
+
+
+
+ Download
+
+
+
+
+ setDeleting(guide)}
+ >
+
+
+
+
+ ))}
+
+
+
+ ) : (
+ setCreating(true)}>Add first guide : undefined}
+ />
+ )}
+ {creating && (
+ guide.version)}
+ suggestedVersion={guides[0]?.version}
+ onClose={() => setCreating(false)}
+ onCreated={(guide) => {
+ setGuides((current) => [guide, ...current])
+ setCreating(false)
+ toast.success("Guide published successfully")
+ void refresh()
+ }}
+ />
+ )}
+ {deleting && (
+ setDeleting(null)}
+ onDeleted={(id) => {
+ setGuides((current) => current.filter((guide) => guide.id !== id))
+ setDeleting(null)
+ toast.success("Guide deleted")
+ void refresh()
+ }}
+ />
+ )}
+
+ )
+}
diff --git a/src/features/guides/guides.functions.ts b/src/features/guides/guides.functions.ts
new file mode 100644
index 0000000..a78408a
--- /dev/null
+++ b/src/features/guides/guides.functions.ts
@@ -0,0 +1,32 @@
+import { createServerFn } from "@tanstack/react-start"
+import { z } from "zod"
+import { adminMiddleware } from "@/server/auth.middleware"
+import { parseGuideForm } from "./guides.validation"
+
+export const getGuides = createServerFn()
+ .middleware([adminMiddleware])
+ .handler(({ context }) => context.backend.web.guides_matricole.getAllGuides.query())
+
+export const createGuide = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(parseGuideForm)
+ .handler(async ({ data, context }) => {
+ const formData = new FormData()
+ formData.set("version", data.version)
+ formData.set("date", data.date)
+ formData.set("file", data.file)
+ formData.set("createdBy", String(context.telegramId))
+
+ const result = await context.backend.web.guides_matricole.addGuide.mutate(formData)
+ if ("error" in result) throw new Error(result.error)
+ return { id: result.id, version: result.version, date: result.date, file: result.file }
+ })
+
+export const deleteGuide = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(z.object({ id: z.number().int().positive() }))
+ .handler(async ({ data, context }) => {
+ const result = await context.backend.web.guides_matricole.deleteGuide.mutate(data)
+ if (result.error) throw new Error(result.error)
+ return result
+ })
diff --git a/src/features/guides/guides.validation.ts b/src/features/guides/guides.validation.ts
new file mode 100644
index 0000000..1dd6d81
--- /dev/null
+++ b/src/features/guides/guides.validation.ts
@@ -0,0 +1,13 @@
+import { z } from "zod"
+
+export function parseGuideForm(data: FormData) {
+ const version = data.get("version")
+ const date = data.get("date")
+ const file = data.get("file")
+
+ if (typeof version !== "string" || !version.trim() || version.trim().length > 100) throw new Error("INVALID_VERSION")
+ if (typeof date !== "string" || !z.iso.datetime().safeParse(date).success) throw new Error("INVALID_DATE")
+ if (!(file instanceof File) || file.type !== "application/pdf") throw new Error("INVALID_FILE_TYPE")
+ if (file.size > 2 * 1024 * 1024) throw new Error("FILE_TOO_LARGE")
+ return { version: version.trim(), date, file }
+}
diff --git a/src/features/guides/types.ts b/src/features/guides/types.ts
new file mode 100644
index 0000000..4168783
--- /dev/null
+++ b/src/features/guides/types.ts
@@ -0,0 +1,3 @@
+import type { ApiOutput } from "@/lib/api/types"
+
+export type Guide = ApiOutput["web"]["guides_matricole"]["getAllGuides"][number]
diff --git a/src/features/onboarding/telegram-link-page.tsx b/src/features/onboarding/telegram-link-page.tsx
new file mode 100644
index 0000000..8b482f0
--- /dev/null
+++ b/src/features/onboarding/telegram-link-page.tsx
@@ -0,0 +1,131 @@
+import { ArrowRight, CheckCircle2, Clock3, Copy, LoaderCircle, LogOut, MessageCircle, RotateCcw } from "lucide-react"
+import { AppMark } from "@/components/app-mark"
+import { ThemeToggle } from "@/components/theme-toggle"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+import { Button, buttonVariants } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
+import { Input } from "@/components/ui/input"
+import type { AdminSession } from "@/lib/auth"
+import { useTelegramLink } from "./use-telegram-link"
+
+const botUsername = import.meta.env.PROD ? "pn_ts_dev_bot" : "pn_ts_devlocal_bot"
+
+export function TelegramLinkPage({ initialSession }: { initialSession: AdminSession }) {
+ const link = useTelegramLink(initialSession)
+
+ return (
+
+
+
+
+
+
+
+ Link your Telegram account
+
+ PoliNetwork uses your Telegram identity to check which administrative roles you can access.
+
+
+
+ {link.notice && (
+
+ {link.notice.kind === "success" && }
+ {link.notice.text}
+
+ )}
+
+ {!link.ready ? (
+
+ Restoring link status…
+
+ ) : link.savedLink ? (
+
+
+
Link code for @{link.savedLink.username}
+
void link.copyCode()}
+ aria-label={`Copy Telegram link code ${link.savedLink.code}`}
+ >
+ {link.savedLink.code}
+
+
+ Expires in {formatRemaining(link.remainingSeconds)}
+
+
+
+
+
+ Open the PoliNetwork bot, send /link,
+ then use the code above. This page checks the result automatically.
+
+
+
+ ) : link.phase === "verified" ? (
+
+ Opening the dashboard…
+
+ ) : (
+ void link.startLink(event)}>
+
+ Telegram username
+ link.setUsername(event.target.value.replaceAll("@", ""))}
+ placeholder="username"
+ autoComplete="off"
+ required
+ disabled={link.phase === "starting"}
+ />
+ Enter the username without the @ sign.
+
+
+ {link.phase === "starting" && }
+ {link.phase === "expired" ? "Generate a new code" : "Generate link code"}
+
+
+ )}
+
+
+
Signed in as {link.session.user.email}
+
void link.logout()} disabled={link.loggingOut}>
+ {link.loggingOut ? (
+
+ ) : (
+
+ )}
+ Use another account
+
+
+
+
+
+ )
+}
+
+function formatRemaining(seconds: number) {
+ const minutes = Math.floor(seconds / 60)
+ const remainder = seconds % 60
+ return `${minutes}:${String(remainder).padStart(2, "0")}`
+}
diff --git a/src/features/onboarding/use-telegram-link.ts b/src/features/onboarding/use-telegram-link.ts
new file mode 100644
index 0000000..a1da933
--- /dev/null
+++ b/src/features/onboarding/use-telegram-link.ts
@@ -0,0 +1,231 @@
+import { useRouter } from "@tanstack/react-router"
+import { type FormEvent, useCallback, useEffect, useState } from "react"
+import { z } from "zod"
+import { type AdminSession, auth, useSession } from "@/lib/auth"
+
+const storageKey = "linktg"
+
+const savedLinkSchema = z.object({
+ username: z.string().min(1),
+ code: z.string().min(1),
+ ttl: z.number().positive(),
+ startTime: z.number().positive(),
+})
+
+type SavedLink = z.infer
+type Phase = "idle" | "starting" | "polling" | "expired" | "verified"
+type Notice = { kind: "error" | "success"; text: string } | null
+
+export function useTelegramLink(initialSession: AdminSession) {
+ const { data: liveSession, refetch: refetchSession } = useSession()
+ const router = useRouter()
+ const session = liveSession ?? initialSession
+ const [username, setUsername] = useState("")
+ const [savedLink, setSavedLink] = useState(null)
+ const [phase, setPhase] = useState("idle")
+ const [notice, setNotice] = useState(null)
+ const [now, setNow] = useState(0)
+ const [ready, setReady] = useState(false)
+ const [loggingOut, setLoggingOut] = useState(false)
+
+ const clearSavedLink = useCallback(() => {
+ try {
+ window.localStorage.removeItem(storageKey)
+ } catch {
+ // The in-memory flow can still be reset if storage is unavailable.
+ }
+ setSavedLink(null)
+ }, [])
+
+ const expireLink = useCallback(
+ (link: SavedLink) => {
+ clearSavedLink()
+ setUsername(link.username)
+ setPhase("expired")
+ setNotice({ kind: "error", text: "This link code expired. Generate a new code to continue." })
+ },
+ [clearSavedLink]
+ )
+
+ const completeLink = useCallback(async () => {
+ clearSavedLink()
+ setPhase("verified")
+ setNotice({ kind: "success", text: "Telegram linked. Opening the dashboard…" })
+ await refetchSession()
+ await router.invalidate()
+ await router.navigate({ to: "/dashboard", replace: true })
+ }, [clearSavedLink, refetchSession, router])
+
+ useEffect(() => {
+ try {
+ const stored = window.localStorage.getItem(storageKey)
+ if (stored) {
+ const parsed = savedLinkSchema.safeParse(JSON.parse(stored))
+ if (parsed.success) {
+ const link = parsed.data
+ setUsername(link.username)
+ if (expiresAt(link) > Date.now()) {
+ setSavedLink(link)
+ setPhase("polling")
+ } else {
+ expireLink(link)
+ }
+ } else {
+ window.localStorage.removeItem(storageKey)
+ }
+ }
+ } catch {
+ setNotice({ kind: "error", text: "Saved Telegram link state could not be restored. Generate a new code." })
+ } finally {
+ setNow(Date.now())
+ setReady(true)
+ }
+ }, [expireLink])
+
+ useEffect(() => {
+ if (!savedLink) return
+
+ const updateClock = () => {
+ const currentTime = Date.now()
+ setNow(currentTime)
+ if (currentTime >= expiresAt(savedLink)) expireLink(savedLink)
+ }
+
+ updateClock()
+ const interval = window.setInterval(updateClock, 1_000)
+ return () => window.clearInterval(interval)
+ }, [expireLink, savedLink])
+
+ useEffect(() => {
+ if (!savedLink || phase !== "polling") return
+
+ let stopped = false
+ let timeout: number | undefined
+
+ const poll = async () => {
+ if (Date.now() >= expiresAt(savedLink)) {
+ expireLink(savedLink)
+ return
+ }
+
+ try {
+ const result = await auth.telegram.link.verify({ query: { code: savedLink.code } })
+ if (stopped) return
+ if (result.error) {
+ setNotice({ kind: "error", text: result.error.message || "Telegram verification could not be checked." })
+ } else if (result.data.verified) {
+ await completeLink()
+ return
+ } else if (result.data.expired) {
+ expireLink(savedLink)
+ return
+ } else {
+ setNotice(null)
+ }
+ } catch {
+ if (!stopped) {
+ setNotice({
+ kind: "error",
+ text: "Telegram verification could not be checked. We will try again automatically.",
+ })
+ }
+ }
+
+ if (!stopped) timeout = window.setTimeout(() => void poll(), 5_000)
+ }
+
+ timeout = window.setTimeout(() => void poll(), 5_000)
+ return () => {
+ stopped = true
+ if (timeout !== undefined) window.clearTimeout(timeout)
+ }
+ }, [completeLink, expireLink, phase, savedLink])
+
+ async function startLink(event: FormEvent) {
+ event.preventDefault()
+ const telegramUsername = username.trim().replace(/^@+/, "")
+ if (!telegramUsername) return
+
+ setPhase("starting")
+ setNotice(null)
+ try {
+ const result = await auth.telegram.link.start({ telegramUsername })
+ if (result.error) {
+ setPhase("idle")
+ setNotice({ kind: "error", text: result.error.message || "A Telegram link code could not be created." })
+ return
+ }
+
+ const link = { username: telegramUsername, code: result.data.code, ttl: result.data.ttl, startTime: Date.now() }
+ try {
+ window.localStorage.setItem(storageKey, JSON.stringify(link))
+ } catch {
+ setNotice({ kind: "error", text: "The code could not be saved in this browser, but it remains usable now." })
+ }
+ setUsername(telegramUsername)
+ setSavedLink(link)
+ setNow(Date.now())
+ setPhase("polling")
+ } catch {
+ setPhase("idle")
+ setNotice({ kind: "error", text: "The authentication service could not create a Telegram link code." })
+ }
+ }
+
+ async function copyCode() {
+ if (!savedLink) return
+ try {
+ await navigator.clipboard.writeText(savedLink.code)
+ setNotice({ kind: "success", text: "Code copied to the clipboard." })
+ } catch {
+ setNotice({ kind: "error", text: "The code could not be copied. Select it and copy it manually." })
+ }
+ }
+
+ function resetLink() {
+ clearSavedLink()
+ setPhase("idle")
+ setNotice(null)
+ }
+
+ async function logout() {
+ if (loggingOut) return
+ setLoggingOut(true)
+ setNotice(null)
+ try {
+ const result = await auth.signOut()
+ if (result.error) throw new Error(result.error.message)
+ clearSavedLink()
+ await refetchSession()
+ await router.invalidate()
+ await router.navigate({ to: "/login", replace: true })
+ } catch {
+ setNotice({ kind: "error", text: "Could not sign out. Please try again." })
+ setLoggingOut(false)
+ }
+ }
+
+ const remainingSeconds = savedLink ? Math.max(0, Math.ceil((expiresAt(savedLink) - now) / 1_000)) : 0
+ const progress = savedLink ? Math.max(0, Math.min(100, (remainingSeconds / savedLink.ttl) * 100)) : 0
+
+ return {
+ session,
+ username,
+ setUsername,
+ savedLink,
+ phase,
+ notice,
+ ready,
+ loggingOut,
+ remainingSeconds,
+ progress,
+ startLink,
+ copyCode,
+ resetLink,
+ logout,
+ }
+}
+
+function expiresAt(link: SavedLink) {
+ return link.startTime + link.ttl * 1_000
+}
diff --git a/src/features/telegram/grants-page.tsx b/src/features/telegram/grants-page.tsx
new file mode 100644
index 0000000..4a3b89e
--- /dev/null
+++ b/src/features/telegram/grants-page.tsx
@@ -0,0 +1,253 @@
+import { Link } from "@tanstack/react-router"
+import type { Column } from "@tanstack/react-table"
+import { ArrowDown, ArrowUp, CalendarClock, ChevronsUpDown } from "lucide-react"
+import { useMemo, useState } from "react"
+import { DataToolbar } from "@/components/data-toolbar"
+import { EmptyState } from "@/components/empty-state"
+import { Pagination } from "@/components/pagination"
+import { CreateGrantDialog } from "@/components/telegram/create-grant-dialog"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { DataTableHead, Table, TableBody, TableCell, TableHeader, TableRow, TableSurface } from "@/components/ui/table"
+import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
+import type { ApiOutput } from "@/lib/api/types"
+import { createAppColumnHelper, type dashboardFeatures, useAppTable } from "@/lib/table"
+
+type GrantRecord = ApiOutput["tg"]["grants"]["getOngoing"]["grants"][number]
+type GrantRow = GrantRecord & { status: "active" | "scheduled" }
+
+type TelegramGrants = {
+ ongoing: ApiOutput["tg"]["grants"]["getOngoing"]
+ scheduled: ApiOutput["tg"]["grants"]["getScheduled"]
+}
+
+const grantColumnHelper = createAppColumnHelper()
+
+function formatDate(value: Date | string) {
+ return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(new Date(value))
+}
+
+export function TelegramGrantsPage({ grants: { ongoing, scheduled } }: { grants: TelegramGrants }) {
+ const [tab, setTab] = useState<"all" | "ongoing" | "scheduled">("all")
+ const ongoingGrants = ongoing.grants.map((record): GrantRow => ({ ...record, status: "active" }))
+ const scheduledGrants = scheduled.grants.map((record): GrantRow => ({ ...record, status: "scheduled" }))
+ const grants = useMemo(
+ () =>
+ tab === "ongoing"
+ ? ongoingGrants
+ : tab === "scheduled"
+ ? scheduledGrants
+ : [...ongoingGrants, ...scheduledGrants],
+ [ongoingGrants, scheduledGrants, tab]
+ )
+ const columns = useMemo(() => {
+ const sortableHeader = (
+ label: string,
+ column: Pick, "getIsSorted" | "getToggleSortingHandler">
+ ) => {
+ const sorted = column.getIsSorted()
+ const Icon = !sorted ? ChevronsUpDown : sorted === "asc" ? ArrowUp : ArrowDown
+ return (
+
+ {label}
+
+
+ )
+ }
+
+ return grantColumnHelper.columns([
+ grantColumnHelper.display({
+ id: "user",
+ header: "User",
+ cell: ({ row }) => {
+ const { user, grant } = row.original
+ const name = user ? [user.firstName, user.lastName].filter(Boolean).join(" ") : `User ${grant.userId}`
+ return (
+
+ {name}
+
+ {user?.username ? `@${user.username}` : `Telegram ID ${grant.userId}`}
+
+
+ )
+ },
+ }),
+ grantColumnHelper.display({
+ id: "reason",
+ header: "Reason",
+ cell: ({ row }) =>
+ row.original.grant.reason || Not provided ,
+ }),
+ grantColumnHelper.display({
+ id: "grantor",
+ header: "Authorized by",
+ cell: ({ row }) => (
+
+ {row.original.grant.grantedBy}
+
+ ),
+ }),
+ grantColumnHelper.accessor((row) => new Date(row.grant.validSince).getTime(), {
+ id: "validSince",
+ header: ({ column }) => sortableHeader("Starts", column),
+ cell: ({ row }) => {formatDate(row.original.grant.validSince)} ,
+ }),
+ grantColumnHelper.accessor((row) => new Date(row.grant.validUntil).getTime(), {
+ id: "validUntil",
+ header: ({ column }) => sortableHeader("Expires", column),
+ cell: ({ row }) => {formatDate(row.original.grant.validUntil)} ,
+ }),
+ grantColumnHelper.accessor("status", {
+ header: ({ column }) => sortableHeader("Status", column),
+ cell: ({ getValue }) => (
+
+ {getValue() === "scheduled" ? "Scheduled" : "Active"}
+
+ ),
+ }),
+ ])
+ }, [])
+ const table = useAppTable({
+ key: "telegram-grants",
+ columns,
+ data: grants,
+ initialState: { sorting: [{ id: "validSince", desc: false }], pagination: { pageIndex: 0, pageSize: 20 } },
+ globalFilterFn: (row, _columnId, value) => {
+ const { grant, user, status } = row.original
+ const query = String(value ?? "")
+ .trim()
+ .toLocaleLowerCase()
+ const searchable = [
+ grant.userId,
+ grant.grantedBy,
+ grant.reason,
+ user?.firstName,
+ user?.lastName,
+ user?.username,
+ formatDate(grant.validSince),
+ formatDate(grant.validUntil),
+ status,
+ ]
+ .filter(Boolean)
+ .join(" ")
+ .toLocaleLowerCase()
+ return !query || searchable.includes(query)
+ },
+ })
+ const filteredCount = table.getFilteredRowModel().rows.length
+ const hasSearch = Boolean(String(table.state.globalFilter ?? "").trim())
+
+ return (
+
+
}
+ onSearch={(value) => {
+ table.setGlobalFilter(value)
+ table.setPageIndex(0)
+ }}
+ />
+
{
+ const next = value[0]
+ if (next === "all" || next === "ongoing" || next === "scheduled") {
+ setTab(next)
+ table.setPageIndex(0)
+ }
+ }}
+ className="mb-4"
+ aria-label="Grant status"
+ >
+
+ All {ongoingGrants.length + scheduledGrants.length}
+
+
+ Ongoing {ongoingGrants.length}
+
+
+ Scheduled {scheduledGrants.length}
+
+
+ {filteredCount ? (
+ <>
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {header.isPlaceholder ? null : }
+
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getAllCells().map((cell) => (
+
+
+
+ ))}
+
+ ))}
+
+
+
+
table.setPageIndex(page - 1)}
+ onPageSizeChange={(pageSize) => table.setPageSize(pageSize)}
+ />
+ >
+ ) : (
+
+ )}
+
+ )
+}
diff --git a/src/features/telegram/grants.functions.ts b/src/features/telegram/grants.functions.ts
new file mode 100644
index 0000000..8dff694
--- /dev/null
+++ b/src/features/telegram/grants.functions.ts
@@ -0,0 +1,44 @@
+import { createServerFn } from "@tanstack/react-start"
+import { z } from "zod"
+import { adminMiddleware } from "@/server/auth.middleware"
+
+export const getTelegramGrants = createServerFn()
+ .middleware([adminMiddleware])
+ .handler(async ({ context }) => {
+ const [ongoing, scheduled] = await Promise.all([
+ context.backend.tg.grants.getOngoing.query(),
+ context.backend.tg.grants.getScheduled.query(),
+ ])
+ return { ongoing, scheduled }
+ })
+
+const grantInput = z
+ .object({
+ userId: z.number().int().positive(),
+ since: z.date(),
+ until: z.date(),
+ reason: z.string().trim().max(500).optional(),
+ })
+ .refine(({ since, until }) => until > since, { message: "The grant end must be after its start.", path: ["until"] })
+
+export const createTelegramGrant = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(grantInput)
+ .handler(({ data, context }) =>
+ context.backend.tg.grants.create.mutate({
+ ...data,
+ adderId: context.telegramId,
+ sendTgLog: true,
+ })
+ )
+
+export const interruptTelegramGrant = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(z.object({ userId: z.number().int().positive() }))
+ .handler(({ data, context }) =>
+ context.backend.tg.grants.interrupt.mutate({
+ userId: data.userId,
+ interruptedById: context.telegramId,
+ sendTgLog: true,
+ })
+ )
diff --git a/src/features/telegram/groups-page.tsx b/src/features/telegram/groups-page.tsx
new file mode 100644
index 0000000..32a99e4
--- /dev/null
+++ b/src/features/telegram/groups-page.tsx
@@ -0,0 +1,275 @@
+import { useRouter } from "@tanstack/react-router"
+import { useServerFn } from "@tanstack/react-start"
+import type { Column } from "@tanstack/react-table"
+import {
+ ArrowDown,
+ ArrowUp,
+ ChevronsUpDown,
+ ExternalLink,
+ Eye,
+ EyeOff,
+ LoaderCircle,
+ MessageCircleMore,
+} from "lucide-react"
+import { useMemo, useState } from "react"
+import { toast } from "sonner"
+import { DataToolbar } from "@/components/data-toolbar"
+import { EmptyState } from "@/components/empty-state"
+import { Pagination } from "@/components/pagination"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { DataTableHead, Table, TableBody, TableCell, TableHeader, TableRow, TableSurface } from "@/components/ui/table"
+import { setGroupVisibility } from "@/features/telegram/groups.functions"
+import { LeaveGroupDialog } from "@/features/telegram/leave-group-dialog"
+import type { TgGroup } from "@/lib/api/types"
+import { createAppColumnHelper, type dashboardFeatures, useAppTable } from "@/lib/table"
+import { cn } from "@/lib/utils"
+
+const groupColumnHelper = createAppColumnHelper()
+
+export function TelegramGroupsPage({ loadedGroups }: { loadedGroups: TgGroup[] }) {
+ const router = useRouter()
+ const setGroupVisibilityFn = useServerFn(setGroupVisibility)
+ const [visibilityOverrides, setVisibilityOverrides] = useState>({})
+ const [updatingId, setUpdatingId] = useState(null)
+ const [mutationError, setMutationError] = useState("")
+ const [refreshError, setRefreshError] = useState("")
+
+ const groups = loadedGroups.map((group) => ({
+ ...group,
+ hide: visibilityOverrides[group.telegramId] ?? group.hide,
+ }))
+
+ async function toggleVisibility(group: TgGroup) {
+ if (updatingId !== null) return
+ const hide = !group.hide
+ setUpdatingId(group.telegramId)
+ setMutationError("")
+ setVisibilityOverrides((current) => ({ ...current, [group.telegramId]: hide }))
+
+ try {
+ await setGroupVisibilityFn({ data: { telegramId: group.telegramId, hide } })
+ toast.success(`${group.title} is now ${hide ? "hidden" : "visible"}.`)
+ try {
+ await router.invalidate({ sync: true })
+ setRefreshError("")
+ setVisibilityOverrides((current) => {
+ const { [group.telegramId]: _removed, ...remaining } = current
+ return remaining
+ })
+ } catch {
+ setRefreshError("The visibility was updated, but the latest group data could not be refreshed.")
+ }
+ } catch {
+ setVisibilityOverrides((current) => {
+ const { [group.telegramId]: _removed, ...remaining } = current
+ return remaining
+ })
+ setMutationError("The visibility setting could not be updated. Check your permissions and try again.")
+ } finally {
+ setUpdatingId(null)
+ }
+ }
+
+ const columns = useMemo(() => {
+ const sortableHeader = (
+ label: string,
+ column: Pick, "getIsSorted" | "getToggleSortingHandler">
+ ) => {
+ const sorted = column.getIsSorted()
+ const Icon = !sorted ? ChevronsUpDown : sorted === "asc" ? ArrowUp : ArrowDown
+ return (
+
+ {label}
+
+
+ )
+ }
+
+ return groupColumnHelper.columns([
+ groupColumnHelper.accessor("title", {
+ header: ({ column }) => sortableHeader("Group", column),
+ cell: ({ row }) => (
+
+
+
+
+ {row.original.title}
+
+ ),
+ }),
+ groupColumnHelper.accessor("telegramId", {
+ header: ({ column }) => sortableHeader("Telegram ID", column),
+ cell: ({ getValue }) => {getValue()} ,
+ }),
+ groupColumnHelper.accessor("tag", {
+ header: ({ column }) => sortableHeader("Tag", column),
+ cell: ({ getValue }) =>
+ getValue() ? (
+
+ @{getValue()}
+
+ ) : (
+ Not set
+ ),
+ }),
+ groupColumnHelper.display({
+ id: "visibility",
+ header: "Visibility",
+ cell: ({ row }) => {
+ const group = row.original
+ const pending = updatingId === group.telegramId
+ const visible = !group.hide
+ return (
+ void toggleVisibility(group)}
+ >
+ {pending ? : visible ? : }
+ {visible ? "Visible" : "Hidden"}
+
+ )
+ },
+ }),
+ groupColumnHelper.display({
+ id: "invite",
+ header: "Invite",
+ cell: ({ row }) => {
+ const link = row.original.link
+ return link ? (
+
+
+ Open invite link
+
+ ) : (
+ Not shared
+ )
+ },
+ }),
+ groupColumnHelper.display({
+ id: "leave",
+ header: "",
+ cell: ({ row }) => ,
+ }),
+ ])
+ }, [updatingId])
+ const table = useAppTable({
+ key: "telegram-groups",
+ columns,
+ data: groups,
+ initialState: { sorting: [{ id: "title", desc: false }], pagination: { pageIndex: 0, pageSize: 20 } },
+ globalFilterFn: (row, _columnId, value) => {
+ const group = row.original
+ const query = String(value ?? "")
+ .trim()
+ .toLocaleLowerCase()
+ .replace(/^@/, "")
+ return !query || [group.title, group.tag].filter(Boolean).join(" ").toLocaleLowerCase().includes(query)
+ },
+ })
+ const filteredCount = table.getFilteredRowModel().rows.length
+ const hasSearch = Boolean(String(table.state.globalFilter ?? "").trim())
+
+ return (
+
+
{
+ table.setGlobalFilter(value)
+ table.setPageIndex(0)
+ }}
+ />
+ {mutationError && (
+
+ {mutationError}
+
+ )}
+ {refreshError && (
+
+ {refreshError}
+
+ )}
+ {filteredCount ? (
+ <>
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {header.isPlaceholder ? null : }
+
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getAllCells().map((cell) => (
+
+
+
+ ))}
+
+ ))}
+
+
+
+ table.setPageIndex(page - 1)}
+ onPageSizeChange={(pageSize) => table.setPageSize(pageSize)}
+ />
+ >
+ ) : (
+
+ )}
+
+ )
+}
diff --git a/src/features/telegram/groups.functions.ts b/src/features/telegram/groups.functions.ts
new file mode 100644
index 0000000..d5630bc
--- /dev/null
+++ b/src/features/telegram/groups.functions.ts
@@ -0,0 +1,23 @@
+import { createServerFn } from "@tanstack/react-start"
+import { z } from "zod"
+import { adminMiddleware } from "@/server/auth.middleware"
+
+export const getTelegramGroups = createServerFn()
+ .middleware([adminMiddleware])
+ .handler(({ context }) => context.backend.tg.groups.getAll.query())
+
+export const setGroupVisibility = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(z.object({ telegramId: z.number().int(), hide: z.boolean() }))
+ .handler(async ({ data, context }) => {
+ const updated = await context.backend.tg.groups.setHide.mutate(data)
+ if (!updated) throw new Error("GROUP_VISIBILITY_NOT_UPDATED")
+ return { updated: true as const }
+ })
+
+export const leaveTelegramGroup = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(z.object({ chatId: z.number().int() }))
+ .handler(({ data, context }) =>
+ context.backend.tg.groups.leaveChat.mutate({ chatId: data.chatId, performerId: context.telegramId })
+ )
diff --git a/src/features/telegram/leave-group-dialog.tsx b/src/features/telegram/leave-group-dialog.tsx
new file mode 100644
index 0000000..855fb25
--- /dev/null
+++ b/src/features/telegram/leave-group-dialog.tsx
@@ -0,0 +1,74 @@
+import { useRouter } from "@tanstack/react-router"
+import { useServerFn } from "@tanstack/react-start"
+import { LoaderCircle, Trash2 } from "lucide-react"
+import { useState } from "react"
+import { toast } from "sonner"
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/components/ui/alert-dialog"
+import { Button } from "@/components/ui/button"
+import { leaveTelegramGroup } from "@/features/telegram/groups.functions"
+
+export function LeaveGroupDialog({ chatId, title }: { chatId: number; title: string }) {
+ const router = useRouter()
+ const leaveTelegramGroupFn = useServerFn(leaveTelegramGroup)
+ const [open, setOpen] = useState(false)
+ const [pending, setPending] = useState(false)
+
+ async function leaveGroup() {
+ setPending(true)
+ try {
+ const result = await leaveTelegramGroupFn({ data: { chatId } })
+ if (result.error === "BOT_ERROR") throw new Error("The Telegram bot could not leave this group.")
+ if (result.error && result.error !== "NOT_FOUND") {
+ throw new Error(
+ result.error === "UNAUTHORIZED"
+ ? "You do not have permission to leave this group."
+ : "The group could not be left."
+ )
+ }
+ if (result.error === "NOT_FOUND") {
+ toast.warning("The bot left the group, but its database record was already missing.")
+ } else {
+ toast.success(`Left ${title}.`)
+ }
+ setOpen(false)
+ await router.invalidate({ sync: true })
+ } catch (error) {
+ toast.error(error instanceof Error ? error.message : "The group could not be left.")
+ } finally {
+ setPending(false)
+ }
+ }
+
+ return (
+ !pending && setOpen(nextOpen)}>
+ }>
+ Leave
+
+
+
+ Leave {title}?
+
+ The bot will leave this Telegram group and the backend will delete its group record. This action affects one
+ group and cannot be undone here.
+
+
+
+ Cancel
+ void leaveGroup()}>
+ {pending && } Confirm leave
+
+
+
+
+ )
+}
diff --git a/src/features/telegram/user-detail/grant-dialogs.tsx b/src/features/telegram/user-detail/grant-dialogs.tsx
new file mode 100644
index 0000000..0ed8897
--- /dev/null
+++ b/src/features/telegram/user-detail/grant-dialogs.tsx
@@ -0,0 +1,83 @@
+import { useRouter } from "@tanstack/react-router"
+import { useServerFn } from "@tanstack/react-start"
+import { LoaderCircle, ShieldX } from "lucide-react"
+import { useState } from "react"
+import { toast } from "sonner"
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogMedia,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/components/ui/alert-dialog"
+import { Button } from "@/components/ui/button"
+import { interruptTelegramGrant } from "@/features/telegram/grants.functions"
+
+export function InterruptGrantDialog({ userId, displayName }: { userId: number; displayName: string }) {
+ const [open, setOpen] = useState(false)
+ const [pending, setPending] = useState(false)
+ const router = useRouter()
+ const interruptGrant = useServerFn(interruptTelegramGrant)
+
+ async function interrupt() {
+ if (pending) return
+ setPending(true)
+ try {
+ const result = await interruptGrant({ data: { userId } })
+ if (result.error) {
+ toast.error(grantMutationError(result.error))
+ return
+ }
+
+ toast.success(`Grant interrupted for ${displayName}.`)
+ setOpen(false)
+ try {
+ await router.invalidate({ sync: true })
+ } catch {
+ toast.warning("The grant was ended, but the latest user data could not be refreshed.")
+ }
+ } catch (error) {
+ console.error(error)
+ toast.error("The grant could not be ended. Check your permissions and try again.")
+ } finally {
+ setPending(false)
+ }
+ }
+
+ return (
+ !pending && setOpen(nextOpen)}>
+ }>
+ End grant
+
+
+
+
+
+
+ End grant?
+
+ {displayName} will immediately return to the normal automatic moderation rules.
+
+
+
+ Cancel
+ void interrupt()}>
+ {pending && }
+ End grant
+
+
+
+
+ )
+}
+
+function grantMutationError(error: string) {
+ if (error === "NOT_FOUND") return "This grant has already expired or been removed."
+ if (error === "UNAUTHORIZED") return "You do not have permission to manage grants."
+ return "The grant update could not be completed."
+}
diff --git a/src/features/telegram/user-detail/group-admin-dialog.tsx b/src/features/telegram/user-detail/group-admin-dialog.tsx
new file mode 100644
index 0000000..31ed099
--- /dev/null
+++ b/src/features/telegram/user-detail/group-admin-dialog.tsx
@@ -0,0 +1,206 @@
+import { useServerFn } from "@tanstack/react-start"
+import { LoaderCircle } from "lucide-react"
+import { useEffect, useState } from "react"
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/components/ui/alert-dialog"
+import { Button } from "@/components/ui/button"
+import {
+ Combobox,
+ ComboboxContent,
+ ComboboxEmpty,
+ ComboboxInput,
+ ComboboxItem,
+ ComboboxList,
+} from "@/components/ui/combobox"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { Field, FieldLabel } from "@/components/ui/field"
+import { addTelegramGroupAdmin, removeTelegramGroupAdmin } from "@/features/telegram/users.functions"
+import type { TelegramUserDetail } from "./types"
+
+export function AddGroupAdminDialog({
+ open,
+ userId,
+ groups,
+ administeredGroupIds,
+ onClose,
+ onSaved,
+}: {
+ open: boolean
+ userId: number
+ groups: TelegramUserDetail["groups"]
+ administeredGroupIds: Set
+ onClose: () => void
+ onSaved: () => Promise
+}) {
+ const [groupId, setGroupId] = useState("")
+ const [pending, setPending] = useState(false)
+ const [error, setError] = useState("")
+ const addGroupAdmin = useServerFn(addTelegramGroupAdmin)
+ const availableGroups = groups.filter((group) => !administeredGroupIds.has(group.telegramId))
+
+ useEffect(() => {
+ if (open) {
+ setGroupId("")
+ setError("")
+ }
+ }, [open])
+
+ async function submit(event: React.FormEvent) {
+ event.preventDefault()
+ if (!groupId) return
+ setPending(true)
+ setError("")
+ try {
+ await addGroupAdmin({ data: { userId, groupId: Number(groupId) } })
+ await onSaved()
+ } catch {
+ setError("The user could not be added as a group administrator.")
+ setPending(false)
+ }
+ }
+
+ return (
+ !nextOpen && onClose()}>
+
+
+
+ GROUP ADMINISTRATION
+
+ Add group administrator
+
+ Choose a group this user should administer.
+
+
+ void submit(event)}>
+
+
+ Group
+
+ String(group.telegramId) === groupId) ?? null}
+ onValueChange={(group) => setGroupId(group ? String(group.telegramId) : "")}
+ itemToStringLabel={(group) => group.title}
+ itemToStringValue={(group) => String(group.telegramId)}
+ disabled={!availableGroups.length}
+ >
+
+
+ No matching groups
+
+ {(group) => (
+
+ {group.title}
+
+ )}
+
+
+
+
+ {!availableGroups.length && (
+
+ This user already administers every available group.
+
+ )}
+ {error && {error}
}
+
+
+ Cancel
+
+
+ {pending && }
+ Add administrator
+
+
+
+
+
+ )
+}
+
+export function RemoveGroupAdminDialog({
+ userId,
+ groupId,
+ groupTitle,
+ onSaved,
+}: {
+ userId: number
+ groupId: number
+ groupTitle: string
+ onSaved: () => Promise
+}) {
+ const [open, setOpen] = useState(false)
+ const [pending, setPending] = useState(false)
+ const [error, setError] = useState("")
+ const removeGroupAdmin = useServerFn(removeTelegramGroupAdmin)
+
+ async function remove() {
+ setPending(true)
+ setError("")
+ try {
+ const result = await removeGroupAdmin({ data: { userId, groupId } })
+ if (result.error) {
+ setError(groupAdminMutationError(result.error))
+ return
+ }
+ setOpen(false)
+ await onSaved()
+ } catch {
+ setError("The group administrator assignment could not be removed.")
+ } finally {
+ setPending(false)
+ }
+ }
+
+ return (
+ {
+ if (pending) return
+ setOpen(nextOpen)
+ if (!nextOpen) setError("")
+ }}
+ >
+ }>Remove
+
+
+ Remove group administrator?
+
+ This user will no longer administer {groupTitle}. Their other group assignments will not change.
+
+
+ {error && {error}
}
+
+ Cancel
+ void remove()}>
+ {pending && }
+ Remove assignment
+
+
+
+
+ )
+}
+
+function groupAdminMutationError(error: string) {
+ if (error === "NOT_FOUND") return "This assignment has already been removed."
+ if (error === "UNAUTHORIZED_SELF_ASSIGN") return "You cannot remove this assignment from yourself."
+ if (error === "UNAUTHORIZED") return "You do not have permission to remove this assignment."
+ return "The group administrator assignment could not be removed."
+}
diff --git a/src/features/telegram/user-detail/profile.tsx b/src/features/telegram/user-detail/profile.tsx
new file mode 100644
index 0000000..db2a296
--- /dev/null
+++ b/src/features/telegram/user-detail/profile.tsx
@@ -0,0 +1,283 @@
+import { useRouter } from "@tanstack/react-router"
+import {
+ CalendarClock,
+ ExternalLink,
+ History,
+ MessageCircle,
+ ShieldCheck,
+ UserPlus,
+ UserRound,
+ UsersRound,
+} from "lucide-react"
+import { useState } from "react"
+import { toast } from "sonner"
+import { CreateGrantDialog } from "@/components/telegram/create-grant-dialog"
+import { Avatar, AvatarFallback } from "@/components/ui/avatar"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader } from "@/components/ui/card"
+import { InterruptGrantDialog } from "./grant-dialogs"
+import { AddGroupAdminDialog, RemoveGroupAdminDialog } from "./group-admin-dialog"
+import { RoleDialog } from "./role-dialog"
+import { Definition, DetailSection, SectionEmpty, SummaryCard } from "./sections"
+import type { TelegramUserDetail } from "./types"
+
+function formatDate(value: Date | string | null | undefined) {
+ if (!value) return "—"
+ return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(new Date(value))
+}
+
+function messageLink(chatId: number, messageId: number) {
+ return `https://t.me/c/${String(chatId).replace("-100", "")}/${messageId}`
+}
+
+function groupLink(chatId: number, messageId: number, inviteLink?: string | null) {
+ return inviteLink ?? messageLink(chatId, messageId)
+}
+
+function GrantDetails({
+ label,
+ status,
+ validSince,
+ validUntil,
+}: {
+ label: string
+ status: "Active" | "Scheduled"
+ validSince: Date | string
+ validUntil: Date | string
+}) {
+ return (
+
+
+ {status}
+
+ {formatDate(validSince)}
+ {formatDate(validUntil)}
+
+ )
+}
+
+export function TelegramUserProfile({ data }: { data: TelegramUserDetail }) {
+ const router = useRouter()
+ const [adminDialogOpen, setAdminDialogOpen] = useState(false)
+ const { user, roles, configuredRoles, groupAdmin, groups, messages, audits, ongoingGrant, scheduledGrants } = data
+ const administeredGroups = groupAdmin.filter((entry): entry is NonNullable => entry !== null)
+ const displayName = [user.firstName, user.lastName].filter(Boolean).join(" ") || "Unnamed account"
+
+ return (
+ <>
+
+
+
+
+ {(user.firstName?.[0] ?? user.username?.[0] ?? String(user.id).slice(-2)).toUpperCase()}
+
+
+
+
+ Telegram profile · {user.id}
+
+
{displayName}
+
+ {user.username ? `@${user.username}` : "No Telegram username"}
+
+
+
+ {roles.length} role{roles.length === 1 ? "" : "s"}
+
+
+
+
+
+
+
+
+ {user.id}
+
+ {displayName}
+ {user.username ? `@${user.username}` : "—"}
+
+
+
+
+
+ >
+ }
+ >
+
+ {roles.length ? (
+ roles.map((role) => (
+
+ {role}
+
+ ))
+ ) : (
+ No assigned roles
+ )}
+
+
+
+
+ {ongoingGrant && }
+ >
+ }
+ >
+ {ongoingGrant || scheduledGrants.length ? (
+
+ {ongoingGrant && (
+
+ )}
+ {scheduledGrants.map((scheduledGrant, index) => (
+
+ ))}
+
+ ) : (
+ No active or scheduled grants
+ )}
+
+
+
+ setAdminDialogOpen(true)}>
+ Add group
+
+ }
+ >
+
+ {administeredGroups.map((entry) => (
+
+
+ {entry.group.title}
+ {entry.group.id}
+
+ Added by {entry.addedBy.firstName}
+ {entry.addedBy.username ? ` · @${entry.addedBy.username}` : ""}
+
+
+ {
+ toast.success("Group administrator removed.")
+ try {
+ await router.invalidate({ sync: true })
+ } catch {
+ toast.warning("The assignment was removed, but the latest user data could not be refreshed.")
+ }
+ }}
+ />
+
+
+
+ ))}
+ {!administeredGroups.length &&
}
+
+
+ entry.group.id))}
+ onClose={() => setAdminDialogOpen(false)}
+ onSaved={async () => {
+ setAdminDialogOpen(false)
+ toast.success("Group administrator added.")
+ try {
+ await router.invalidate({ sync: true })
+ } catch {
+ toast.warning("The administrator was added, but the latest user data could not be refreshed.")
+ }
+ }}
+ />
+
+
+ {messages.map((message) => (
+
+
+
+
+
+ {formatDate(message.timestamp)}
+
+
+
+
+ {message.message}
+
+
+
+
+
+ ))}
+ {!messages.length &&
}
+
+
+
+
+ {audits.map((audit) => (
+
+
+
+
{audit.type}
+ {formatDate(audit.createdAt)}
+
+ {audit.reason ?? "No reason provided"}
+ {audit.groupTitle && (
+
+ {audit.groupTitle} · {audit.groupId}
+
+ )}
+
+
+ ))}
+ {!audits.length &&
}
+
+
+ >
+ )
+}
diff --git a/src/features/telegram/user-detail/role-dialog.tsx b/src/features/telegram/user-detail/role-dialog.tsx
new file mode 100644
index 0000000..7f0fbc8
--- /dev/null
+++ b/src/features/telegram/user-detail/role-dialog.tsx
@@ -0,0 +1,178 @@
+import { useRouter } from "@tanstack/react-router"
+import { useServerFn } from "@tanstack/react-start"
+import { LoaderCircle, Minus, Plus } from "lucide-react"
+import { useState } from "react"
+import { toast } from "sonner"
+import { Button } from "@/components/ui/button"
+import {
+ Combobox,
+ ComboboxContent,
+ ComboboxEmpty,
+ ComboboxInput,
+ ComboboxItem,
+ ComboboxList,
+} from "@/components/ui/combobox"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog"
+import { Field, FieldLabel } from "@/components/ui/field"
+import { addTelegramUserRole, removeTelegramUserRole } from "@/features/telegram/users.functions"
+import type { TgUserRole } from "@/lib/api/types"
+
+function roleLabel(role: TgUserRole) {
+ return role
+ .split("_")
+ .map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
+ .join(" ")
+}
+
+export function RoleDialog({
+ mode,
+ userId,
+ roles,
+ configuredRoles,
+}: {
+ mode: "add" | "remove"
+ userId: number
+ roles: TgUserRole[]
+ configuredRoles: TgUserRole[]
+}) {
+ const [open, setOpen] = useState(false)
+ const [selectedRole, setSelectedRole] = useState(null)
+ const [pending, setPending] = useState(false)
+ const router = useRouter()
+ const addUserRole = useServerFn(addTelegramUserRole)
+ const removeUserRole = useServerFn(removeTelegramUserRole)
+ const adding = mode === "add"
+ const choices = (adding ? configuredRoles.filter((role) => !roles.includes(role)) : roles).map((role) => ({
+ value: role,
+ label: roleLabel(role),
+ }))
+ const selectedChoice = choices.find((choice) => choice.value === selectedRole) ?? null
+ const Icon = adding ? Plus : Minus
+
+ function handleOpenChange(nextOpen: boolean) {
+ if (pending) return
+ setOpen(nextOpen)
+ if (!nextOpen) setSelectedRole(null)
+ }
+
+ async function submit(event: React.FormEvent) {
+ event.preventDefault()
+ if (!selectedRole || pending) return
+ setPending(true)
+
+ try {
+ const action = adding ? addUserRole : removeUserRole
+ const result = await action({ data: { userId, role: selectedRole } })
+ if (result.error) {
+ toast.error(roleMutationError(result.error, adding))
+ return
+ }
+
+ toast.success(`${roleLabel(selectedRole)} role ${adding ? "assigned" : "removed"}.`)
+ setOpen(false)
+ setSelectedRole(null)
+ try {
+ await router.invalidate({ sync: true })
+ } catch {
+ toast.warning("The role was updated, but the latest user data could not be refreshed.")
+ }
+ } catch (error) {
+ console.error(error)
+ toast.error(`The role could not be ${adding ? "assigned" : "removed"}. Check your permissions and try again.`)
+ } finally {
+ setPending(false)
+ }
+ }
+
+ const actionLabel = adding ? "Assign role" : "Remove role"
+
+ return (
+
+
+ }
+ >
+ {actionLabel}
+
+
+
+ USER ROLES
+ {actionLabel}
+
+ {adding
+ ? "Choose one of the roles currently configured for Telegram users."
+ : "Choose an assigned role to remove from this user."}
+
+
+ void submit(event)}>
+
+
+ Role
+
+ setSelectedRole(choice?.value ?? null)}
+ itemToStringLabel={(choice) => choice.label}
+ itemToStringValue={(choice) => choice.value}
+ >
+
+
+ {adding ? "No available roles" : "No assigned roles"}
+
+ {(choice) => (
+
+ {choice.label}
+
+ )}
+
+
+
+
+
+ handleOpenChange(false)}>
+ Cancel
+
+
+ {pending && }
+ {actionLabel}
+
+
+
+
+
+ )
+}
+
+function roleMutationError(error: string, adding: boolean) {
+ if (error === "UNAUTHORIZED_SELF_ASSIGN") return `You cannot ${adding ? "assign" : "remove"} this role on yourself.`
+ if (error === "NOT_FOUND") return "That role is no longer assigned to this user."
+ if (error === "UNAUTHORIZED") return "You do not have permission to manage this role."
+ return "The role update could not be completed."
+}
diff --git a/src/features/telegram/user-detail/sections.tsx b/src/features/telegram/user-detail/sections.tsx
new file mode 100644
index 0000000..b090c2f
--- /dev/null
+++ b/src/features/telegram/user-detail/sections.tsx
@@ -0,0 +1,88 @@
+import { Link } from "@tanstack/react-router"
+import { ArrowLeft, type LucideIcon } from "lucide-react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+
+export function UserDetailBackLink() {
+ return (
+
+ Back to users
+
+ )
+}
+
+export function SummaryCard({
+ icon: Icon,
+ label,
+ actions,
+ children,
+}: {
+ icon: LucideIcon
+ label: string
+ actions?: React.ReactNode
+ children: React.ReactNode
+}) {
+ return (
+
+
+
+
+ {label}
+
+
+
+ {children}
+ {actions && {actions}
}
+
+
+ )
+}
+
+export function Definition({ label, children }: { label: string; children: React.ReactNode }) {
+ return (
+
+
{label}
+ {children}
+
+ )
+}
+
+export function SectionEmpty({ text }: { text: string }) {
+ return (
+
+ {text}
+
+ )
+}
+
+export function DetailSection({
+ icon: Icon,
+ title,
+ count,
+ action,
+ children,
+}: {
+ icon: LucideIcon
+ title: string
+ count: number
+ action?: React.ReactNode
+ children: React.ReactNode
+}) {
+ return (
+
+
+
+
+ {title}
+
+
+ {action}
+ {count}
+
+
+ {children}
+
+ )
+}
diff --git a/src/features/telegram/user-detail/types.ts b/src/features/telegram/user-detail/types.ts
new file mode 100644
index 0000000..b3e4924
--- /dev/null
+++ b/src/features/telegram/user-detail/types.ts
@@ -0,0 +1,13 @@
+import type { ApiOutput, TgGrant, TgUser, TgUserRole } from "@/lib/api/types"
+
+export type TelegramUserDetail = {
+ user: TgUser
+ roles: NonNullable
+ configuredRoles: TgUserRole[]
+ groupAdmin: ApiOutput["tg"]["permissions"]["getRoles"]["groupAdmin"]
+ groups: ApiOutput["tg"]["groups"]["getAll"]
+ messages: NonNullable
+ audits: ApiOutput["tg"]["auditLog"]["getById"]
+ ongoingGrant: TgGrant | null
+ scheduledGrants: TgGrant[]
+}
diff --git a/src/features/telegram/users-page.tsx b/src/features/telegram/users-page.tsx
new file mode 100644
index 0000000..3405d7d
--- /dev/null
+++ b/src/features/telegram/users-page.tsx
@@ -0,0 +1,166 @@
+import { useNavigate } from "@tanstack/react-router"
+import { ArrowRight, MessageCircle } from "lucide-react"
+import { useMemo } from "react"
+import { DataToolbar } from "@/components/data-toolbar"
+import { EmptyState } from "@/components/empty-state"
+import { Pagination } from "@/components/pagination"
+import { Avatar, AvatarFallback } from "@/components/ui/avatar"
+import { DataTableHead, Table, TableBody, TableCell, TableHeader, TableRow, TableSurface } from "@/components/ui/table"
+import type { TgUser } from "@/lib/api/types"
+import { createAppColumnHelper, useAppTable } from "@/lib/table"
+
+const userColumnHelper = createAppColumnHelper()
+
+export function TelegramUsersPage({ users }: { users: TgUser[] }) {
+ const navigate = useNavigate()
+
+ const columns = useMemo(
+ () =>
+ userColumnHelper.columns([
+ userColumnHelper.display({
+ id: "name",
+ header: "Name",
+ cell: ({ row }) => {
+ const { firstName, lastName } = row.original
+ const name = [firstName, lastName].filter(Boolean).join(" ")
+ return (
+
+
+
+ {[firstName[0], lastName?.[0] ?? ""].join("").trim().toUpperCase()}
+
+
+
{name}
+
+ )
+ },
+ }),
+ userColumnHelper.accessor("id", {
+ header: "Telegram ID",
+ cell: ({ getValue }) => {getValue()} ,
+ }),
+ userColumnHelper.accessor("username", {
+ header: "Username",
+ cell: ({ getValue }) => {
+ const username = getValue()
+ return username ? (
+ @{username}
+ ) : (
+ Not set
+ )
+ },
+ }),
+ userColumnHelper.display({
+ id: "actions",
+ header: "",
+ cell: () => (
+
+
+
+ ),
+ }),
+ ]),
+ []
+ )
+ const table = useAppTable({
+ key: "telegram-users",
+ columns,
+ data: users,
+ initialState: { pagination: { pageIndex: 0, pageSize: 25 } },
+ globalFilterFn: (row, _columnId, value) => {
+ const user = row.original
+ const query = String(value ?? "")
+ .trim()
+ .toLocaleLowerCase()
+ .replace(/^@/, "")
+ const searchable = [user.username, user.firstName, user.lastName].filter(Boolean).join(" ").toLocaleLowerCase()
+ return !query || searchable.includes(query)
+ },
+ })
+ const filteredCount = table.getFilteredRowModel().rows.length
+ const hasSearch = Boolean(String(table.state.globalFilter ?? "").trim())
+
+ return (
+
+
{
+ table.setGlobalFilter(value)
+ table.setPageIndex(0)
+ }}
+ />
+ {filteredCount ? (
+ <>
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {header.isPlaceholder ? null : }
+
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => {
+ const user = row.original
+ const openUser = () =>
+ navigate({
+ to: "/dashboard/telegram/users/$userId",
+ params: { userId: String(user.id) },
+ })
+
+ return (
+ void openUser()}
+ onKeyDown={(event) => {
+ if (event.key === "Enter" || event.key === " ") {
+ event.preventDefault()
+ void openUser()
+ }
+ }}
+ >
+ {row.getAllCells().map((cell) => (
+
+
+
+ ))}
+
+ )
+ })}
+
+
+
+ table.setPageIndex(page - 1)}
+ onPageSizeChange={(pageSize) => table.setPageSize(pageSize)}
+ />
+ >
+ ) : (
+
+ )}
+
+ )
+}
diff --git a/src/features/telegram/users.functions.ts b/src/features/telegram/users.functions.ts
new file mode 100644
index 0000000..6ef2f5e
--- /dev/null
+++ b/src/features/telegram/users.functions.ts
@@ -0,0 +1,112 @@
+import { USER_ROLE } from "@polinetwork/backend"
+import { notFound } from "@tanstack/react-router"
+import { createServerFn } from "@tanstack/react-start"
+import { z } from "zod"
+import type { TgUser, TgUserRole } from "@/lib/api/types"
+import { adminMiddleware } from "@/server/auth.middleware"
+
+export const getTelegramUsers = createServerFn()
+ .middleware([adminMiddleware])
+ .handler(async ({ context }) => {
+ const result = await context.backend.tg.users.getAll.query()
+ if (result.error) throw new Error(result.error)
+ return result.users ?? []
+ })
+
+const telegramUserLookupInput = z.discriminatedUnion("by", [
+ z.object({ by: z.literal("username"), username: z.string().trim().min(1).max(32) }),
+ z.object({ by: z.literal("id"), userId: z.number().int().positive() }),
+])
+
+type TelegramUserLookupResult = {
+ user: TgUser | null
+ status: "found" | "not-found" | "error"
+ message?: string
+}
+
+export const findTelegramUser = createServerFn()
+ .middleware([adminMiddleware])
+ .validator(telegramUserLookupInput)
+ .handler(async ({ data, context }): Promise => {
+ try {
+ const response =
+ data.by === "username"
+ ? await context.backend.tg.users.getByUsername.query({ username: data.username.replace(/^@/, "") })
+ : await context.backend.tg.users.get.query({ userId: data.userId })
+
+ if (response.error === "NOT_FOUND" || !response.user) return { user: null, status: "not-found" }
+ if (response.error) return { user: null, status: "error", message: "Telegram user lookup failed." }
+ return { user: response.user, status: "found" }
+ } catch (error) {
+ console.error(error)
+ return {
+ user: null,
+ status: "error",
+ message: "The PoliNetwork backend is currently unavailable.",
+ }
+ }
+ })
+
+export const getTelegramUserDetails = createServerFn()
+ .middleware([adminMiddleware])
+ .validator(z.object({ userId: z.number().int().positive() }))
+ .handler(async ({ data, context }) => {
+ const { user } = await context.backend.tg.users.get.query({ userId: data.userId })
+ if (!user) throw notFound()
+
+ const [permissions, messages, audits, ongoingGrant, scheduledGrants, groups] = await Promise.all([
+ context.backend.tg.permissions.getRoles.query({ userId: user.id }),
+ context.backend.tg.messages.getLastByUser.query({ userId: user.id, limit: 15 }),
+ context.backend.tg.auditLog.getById.query({ targetId: user.id }),
+ context.backend.tg.grants.checkUser.query({ userId: user.id }),
+ context.backend.tg.grants.getScheduled.query(),
+ context.backend.tg.groups.getAll.query(),
+ ])
+ const userScheduledGrants = scheduledGrants.grants
+ .filter((record) => record.grant.userId === user.id)
+ .map((record) => record.grant)
+ .sort((left, right) => new Date(left.validSince).getTime() - new Date(right.validSince).getTime())
+
+ return {
+ user,
+ roles: permissions.roles ?? [],
+ configuredRoles: Object.values(USER_ROLE),
+ groupAdmin: permissions.groupAdmin.filter((group) => group !== null),
+ groups,
+ messages: messages.messages ?? [],
+ audits,
+ ongoingGrant: ongoingGrant.grant ?? null,
+ scheduledGrants: userScheduledGrants,
+ }
+ })
+
+export const addTelegramGroupAdmin = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(z.object({ userId: z.number().int().positive(), groupId: z.number().int() }))
+ .handler(({ data, context }) =>
+ context.backend.tg.permissions.addGroup.mutate({ ...data, adderId: context.telegramId })
+ )
+
+export const removeTelegramGroupAdmin = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(z.object({ userId: z.number().int().positive(), groupId: z.number().int() }))
+ .handler(({ data, context }) =>
+ context.backend.tg.permissions.removeGroup.mutate({ ...data, removerId: context.telegramId })
+ )
+
+const telegramRoleValues = Object.values(USER_ROLE) as [TgUserRole, ...TgUserRole[]]
+const telegramRoleInput = z.object({ userId: z.number().int().positive(), role: z.enum(telegramRoleValues) })
+
+export const addTelegramUserRole = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(telegramRoleInput)
+ .handler(({ data, context }) =>
+ context.backend.tg.permissions.addRole.mutate({ ...data, adderId: context.telegramId })
+ )
+
+export const removeTelegramUserRole = createServerFn({ method: "POST" })
+ .middleware([adminMiddleware])
+ .validator(telegramRoleInput)
+ .handler(({ data, context }) =>
+ context.backend.tg.permissions.removeRole.mutate({ ...data, removerId: context.telegramId })
+ )
diff --git a/src/hooks/use-cookie-storage.tsx b/src/hooks/use-cookie-storage.tsx
deleted file mode 100644
index a6339cc..0000000
--- a/src/hooks/use-cookie-storage.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { type Dispatch, type SetStateAction, useCallback, useMemo, useState } from "react"
-import { type CookieOptions, deleteCookie, getCookie, getDefaultCookieOptions, setCookie } from "@/utils/cookies"
-
-export function useCookieStorage(
- key: string,
- initialValue: T,
- options: CookieOptions = {}
-): [T, Dispatch>] {
- const envOptions = useMemo(() => getDefaultCookieOptions(), [])
- const mergedOptions = useMemo(() => ({ ...envOptions, ...options }), [options, envOptions])
-
- const readValue = useCallback((): T => {
- if (typeof document === "undefined") {
- return initialValue
- }
-
- try {
- const item = getCookie(key)
- return item ? (JSON.parse(item) as T) : initialValue
- } catch (error) {
- console.warn(`Error reading cookie "${key}":`, error)
- return initialValue
- }
- }, [key, initialValue])
-
- const [storedValue, setStoredValue] = useState(readValue)
-
- const setValue: Dispatch> = useCallback(
- (value) => {
- try {
- const valueToStore = value instanceof Function ? value(storedValue) : value
- setStoredValue(valueToStore)
-
- if (typeof document !== "undefined") {
- if (valueToStore === null || valueToStore === undefined) {
- deleteCookie(key, { path: mergedOptions.path, domain: mergedOptions.domain })
- } else {
- setCookie(key, JSON.stringify(valueToStore), mergedOptions)
- }
- }
- } catch (error) {
- console.warn(`Error setting cookie "${key}":`, error)
- }
- },
- [key, storedValue, mergedOptions]
- )
-
- return [storedValue, setValue]
-}
diff --git a/src/hooks/use-local-storage.tsx b/src/hooks/use-local-storage.tsx
deleted file mode 100644
index 7c4209e..0000000
--- a/src/hooks/use-local-storage.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { useCallback, useEffect, useState } from "react"
-
-type Return = {
- data: T | null | undefined
- update: (newData: T) => void
- remove: () => void
-}
-
-export function useLocalStorage(key: string): Return {
- const [data, setData] = useState()
-
- useEffect(() => {
- const saved = window.localStorage.getItem(key)
- setData(saved ? (JSON.parse(saved) as T) : null)
- }, [key])
-
- const update = useCallback(
- (newData: T) => {
- setData(newData)
- localStorage.setItem(key, JSON.stringify(newData))
- },
- [key]
- )
-
- const remove = useCallback(() => {
- setData(null)
- localStorage.removeItem(key)
- }, [key])
-
- return { data, update, remove }
-}
diff --git a/src/hooks/use-mobile.tsx b/src/hooks/use-mobile.tsx
deleted file mode 100644
index 2b0fe1d..0000000
--- a/src/hooks/use-mobile.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import * as React from "react"
-
-const MOBILE_BREAKPOINT = 768
-
-export function useIsMobile() {
- const [isMobile, setIsMobile] = React.useState(undefined)
-
- React.useEffect(() => {
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
- const onChange = () => {
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- }
- mql.addEventListener("change", onChange)
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- return () => mql.removeEventListener("change", onChange)
- }, [])
-
- return !!isMobile
-}
diff --git a/src/hooks/use-session-storage.tsx b/src/hooks/use-session-storage.tsx
deleted file mode 100644
index b1de618..0000000
--- a/src/hooks/use-session-storage.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { type Dispatch, type SetStateAction, useCallback, useState } from "react"
-
-export function useSessionStorage(key: string, initialValue: T): [T, Dispatch>] {
- // Read from sessionStorage on initialization
- const readValue = useCallback((): T => {
- if (typeof window === "undefined") {
- return initialValue
- }
-
- try {
- const item = window.sessionStorage.getItem(key)
- return item ? (JSON.parse(item) as T) : initialValue
- } catch (error) {
- console.warn(`Error reading sessionStorage key "${key}":`, error)
- return initialValue
- }
- }, [key, initialValue])
-
- const [storedValue, setStoredValue] = useState(readValue)
-
- // Return a memoized setter function
- const setValue: Dispatch> = useCallback(
- (value) => {
- try {
- const valueToStore = value instanceof Function ? value(storedValue) : value
- setStoredValue(valueToStore)
-
- if (typeof window !== "undefined") {
- window.sessionStorage.setItem(key, JSON.stringify(valueToStore))
- }
- } catch (error) {
- console.warn(`Error setting sessionStorage key "${key}":`, error)
- }
- },
- [key, storedValue]
- )
-
- return [storedValue, setValue]
-}
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index 40bce93..0000000
--- a/src/index.css
+++ /dev/null
@@ -1,181 +0,0 @@
-@import "tailwindcss";
-@import "tw-animate-css";
-@import "shadcn/tailwind.css";
-
-@plugin "tailwindcss-animate";
-@plugin "tailwind-scrollbar" {
- preferredstrategy: "pseudoelements";
- nocompatible: true;
-}
-
-@custom-variant dark (&:is(.dark *));
-@layer base {
- * {
- @apply border-border;
- }
- body {
- @apply bg-background text-foreground;
- }
-}
-
-:root {
- --background: oklch(0.99 0 264.54);
- --foreground: oklch(0.31 0.06 255.87);
- --card: oklch(0.99 0 264.54);
- --card-foreground: oklch(0.31 0.06 255.87);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.36 0.08 255.16);
- --primary: oklch(0.4659 0.1551 257.42);
- --primary-foreground: oklch(1 0 0);
- --secondary: oklch(0.88 0.04 259.29);
- --secondary-foreground: oklch(0.35 0.07 255.46);
- --muted: oklch(0.92 0.02 256.73);
- --muted-foreground: oklch(0.6306 0.0324 255.03);
- --placeholder: oklch(0.7374 0.0489 255.03);
- --accent: oklch(0.91 0.03 258.37);
- --accent-foreground: oklch(0.3 0.09 256.65);
- --destructive: oklch(0.51 0.19 27.08);
- --destructive-foreground: oklch(1 0 0);
- --border: oklch(0.89 0.04 254.88);
- --input: oklch(0.88 0.02 254.92);
- --ring: oklch(0.57 0.18 257.25);
- --chart-1: oklch(0.59 0.18 256.99);
- --chart-2: oklch(0.39 0.23 268.12);
- --chart-3: oklch(0.38 0.04 226.15);
- --chart-4: oklch(0.83 0.12 88.35);
- --chart-5: oklch(0.78 0.13 59);
- --sidebar: oklch(0.93 0.02 255.54);
- --sidebar-foreground: oklch(0.4659 0.1551 257.42);
- --sidebar-primary: oklch(0.4 0.03 285.2);
- --sidebar-primary-foreground: oklch(0.97 0.01 278.64);
- --sidebar-accent: oklch(0.95 0.02 255.53);
- --sidebar-accent-foreground: oklch(0.44 0.14 257.11);
- --sidebar-border: oklch(0.93 0 286.32);
- --sidebar-ring: oklch(0.4659 0.1551 257.42);
- --font-sans: Poppins, sans-serif;
- --font-serif: Libre Baskerville, serif;
- --font-mono: Space Mono, monospace;
- --radius: 0.6rem;
- --shadow-2xs: 0 1.5px 10px 0px hsl(213.91 86.47% 26.08% / 0.07);
- --shadow-xs: 0 1.5px 10px 0px hsl(213.91 86.47% 26.08% / 0.07);
- --shadow-sm: 0 1.5px 10px 0px hsl(213.91 86.47% 26.08% / 0.15), 0 1px 2px -1px hsl(213.91 86.47% 26.08% / 0.15);
- --shadow: 0 1.5px 10px 0px hsl(213.91 86.47% 26.08% / 0.15), 0 1px 2px -1px hsl(213.91 86.47% 26.08% / 0.15);
- --shadow-md: 0 1.5px 10px 0px hsl(213.91 86.47% 26.08% / 0.15), 0 2px 4px -1px hsl(213.91 86.47% 26.08% / 0.15);
- --shadow-lg: 0 1.5px 10px 0px hsl(213.91 86.47% 26.08% / 0.15), 0 4px 6px -1px hsl(213.91 86.47% 26.08% / 0.15);
- --shadow-xl: 0 1.5px 10px 0px hsl(213.91 86.47% 26.08% / 0.15), 0 8px 10px -1px hsl(213.91 86.47% 26.08% / 0.15);
- --shadow-2xl: 0 1.5px 10px 0px hsl(213.91 86.47% 26.08% / 0.38);
-}
-
-.dark {
- --background: oklch(0.19 0.03 256.45);
- --foreground: oklch(0.88 0.03 277.2);
- --card: oklch(0.24 0.03 259.05);
- --card-foreground: oklch(0.9 0.03 255.59);
- --popover: oklch(0.26 0.03 258.31);
- --popover-foreground: oklch(0.96 0.01 278.64);
- --primary: oklch(0.4659 0.1551 257.42);
- --primary-foreground: oklch(0.93 0.03 252.46);
- --secondary: oklch(0.34 0.07 255.44);
- --secondary-foreground: oklch(0.85 0.03 275.09);
- --muted: oklch(0.3045 0.04 254.07);
- --muted-foreground: oklch(0.7077 0.04 280.47);
- --placeholder: oklch(0.5623 0.04 280.47);
- --accent: oklch(0.35 0.04 254.63);
- --accent-foreground: oklch(0.96 0.01 292.8);
- --destructive: oklch(0.61 0.19 27.08);
- --destructive-foreground: oklch(1 0 0);
- --border: oklch(0.35 0.04 254.63);
- --input: oklch(0.3961 0.03 259.05);
- --ring: oklch(0.4659 0.1551 257.42);
- --chart-1: oklch(0.49 0.12 267.94);
- --chart-2: oklch(0.56 0.19 306.86);
- --chart-3: oklch(0.72 0.15 58.79);
- --chart-4: oklch(0.66 0.2 335.4);
- --chart-5: oklch(0.76 0.12 214.15);
- --sidebar: oklch(0.24 0.03 259.05);
- --sidebar-foreground: oklch(0.82 0.02 256.74);
- --sidebar-primary: oklch(0.4659 0.1551 257.42);
- --sidebar-primary-foreground: oklch(0.96 0.01 278.64);
- --sidebar-accent: oklch(0.3 0.03 254.37);
- --sidebar-accent-foreground: oklch(0.96 0.01 292.8);
- --sidebar-border: oklch(0.35 0.04 254.63);
- --sidebar-ring: oklch(0.4659 0.1551 257.42);
- --font-sans: Poppins, sans-serif;
- --font-serif: Libre Baskerville, serif;
- --font-mono: Space Mono, monospace;
- --radius: 0.6rem;
- --shadow-2xs: 0 1.5px 10px 0px hsl(0 0% 0% / 0.07);
- --shadow-xs: 0 1.5px 10px 0px hsl(0 0% 0% / 0.07);
- --shadow-sm: 0 1.5px 10px 0px hsl(0 0% 0% / 0.15), 0 1px 2px -1px hsl(0 0% 0% / 0.15);
- --shadow: 0 1.5px 10px 0px hsl(0 0% 0% / 0.15), 0 1px 2px -1px hsl(0 0% 0% / 0.15);
- --shadow-md: 0 1.5px 10px 0px hsl(0 0% 0% / 0.15), 0 2px 4px -1px hsl(0 0% 0% / 0.15);
- --shadow-lg: 0 1.5px 10px 0px hsl(0 0% 0% / 0.15), 0 4px 6px -1px hsl(0 0% 0% / 0.15);
- --shadow-xl: 0 1.5px 10px 0px hsl(0 0% 0% / 0.15), 0 8px 10px -1px hsl(0 0% 0% / 0.15);
- --shadow-2xl: 0 1.5px 10px 0px hsl(0 0% 0% / 0.38);
-}
-@theme inline {
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-card: var(--card);
- --color-card-foreground: var(--card-foreground);
- --color-popover: var(--popover);
- --color-popover-foreground: var(--popover-foreground);
- --color-primary: var(--primary);
- --color-primary-foreground: var(--primary-foreground);
- --color-secondary: var(--secondary);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-muted: var(--muted);
- --color-muted-foreground: var(--muted-foreground);
- --color-placeholder: var(--placeholder);
- --color-accent: var(--accent);
- --color-accent-foreground: var(--accent-foreground);
- --color-destructive: var(--destructive);
- --color-destructive-foreground: var(--destructive-foreground);
- --color-border: var(--border);
- --color-input: var(--input);
- --color-ring: var(--ring);
- --color-chart-1: var(--chart-1);
- --color-chart-2: var(--chart-2);
- --color-chart-3: var(--chart-3);
- --color-chart-4: var(--chart-4);
- --color-chart-5: var(--chart-5);
- --color-sidebar: var(--sidebar);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-ring: var(--sidebar-ring);
-
- --font-sans: var(--font-sans);
- --font-mono: var(--font-mono);
- --font-serif: var(--font-serif);
-
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
-
- --shadow-2xs: var(--shadow-2xs);
- --shadow-xs: var(--shadow-xs);
- --shadow-sm: var(--shadow-sm);
- --shadow: var(--shadow);
- --shadow-md: var(--shadow-md);
- --shadow-lg: var(--shadow-lg);
- --shadow-xl: var(--shadow-xl);
- --shadow-2xl: var(--shadow-2xl);
- --font-heading: var(--font-sans);
- --radius-2xl: calc(var(--radius) * 1.8);
- --radius-3xl: calc(var(--radius) * 2.2);
- --radius-4xl: calc(var(--radius) * 2.6);
-}
-
-@theme {
- --breakpoint-xs: 30rem;
-}
-
-@utility container {
- margin-inline: auto;
- @apply pb-8 flex flex-col items-start justify-start gap-6;
-}
diff --git a/src/server/trpc/types.tsx b/src/lib/api/types.ts
similarity index 88%
rename from src/server/trpc/types.tsx
rename to src/lib/api/types.ts
index a81a172..99a72f5 100644
--- a/src/server/trpc/types.tsx
+++ b/src/lib/api/types.ts
@@ -7,7 +7,7 @@ export type ApiError = inferRouterError
export type TgUser = NonNullable
export type TgGrant = NonNullable
-export type TgGroup = NonNullable
+export type TgGroup = ApiOutput["tg"]["groups"]["getAll"][number]
export type TgUserRole = NonNullable
export type AzureMember = ApiOutput["azure"]["members"]["getAll"][number]
diff --git a/src/lib/auth-plugins.ts b/src/lib/auth-plugins.ts
new file mode 100644
index 0000000..c6a9e77
--- /dev/null
+++ b/src/lib/auth-plugins.ts
@@ -0,0 +1,11 @@
+import { passkeyClient } from "@better-auth/passkey/client"
+import type { TelegramPlugin } from "@polinetwork/backend"
+import type { BetterAuthClientPlugin } from "better-auth"
+import { emailOTPClient } from "better-auth/client/plugins"
+
+const telegramPlugin = () =>
+ ({ id: "telegram", $InferServerPlugin: {} as ReturnType }) satisfies BetterAuthClientPlugin
+
+export function createAuthPlugins() {
+ return [telegramPlugin(), emailOTPClient(), passkeyClient()]
+}
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index f00cd8e..730533b 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -1,22 +1,12 @@
-import { passkeyClient } from "@better-auth/passkey/client"
-import { AUTH_PATH, type TelegramPlugin } from "@polinetwork/backend"
-import type { BetterAuthClientPlugin } from "better-auth"
-import { emailOTPClient } from "better-auth/client/plugins"
-import { nextCookies } from "better-auth/next-js"
+import { AUTH_PATH } from "@polinetwork/backend"
import { createAuthClient } from "better-auth/react"
-import { getBaseUrl } from "./utils"
-
-const telegramPlugin = () => {
- return {
- id: "telegram",
- $InferServerPlugin: {} as ReturnType,
- } satisfies BetterAuthClientPlugin
-}
+import { createAuthPlugins } from "@/lib/auth-plugins"
export const auth = createAuthClient({
- baseURL: getBaseUrl(),
basePath: AUTH_PATH,
- plugins: [telegramPlugin(), emailOTPClient(), nextCookies(), passkeyClient()],
+ plugins: createAuthPlugins(),
})
-export const { signIn, signOut, getSession, useSession } = auth
+export const { signIn, signOut, useSession } = auth
+
+export type AdminSession = NonNullable>["data"]>
diff --git a/src/lib/table.ts b/src/lib/table.ts
new file mode 100644
index 0000000..23cf8ea
--- /dev/null
+++ b/src/lib/table.ts
@@ -0,0 +1,31 @@
+import {
+ columnFilteringFeature,
+ createFilteredRowModel,
+ createPaginatedRowModel,
+ createSortedRowModel,
+ createTableHook,
+ filterFns,
+ globalFilteringFeature,
+ rowPaginationFeature,
+ rowSortingFeature,
+ sortFns,
+ tableFeatures,
+} from "@tanstack/react-table"
+
+export const dashboardFeatures = tableFeatures({
+ columnFilteringFeature,
+ globalFilteringFeature,
+ rowSortingFeature,
+ rowPaginationFeature,
+ filteredRowModel: createFilteredRowModel(),
+ sortedRowModel: createSortedRowModel(),
+ paginatedRowModel: createPaginatedRowModel(),
+ filterFns,
+ sortFns,
+})
+
+// One v9 table factory keeps the feature set and column-helper types consistent
+// across every data table in the dashboard.
+export const { useAppTable, createAppColumnHelper } = createTableHook({
+ features: dashboardFeatures,
+})
diff --git a/src/lib/utils/shadcn.ts b/src/lib/utils.ts
similarity index 100%
rename from src/lib/utils/shadcn.ts
rename to src/lib/utils.ts
diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts
deleted file mode 100644
index 35a3c3e..0000000
--- a/src/lib/utils/index.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export function getBaseUrl() {
- if (typeof window !== "undefined") return window.location.origin
- if (process.env.PUBLIC_URL) return process.env.PUBLIC_URL
- return `http://localhost:${process.env.PORT ?? 3000}`
-}
-
-export function getInitials(name: string): string {
- const [first, second] = name.split(" ")
- if (first?.[0] && second?.[0]) return first[0].toUpperCase() + second[0].toUpperCase()
- if (first?.[0] && !second?.[0] && first?.[1]) return first[0].toUpperCase() + first[1].toLowerCase()
-
- return name.slice(0, 2) // fallback
-}
-
-export async function wait(ms: number): Promise {
- return new Promise((res) => setTimeout(res, ms))
-}
diff --git a/src/lib/utils/telegram.ts b/src/lib/utils/telegram.ts
deleted file mode 100644
index a7741cc..0000000
--- a/src/lib/utils/telegram.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { TgUser } from "../../server/trpc/types"
-
-export function stripChatId(chatId: number): number {
- if (chatId > 0) return chatId
- const positive = -chatId
-
- const str = positive.toString()
- if (str.length < 13) return positive
- return parseInt(str.slice(1), 10)
-}
-
-export function fmtUser(user: TgUser, printId: boolean = true) {
- let string = user.firstName
- if (user.lastName) string += ` ${user.lastName}`
- if (user.username) string += ` @${user.username}`
- if (printId) string += ` [${user.id}]`
- return string
-}
diff --git a/src/middleware.ts b/src/middleware.ts
deleted file mode 100644
index 95fd401..0000000
--- a/src/middleware.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { AUTH_PATH } from "@polinetwork/backend"
-import type { NextRequest } from "next/server"
-import { NextResponse } from "next/server"
-import { env } from "./env"
-
-export const config = {
- matcher: [`/api/auth/:path*`],
-}
-
-export function middleware(request: NextRequest) {
- if (request.nextUrl.pathname.startsWith(AUTH_PATH)) {
- return NextResponse.rewrite(new URL(request.nextUrl.pathname + request.nextUrl.search, env.BACKEND_URL))
- }
-}
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
new file mode 100644
index 0000000..a3e5ec9
--- /dev/null
+++ b/src/routeTree.gen.ts
@@ -0,0 +1,376 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as LoginRouteImport } from './routes/login'
+import { Route as DashboardRouteImport } from './routes/dashboard'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as DashboardIndexRouteImport } from './routes/dashboard/index'
+import { Route as OnboardingUnauthorizedRouteImport } from './routes/onboarding/unauthorized'
+import { Route as OnboardingLinkRouteImport } from './routes/onboarding/link'
+import { Route as DashboardAccountRouteImport } from './routes/dashboard/account'
+import { Route as DashboardWebGuidesRouteImport } from './routes/dashboard/web/guides'
+import { Route as DashboardTelegramGroupsRouteImport } from './routes/dashboard/telegram/groups'
+import { Route as DashboardTelegramGrantsRouteImport } from './routes/dashboard/telegram/grants'
+import { Route as DashboardAzureMembersRouteImport } from './routes/dashboard/azure/members'
+import { Route as DashboardAzureGroupsRouteImport } from './routes/dashboard/azure/groups'
+import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
+import { Route as DashboardTelegramUsersIndexRouteImport } from './routes/dashboard/telegram/users/index'
+import { Route as DashboardTelegramUsersUserIdRouteImport } from './routes/dashboard/telegram/users/$userId'
+
+const LoginRoute = LoginRouteImport.update({
+ id: '/login',
+ path: '/login',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DashboardRoute = DashboardRouteImport.update({
+ id: '/dashboard',
+ path: '/dashboard',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DashboardIndexRoute = DashboardIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => DashboardRoute,
+} as any)
+const OnboardingUnauthorizedRoute = OnboardingUnauthorizedRouteImport.update({
+ id: '/onboarding/unauthorized',
+ path: '/onboarding/unauthorized',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const OnboardingLinkRoute = OnboardingLinkRouteImport.update({
+ id: '/onboarding/link',
+ path: '/onboarding/link',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DashboardAccountRoute = DashboardAccountRouteImport.update({
+ id: '/account',
+ path: '/account',
+ getParentRoute: () => DashboardRoute,
+} as any)
+const DashboardWebGuidesRoute = DashboardWebGuidesRouteImport.update({
+ id: '/web/guides',
+ path: '/web/guides',
+ getParentRoute: () => DashboardRoute,
+} as any)
+const DashboardTelegramGroupsRoute = DashboardTelegramGroupsRouteImport.update({
+ id: '/telegram/groups',
+ path: '/telegram/groups',
+ getParentRoute: () => DashboardRoute,
+} as any)
+const DashboardTelegramGrantsRoute = DashboardTelegramGrantsRouteImport.update({
+ id: '/telegram/grants',
+ path: '/telegram/grants',
+ getParentRoute: () => DashboardRoute,
+} as any)
+const DashboardAzureMembersRoute = DashboardAzureMembersRouteImport.update({
+ id: '/azure/members',
+ path: '/azure/members',
+ getParentRoute: () => DashboardRoute,
+} as any)
+const DashboardAzureGroupsRoute = DashboardAzureGroupsRouteImport.update({
+ id: '/azure/groups',
+ path: '/azure/groups',
+ getParentRoute: () => DashboardRoute,
+} as any)
+const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
+ id: '/api/auth/$',
+ path: '/api/auth/$',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const DashboardTelegramUsersIndexRoute =
+ DashboardTelegramUsersIndexRouteImport.update({
+ id: '/telegram/users/',
+ path: '/telegram/users/',
+ getParentRoute: () => DashboardRoute,
+ } as any)
+const DashboardTelegramUsersUserIdRoute =
+ DashboardTelegramUsersUserIdRouteImport.update({
+ id: '/telegram/users/$userId',
+ path: '/telegram/users/$userId',
+ getParentRoute: () => DashboardRoute,
+ } as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/dashboard': typeof DashboardRouteWithChildren
+ '/login': typeof LoginRoute
+ '/dashboard/account': typeof DashboardAccountRoute
+ '/onboarding/link': typeof OnboardingLinkRoute
+ '/onboarding/unauthorized': typeof OnboardingUnauthorizedRoute
+ '/dashboard/': typeof DashboardIndexRoute
+ '/api/auth/$': typeof ApiAuthSplatRoute
+ '/dashboard/azure/groups': typeof DashboardAzureGroupsRoute
+ '/dashboard/azure/members': typeof DashboardAzureMembersRoute
+ '/dashboard/telegram/grants': typeof DashboardTelegramGrantsRoute
+ '/dashboard/telegram/groups': typeof DashboardTelegramGroupsRoute
+ '/dashboard/web/guides': typeof DashboardWebGuidesRoute
+ '/dashboard/telegram/users/$userId': typeof DashboardTelegramUsersUserIdRoute
+ '/dashboard/telegram/users/': typeof DashboardTelegramUsersIndexRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/login': typeof LoginRoute
+ '/dashboard/account': typeof DashboardAccountRoute
+ '/onboarding/link': typeof OnboardingLinkRoute
+ '/onboarding/unauthorized': typeof OnboardingUnauthorizedRoute
+ '/dashboard': typeof DashboardIndexRoute
+ '/api/auth/$': typeof ApiAuthSplatRoute
+ '/dashboard/azure/groups': typeof DashboardAzureGroupsRoute
+ '/dashboard/azure/members': typeof DashboardAzureMembersRoute
+ '/dashboard/telegram/grants': typeof DashboardTelegramGrantsRoute
+ '/dashboard/telegram/groups': typeof DashboardTelegramGroupsRoute
+ '/dashboard/web/guides': typeof DashboardWebGuidesRoute
+ '/dashboard/telegram/users/$userId': typeof DashboardTelegramUsersUserIdRoute
+ '/dashboard/telegram/users': typeof DashboardTelegramUsersIndexRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/dashboard': typeof DashboardRouteWithChildren
+ '/login': typeof LoginRoute
+ '/dashboard/account': typeof DashboardAccountRoute
+ '/onboarding/link': typeof OnboardingLinkRoute
+ '/onboarding/unauthorized': typeof OnboardingUnauthorizedRoute
+ '/dashboard/': typeof DashboardIndexRoute
+ '/api/auth/$': typeof ApiAuthSplatRoute
+ '/dashboard/azure/groups': typeof DashboardAzureGroupsRoute
+ '/dashboard/azure/members': typeof DashboardAzureMembersRoute
+ '/dashboard/telegram/grants': typeof DashboardTelegramGrantsRoute
+ '/dashboard/telegram/groups': typeof DashboardTelegramGroupsRoute
+ '/dashboard/web/guides': typeof DashboardWebGuidesRoute
+ '/dashboard/telegram/users/$userId': typeof DashboardTelegramUsersUserIdRoute
+ '/dashboard/telegram/users/': typeof DashboardTelegramUsersIndexRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths:
+ | '/'
+ | '/dashboard'
+ | '/login'
+ | '/dashboard/account'
+ | '/onboarding/link'
+ | '/onboarding/unauthorized'
+ | '/dashboard/'
+ | '/api/auth/$'
+ | '/dashboard/azure/groups'
+ | '/dashboard/azure/members'
+ | '/dashboard/telegram/grants'
+ | '/dashboard/telegram/groups'
+ | '/dashboard/web/guides'
+ | '/dashboard/telegram/users/$userId'
+ | '/dashboard/telegram/users/'
+ fileRoutesByTo: FileRoutesByTo
+ to:
+ | '/'
+ | '/login'
+ | '/dashboard/account'
+ | '/onboarding/link'
+ | '/onboarding/unauthorized'
+ | '/dashboard'
+ | '/api/auth/$'
+ | '/dashboard/azure/groups'
+ | '/dashboard/azure/members'
+ | '/dashboard/telegram/grants'
+ | '/dashboard/telegram/groups'
+ | '/dashboard/web/guides'
+ | '/dashboard/telegram/users/$userId'
+ | '/dashboard/telegram/users'
+ id:
+ | '__root__'
+ | '/'
+ | '/dashboard'
+ | '/login'
+ | '/dashboard/account'
+ | '/onboarding/link'
+ | '/onboarding/unauthorized'
+ | '/dashboard/'
+ | '/api/auth/$'
+ | '/dashboard/azure/groups'
+ | '/dashboard/azure/members'
+ | '/dashboard/telegram/grants'
+ | '/dashboard/telegram/groups'
+ | '/dashboard/web/guides'
+ | '/dashboard/telegram/users/$userId'
+ | '/dashboard/telegram/users/'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ DashboardRoute: typeof DashboardRouteWithChildren
+ LoginRoute: typeof LoginRoute
+ OnboardingLinkRoute: typeof OnboardingLinkRoute
+ OnboardingUnauthorizedRoute: typeof OnboardingUnauthorizedRoute
+ ApiAuthSplatRoute: typeof ApiAuthSplatRoute
+}
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/login': {
+ id: '/login'
+ path: '/login'
+ fullPath: '/login'
+ preLoaderRoute: typeof LoginRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/dashboard': {
+ id: '/dashboard'
+ path: '/dashboard'
+ fullPath: '/dashboard'
+ preLoaderRoute: typeof DashboardRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/dashboard/': {
+ id: '/dashboard/'
+ path: '/'
+ fullPath: '/dashboard/'
+ preLoaderRoute: typeof DashboardIndexRouteImport
+ parentRoute: typeof DashboardRoute
+ }
+ '/onboarding/unauthorized': {
+ id: '/onboarding/unauthorized'
+ path: '/onboarding/unauthorized'
+ fullPath: '/onboarding/unauthorized'
+ preLoaderRoute: typeof OnboardingUnauthorizedRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/onboarding/link': {
+ id: '/onboarding/link'
+ path: '/onboarding/link'
+ fullPath: '/onboarding/link'
+ preLoaderRoute: typeof OnboardingLinkRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/dashboard/account': {
+ id: '/dashboard/account'
+ path: '/account'
+ fullPath: '/dashboard/account'
+ preLoaderRoute: typeof DashboardAccountRouteImport
+ parentRoute: typeof DashboardRoute
+ }
+ '/dashboard/web/guides': {
+ id: '/dashboard/web/guides'
+ path: '/web/guides'
+ fullPath: '/dashboard/web/guides'
+ preLoaderRoute: typeof DashboardWebGuidesRouteImport
+ parentRoute: typeof DashboardRoute
+ }
+ '/dashboard/telegram/groups': {
+ id: '/dashboard/telegram/groups'
+ path: '/telegram/groups'
+ fullPath: '/dashboard/telegram/groups'
+ preLoaderRoute: typeof DashboardTelegramGroupsRouteImport
+ parentRoute: typeof DashboardRoute
+ }
+ '/dashboard/telegram/grants': {
+ id: '/dashboard/telegram/grants'
+ path: '/telegram/grants'
+ fullPath: '/dashboard/telegram/grants'
+ preLoaderRoute: typeof DashboardTelegramGrantsRouteImport
+ parentRoute: typeof DashboardRoute
+ }
+ '/dashboard/azure/members': {
+ id: '/dashboard/azure/members'
+ path: '/azure/members'
+ fullPath: '/dashboard/azure/members'
+ preLoaderRoute: typeof DashboardAzureMembersRouteImport
+ parentRoute: typeof DashboardRoute
+ }
+ '/dashboard/azure/groups': {
+ id: '/dashboard/azure/groups'
+ path: '/azure/groups'
+ fullPath: '/dashboard/azure/groups'
+ preLoaderRoute: typeof DashboardAzureGroupsRouteImport
+ parentRoute: typeof DashboardRoute
+ }
+ '/api/auth/$': {
+ id: '/api/auth/$'
+ path: '/api/auth/$'
+ fullPath: '/api/auth/$'
+ preLoaderRoute: typeof ApiAuthSplatRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/dashboard/telegram/users/': {
+ id: '/dashboard/telegram/users/'
+ path: '/telegram/users'
+ fullPath: '/dashboard/telegram/users/'
+ preLoaderRoute: typeof DashboardTelegramUsersIndexRouteImport
+ parentRoute: typeof DashboardRoute
+ }
+ '/dashboard/telegram/users/$userId': {
+ id: '/dashboard/telegram/users/$userId'
+ path: '/telegram/users/$userId'
+ fullPath: '/dashboard/telegram/users/$userId'
+ preLoaderRoute: typeof DashboardTelegramUsersUserIdRouteImport
+ parentRoute: typeof DashboardRoute
+ }
+ }
+}
+
+interface DashboardRouteChildren {
+ DashboardAccountRoute: typeof DashboardAccountRoute
+ DashboardIndexRoute: typeof DashboardIndexRoute
+ DashboardAzureGroupsRoute: typeof DashboardAzureGroupsRoute
+ DashboardAzureMembersRoute: typeof DashboardAzureMembersRoute
+ DashboardTelegramGrantsRoute: typeof DashboardTelegramGrantsRoute
+ DashboardTelegramGroupsRoute: typeof DashboardTelegramGroupsRoute
+ DashboardWebGuidesRoute: typeof DashboardWebGuidesRoute
+ DashboardTelegramUsersUserIdRoute: typeof DashboardTelegramUsersUserIdRoute
+ DashboardTelegramUsersIndexRoute: typeof DashboardTelegramUsersIndexRoute
+}
+
+const DashboardRouteChildren: DashboardRouteChildren = {
+ DashboardAccountRoute: DashboardAccountRoute,
+ DashboardIndexRoute: DashboardIndexRoute,
+ DashboardAzureGroupsRoute: DashboardAzureGroupsRoute,
+ DashboardAzureMembersRoute: DashboardAzureMembersRoute,
+ DashboardTelegramGrantsRoute: DashboardTelegramGrantsRoute,
+ DashboardTelegramGroupsRoute: DashboardTelegramGroupsRoute,
+ DashboardWebGuidesRoute: DashboardWebGuidesRoute,
+ DashboardTelegramUsersUserIdRoute: DashboardTelegramUsersUserIdRoute,
+ DashboardTelegramUsersIndexRoute: DashboardTelegramUsersIndexRoute,
+}
+
+const DashboardRouteWithChildren = DashboardRoute._addFileChildren(
+ DashboardRouteChildren,
+)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ DashboardRoute: DashboardRouteWithChildren,
+ LoginRoute: LoginRoute,
+ OnboardingLinkRoute: OnboardingLinkRoute,
+ OnboardingUnauthorizedRoute: OnboardingUnauthorizedRoute,
+ ApiAuthSplatRoute: ApiAuthSplatRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/react-start'
+declare module '@tanstack/react-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/src/router.tsx b/src/router.tsx
new file mode 100644
index 0000000..2379cb6
--- /dev/null
+++ b/src/router.tsx
@@ -0,0 +1,17 @@
+import { createRouter } from "@tanstack/react-router"
+import { routeTree } from "./routeTree.gen"
+
+export function getRouter() {
+ return createRouter({
+ routeTree,
+ defaultPreload: "intent",
+ defaultPendingMs: 100,
+ scrollRestoration: true,
+ })
+}
+
+declare module "@tanstack/react-router" {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx
new file mode 100644
index 0000000..f2b7ce2
--- /dev/null
+++ b/src/routes/__root.tsx
@@ -0,0 +1,40 @@
+import { createRootRoute, HeadContent, Scripts } from "@tanstack/react-router"
+import { RouteError, RouteNotFound } from "@/components/route-error"
+import { Toaster } from "../components/ui/sonner"
+import { TooltipProvider } from "../components/ui/tooltip"
+import appCss from "../styles.css?url"
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [
+ { charSet: "utf-8" },
+ { name: "viewport", content: "width=device-width, initial-scale=1" },
+ { title: "PoliNetwork Admin" },
+ { name: "description", content: "PoliNetwork's internal operations console." },
+ ],
+ links: [{ rel: "stylesheet", href: appCss }],
+ }),
+ errorComponent: RouteError,
+ notFoundComponent: RouteNotFound,
+ shellComponent: RootDocument,
+})
+
+function RootDocument({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
diff --git a/src/routes/api/auth/$.ts b/src/routes/api/auth/$.ts
new file mode 100644
index 0000000..fa4ddf3
--- /dev/null
+++ b/src/routes/api/auth/$.ts
@@ -0,0 +1,14 @@
+import { createFileRoute } from "@tanstack/react-router"
+import { proxyAuthRequest } from "@/server/auth-proxy.server"
+
+export const Route = createFileRoute("/api/auth/$")({
+ server: {
+ handlers: {
+ GET: ({ request }) => proxyAuthRequest(request),
+ POST: ({ request }) => proxyAuthRequest(request),
+ PUT: ({ request }) => proxyAuthRequest(request),
+ PATCH: ({ request }) => proxyAuthRequest(request),
+ DELETE: ({ request }) => proxyAuthRequest(request),
+ },
+ },
+})
diff --git a/src/routes/dashboard.tsx b/src/routes/dashboard.tsx
new file mode 100644
index 0000000..52fa244
--- /dev/null
+++ b/src/routes/dashboard.tsx
@@ -0,0 +1,21 @@
+import { createFileRoute, redirect } from "@tanstack/react-router"
+import { DashboardFrame } from "@/components/dashboard-frame"
+import { RouteError } from "@/components/route-error"
+import { getDashboardAccess } from "@/features/auth/auth.functions"
+
+export const Route = createFileRoute("/dashboard")({
+ beforeLoad: async () => {
+ const access = await getDashboardAccess()
+ if (access.status === "unauthenticated") throw redirect({ to: "/login" })
+ if (access.status === "telegram-unlinked") throw redirect({ to: "/onboarding/link" })
+ if (access.status === "forbidden") throw redirect({ to: "/onboarding/unauthorized" })
+ return { session: access.session, roles: access.roles }
+ },
+ errorComponent: RouteError,
+ component: DashboardLayout,
+})
+
+function DashboardLayout() {
+ const { session } = Route.useRouteContext()
+ return
+}
diff --git a/src/routes/dashboard/account.tsx b/src/routes/dashboard/account.tsx
new file mode 100644
index 0000000..aabb01f
--- /dev/null
+++ b/src/routes/dashboard/account.tsx
@@ -0,0 +1,11 @@
+import { createFileRoute } from "@tanstack/react-router"
+import { AccountPage } from "@/features/account/account-page"
+
+export const Route = createFileRoute("/dashboard/account")({
+ component: AccountRoute,
+})
+
+function AccountRoute() {
+ const { roles, session } = Route.useRouteContext()
+ return
+}
diff --git a/src/routes/dashboard/azure/groups.tsx b/src/routes/dashboard/azure/groups.tsx
new file mode 100644
index 0000000..5d0ca51
--- /dev/null
+++ b/src/routes/dashboard/azure/groups.tsx
@@ -0,0 +1,15 @@
+import { createFileRoute } from "@tanstack/react-router"
+import { getAzureDirectory } from "@/features/azure/azure.functions"
+import { AzureGroupsPage, AzureGroupsSkeleton } from "@/features/azure/groups-page"
+
+export const Route = createFileRoute("/dashboard/azure/groups")({
+ loader: () => getAzureDirectory(),
+ head: () => ({ meta: [{ title: "Microsoft 365 Groups | PoliNetwork Admin" }] }),
+ pendingComponent: AzureGroupsSkeleton,
+ component: AzureGroupsRoute,
+})
+
+function AzureGroupsRoute() {
+ const { groups, members } = Route.useLoaderData()
+ return
+}
diff --git a/src/routes/dashboard/azure/members.tsx b/src/routes/dashboard/azure/members.tsx
new file mode 100644
index 0000000..fb5ae37
--- /dev/null
+++ b/src/routes/dashboard/azure/members.tsx
@@ -0,0 +1,15 @@
+import { createFileRoute } from "@tanstack/react-router"
+import { DataPageSkeleton } from "@/components/loading-skeleton"
+import { getAzureMembers } from "@/features/azure/azure.functions"
+import { AzureMembersPage } from "@/features/azure/members-page"
+
+export const Route = createFileRoute("/dashboard/azure/members")({
+ loader: () => getAzureMembers(),
+ pendingComponent: () => ,
+ component: AzureMembersRoute,
+})
+
+function AzureMembersRoute() {
+ const members = Route.useLoaderData()
+ return
+}
diff --git a/src/routes/dashboard/index.tsx b/src/routes/dashboard/index.tsx
new file mode 100644
index 0000000..a45ad00
--- /dev/null
+++ b/src/routes/dashboard/index.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from "@tanstack/react-router"
+import { DashboardOverviewPage } from "@/features/dashboard/overview-page"
+
+export const Route = createFileRoute("/dashboard/")({ component: DashboardOverviewPage })
diff --git a/src/routes/dashboard/telegram/grants.tsx b/src/routes/dashboard/telegram/grants.tsx
new file mode 100644
index 0000000..61e2169
--- /dev/null
+++ b/src/routes/dashboard/telegram/grants.tsx
@@ -0,0 +1,15 @@
+import { createFileRoute } from "@tanstack/react-router"
+import { DataPageSkeleton } from "@/components/loading-skeleton"
+import { getTelegramGrants } from "@/features/telegram/grants.functions"
+import { TelegramGrantsPage } from "@/features/telegram/grants-page"
+
+export const Route = createFileRoute("/dashboard/telegram/grants")({
+ loader: () => getTelegramGrants(),
+ pendingComponent: () => ,
+ component: TelegramGrantsRoute,
+})
+
+function TelegramGrantsRoute() {
+ const grants = Route.useLoaderData()
+ return
+}
diff --git a/src/routes/dashboard/telegram/groups.tsx b/src/routes/dashboard/telegram/groups.tsx
new file mode 100644
index 0000000..301d9d1
--- /dev/null
+++ b/src/routes/dashboard/telegram/groups.tsx
@@ -0,0 +1,15 @@
+import { createFileRoute } from "@tanstack/react-router"
+import { DataPageSkeleton } from "@/components/loading-skeleton"
+import { getTelegramGroups } from "@/features/telegram/groups.functions"
+import { TelegramGroupsPage } from "@/features/telegram/groups-page"
+
+export const Route = createFileRoute("/dashboard/telegram/groups")({
+ loader: () => getTelegramGroups(),
+ pendingComponent: () => ,
+ component: TelegramGroupsRoute,
+})
+
+function TelegramGroupsRoute() {
+ const groups = Route.useLoaderData()
+ return
+}
diff --git a/src/routes/dashboard/telegram/users/$userId.tsx b/src/routes/dashboard/telegram/users/$userId.tsx
new file mode 100644
index 0000000..bb168ab
--- /dev/null
+++ b/src/routes/dashboard/telegram/users/$userId.tsx
@@ -0,0 +1,25 @@
+import { createFileRoute, notFound } from "@tanstack/react-router"
+import { DetailPageSkeleton } from "@/components/loading-skeleton"
+import { TelegramUserProfile } from "@/features/telegram/user-detail/profile"
+import { UserDetailBackLink } from "@/features/telegram/user-detail/sections"
+import { getTelegramUserDetails } from "@/features/telegram/users.functions"
+
+export const Route = createFileRoute("/dashboard/telegram/users/$userId")({
+ loader: ({ params }) => {
+ const userId = Number(params.userId)
+ if (!Number.isInteger(userId) || userId <= 0) throw notFound()
+ return getTelegramUserDetails({ data: { userId } })
+ },
+ pendingComponent: DetailPageSkeleton,
+ component: UserProfileRoute,
+})
+
+function UserProfileRoute() {
+ const data = Route.useLoaderData()
+ return (
+
+
+
+
+ )
+}
diff --git a/src/routes/dashboard/telegram/users/index.tsx b/src/routes/dashboard/telegram/users/index.tsx
new file mode 100644
index 0000000..a2d78a2
--- /dev/null
+++ b/src/routes/dashboard/telegram/users/index.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from "@tanstack/react-router"
+import { DataPageSkeleton } from "@/components/loading-skeleton"
+import { getTelegramUsers } from "@/features/telegram/users.functions"
+import { TelegramUsersPage } from "@/features/telegram/users-page"
+
+export const Route = createFileRoute("/dashboard/telegram/users/")({
+ loader: () => getTelegramUsers(),
+ pendingComponent: () => ,
+ component: TelegramUsersRoute,
+})
+
+function TelegramUsersRoute() {
+ return
+}
diff --git a/src/routes/dashboard/web/guides.tsx b/src/routes/dashboard/web/guides.tsx
new file mode 100644
index 0000000..db593e4
--- /dev/null
+++ b/src/routes/dashboard/web/guides.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute } from "@tanstack/react-router"
+import { DataPageSkeleton } from "@/components/loading-skeleton"
+import { getGuides } from "@/features/guides/guides.functions"
+import { GuidesPage } from "@/features/guides/guides-page"
+
+export const Route = createFileRoute("/dashboard/web/guides")({
+ loader: () => getGuides(),
+ pendingComponent: () => ,
+ component: GuidesRoute,
+})
+
+function GuidesRoute() {
+ return
+}
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
new file mode 100644
index 0000000..e753ac4
--- /dev/null
+++ b/src/routes/index.tsx
@@ -0,0 +1,7 @@
+import { createFileRoute, redirect } from "@tanstack/react-router"
+
+export const Route = createFileRoute("/")({
+ beforeLoad: () => {
+ throw redirect({ to: "/dashboard" })
+ },
+})
diff --git a/src/routes/login.tsx b/src/routes/login.tsx
new file mode 100644
index 0000000..622543f
--- /dev/null
+++ b/src/routes/login.tsx
@@ -0,0 +1,11 @@
+import { createFileRoute, redirect } from "@tanstack/react-router"
+import { getSessionState } from "@/features/auth/auth.functions"
+import { LoginPage } from "@/features/auth/login-page"
+
+export const Route = createFileRoute("/login")({
+ beforeLoad: async () => {
+ const { agentMode, session } = await getSessionState()
+ if (!agentMode && session) throw redirect({ to: "/dashboard" })
+ },
+ component: LoginPage,
+})
diff --git a/src/routes/onboarding/link.tsx b/src/routes/onboarding/link.tsx
new file mode 100644
index 0000000..5104802
--- /dev/null
+++ b/src/routes/onboarding/link.tsx
@@ -0,0 +1,19 @@
+import { createFileRoute, redirect } from "@tanstack/react-router"
+import { getDashboardAccess } from "@/features/auth/auth.functions"
+import { TelegramLinkPage } from "@/features/onboarding/telegram-link-page"
+
+export const Route = createFileRoute("/onboarding/link")({
+ beforeLoad: async () => {
+ const access = await getDashboardAccess()
+ if (access.status === "unauthenticated") throw redirect({ to: "/login" })
+ if (access.status === "authorized") throw redirect({ to: "/dashboard" })
+ if (access.status === "forbidden") throw redirect({ to: "/onboarding/unauthorized" })
+ return { session: access.session }
+ },
+ component: TelegramLinkRoute,
+})
+
+function TelegramLinkRoute() {
+ const { session } = Route.useRouteContext()
+ return
+}
diff --git a/src/routes/onboarding/unauthorized.tsx b/src/routes/onboarding/unauthorized.tsx
new file mode 100644
index 0000000..5e1a124
--- /dev/null
+++ b/src/routes/onboarding/unauthorized.tsx
@@ -0,0 +1,74 @@
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router"
+import { LogOut, ShieldX } from "lucide-react"
+import { useState } from "react"
+import { AppMark } from "@/components/app-mark"
+import { ThemeToggle } from "@/components/theme-toggle"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { getDashboardAccess } from "@/features/auth/auth.functions"
+import { auth } from "@/lib/auth"
+
+export const Route = createFileRoute("/onboarding/unauthorized")({
+ beforeLoad: async () => {
+ const access = await getDashboardAccess()
+ if (access.status === "unauthenticated") throw redirect({ to: "/login" })
+ if (access.status === "telegram-unlinked") throw redirect({ to: "/onboarding/link" })
+ if (access.status === "authorized") throw redirect({ to: "/dashboard" })
+ return { session: access.session }
+ },
+ component: Unauthorized,
+})
+
+function Unauthorized() {
+ const { session } = Route.useRouteContext()
+ const router = useRouter()
+ const [error, setError] = useState("")
+ const [pending, setPending] = useState(false)
+
+ async function logout() {
+ setPending(true)
+ setError("")
+ try {
+ const result = await auth.signOut()
+ if (result.error) throw new Error(result.error.message)
+ await router.invalidate()
+ await router.navigate({ to: "/login", replace: true })
+ } catch {
+ setError("Could not sign out. Please try again.")
+ setPending(false)
+ }
+ }
+
+ return (
+
+
+
+
+