From c444ab5ab6f6f3fe543c8eae7c1862a67221a6c7 Mon Sep 17 00:00:00 2001 From: vidvidvid Date: Fri, 31 Jan 2025 10:23:54 +0100 Subject: [PATCH] undelegate --- packages/ui/components/ui/collapsible.tsx | 11 + .../components/veion/DelegateVeIonTable.tsx | 42 ++- .../ui/components/veion/DelegatedToCell.tsx | 45 ++- .../ui/components/veion/UndelegateDIalog.tsx | 303 ++++++++++++++++++ packages/ui/hooks/veion/useVeIONLocks.ts | 70 +++- packages/ui/hooks/veion/useVeIONManage.ts | 100 +++--- packages/ui/lib/utils.ts | 6 +- packages/ui/package.json | 7 +- yarn.lock | 67 ++-- 9 files changed, 567 insertions(+), 84 deletions(-) create mode 100644 packages/ui/components/ui/collapsible.tsx create mode 100644 packages/ui/components/veion/UndelegateDIalog.tsx diff --git a/packages/ui/components/ui/collapsible.tsx b/packages/ui/components/ui/collapsible.tsx new file mode 100644 index 0000000000..9fa48946af --- /dev/null +++ b/packages/ui/components/ui/collapsible.tsx @@ -0,0 +1,11 @@ +"use client" + +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" + +const Collapsible = CollapsiblePrimitive.Root + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/packages/ui/components/veion/DelegateVeIonTable.tsx b/packages/ui/components/veion/DelegateVeIonTable.tsx index 45c9f65ca5..707d3a32e4 100644 --- a/packages/ui/components/veion/DelegateVeIonTable.tsx +++ b/packages/ui/components/veion/DelegateVeIonTable.tsx @@ -8,23 +8,35 @@ import type { } from '@ui/components/CommonTable'; import TokenPair from '@ui/components/TokenPair'; import { useVeIONContext } from '@ui/context/VeIonContext'; -import { useToast } from '@ui/hooks/use-toast'; import type { DelegateVeionData } from '@ui/types/veION'; import { DelegatedToCell } from './DelegatedToCell'; import PositionTitle from './PositionTitle'; +import UndelegateDialog from './UndelegateDIalog'; interface DelegateVeionTableProps { onUndelegateSuccess?: () => void; } function DelegateVeionTable({ onUndelegateSuccess }: DelegateVeionTableProps) { - const { toast } = useToast(); - const [processingId, setProcessingId] = useState(null); + const [selectedPosition, setSelectedPosition] = + useState(null); + const [isUndelegateDialogOpen, setIsUndelegateDialogOpen] = useState(false); + const { locks: { delegatedLocks, isLoading } } = useVeIONContext(); + const handleUndelegateClick = (position: DelegateVeionData) => { + setSelectedPosition(position); + setIsUndelegateDialogOpen(true); + }; + + const handleDialogClose = () => { + setSelectedPosition(null); + setIsUndelegateDialogOpen(false); + }; + const delegateVeionColumns: EnhancedColumnDef[] = [ { id: 'id', @@ -109,15 +121,13 @@ function DelegateVeionTable({ onUndelegateSuccess }: DelegateVeionTableProps) { enableSorting: false, cell: ({ row }: MarketCellProps) => { const data = row.original; - const isProcessing = processingId === data.id; return (
{}} - disabled={isProcessing} - label={isProcessing ? 'Undelegating...' : 'Undelegate'} + action={() => handleUndelegateClick(data)} + label="Undelegate" />
); @@ -132,6 +142,24 @@ function DelegateVeionTable({ onUndelegateSuccess }: DelegateVeionTableProps) { columns={delegateVeionColumns} isLoading={isLoading} /> + + {selectedPosition && ( + Number(bigintValue) + ), + amounts: selectedPosition.delegation.delegatedAmounts // Add this line + } + }} + onSuccess={onUndelegateSuccess} + /> + )} ); } diff --git a/packages/ui/components/veion/DelegatedToCell.tsx b/packages/ui/components/veion/DelegatedToCell.tsx index bd12da7206..a26c3a4235 100644 --- a/packages/ui/components/veion/DelegatedToCell.tsx +++ b/packages/ui/components/veion/DelegatedToCell.tsx @@ -2,6 +2,14 @@ import React from 'react'; import Image from 'next/image'; +import { formatUnits } from 'viem'; + +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger +} from '@ui/components/ui/tooltip'; import { getChainName } from '@ui/constants/mock'; import { cn } from '@ui/lib/utils'; import type { ChainId } from '@ui/types/veION'; @@ -27,18 +35,22 @@ interface BadgePositionTitleProps { position: number; size?: Size; className?: string; + amount?: string; // Add this } const BadgePositionTitle = ({ chainId, position, size = 'md', - className + className, + amount }: BadgePositionTitleProps) => { const chainName = getChainName(chainId); const imageSize = imageSizes[size]; - return ( + const formattedAmount = amount ? formatUnits(BigInt(amount), 18) : undefined; + + const badge = (
#{position}
); + + if (!formattedAmount) return badge; + + return ( + + + {badge} + +

Delegated: {Number(formattedAmount).toFixed(4)} BLP

+
+
+
+ ); }; -// Container component for multiple badges +// Update BadgeGrid props to include amounts interface BadgeGridProps { delegatedTo: number[]; + delegatedAmounts?: string[]; // Add this chainId: ChainId; size?: Size; } -const BadgeGrid = ({ delegatedTo, chainId, size = 'md' }: BadgeGridProps) => { +const BadgeGrid = ({ + delegatedTo, + delegatedAmounts, + chainId, + size = 'md' +}: BadgeGridProps) => { return (
- {delegatedTo.map((id: number) => ( + {delegatedTo.map((id: number, index: number) => ( ))}
); }; -// Updated cell component +// Update cell component to pass amounts const DelegatedToCell = ({ row }: MarketCellProps) => ( ); diff --git a/packages/ui/components/veion/UndelegateDIalog.tsx b/packages/ui/components/veion/UndelegateDIalog.tsx new file mode 100644 index 0000000000..62ddc4ccce --- /dev/null +++ b/packages/ui/components/veion/UndelegateDIalog.tsx @@ -0,0 +1,303 @@ +import { useState } from 'react'; + +import { InfoIcon, ChevronDown } from 'lucide-react'; +import { formatUnits, parseUnits } from 'viem'; +import { useAccount } from 'wagmi'; + +import TransactionButton from '@ui/components/TransactionButton'; +import { Button } from '@ui/components/ui/button'; +import { Checkbox } from '@ui/components/ui/checkbox'; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger +} from '@ui/components/ui/collapsible'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle +} from '@ui/components/ui/dialog'; +import { Input } from '@ui/components/ui/input'; +import { Slider } from '@ui/components/ui/slider'; +import { useVeIONManage } from '@ui/hooks/veion/useVeIONManage'; +import type { ChainId } from '@ui/types/veION'; + +import { BadgePositionTitle } from './DelegatedToCell'; + +interface DelegatePosition { + id: string; + chainId: ChainId; + delegation: { + delegatedTo: number[]; + amounts: string[]; + }; +} + +interface UndelegateDialogProps { + isOpen: boolean; + onClose: () => void; + position: DelegatePosition; + onSuccess?: () => void; +} + +export default function UndelegateDialog({ + isOpen, + onClose, + position, + onSuccess +}: UndelegateDialogProps) { + const [selectedPositions, setSelectedPositions] = useState< + Record + >({}); + const [undelegateAmounts, setUndelegateAmounts] = useState< + Record + >({}); + const [openPositions, setOpenPositions] = useState>( + {} + ); + + const { address } = useAccount(); + const { handleUndelegate } = useVeIONManage(position.chainId); + + const togglePosition = (id: number, checked: boolean) => { + setSelectedPositions((prev) => ({ ...prev, [id]: checked })); + if (!checked) { + setUndelegateAmounts((prev) => { + const next = { ...prev }; + delete next[id]; + return next; + }); + setOpenPositions((prev) => { + const next = { ...prev }; + delete next[id]; + return next; + }); + } else { + setUndelegateAmounts((prev) => ({ ...prev, [id]: '0' })); + setOpenPositions((prev) => ({ ...prev, [id]: true })); + } + }; + + const handleSliderChange = (id: number, values: number[]) => { + const index = position.delegation.delegatedTo.indexOf(id); + const maxAmount = position.delegation.amounts[index]; + const percentage = values[0]; + + // If slider is at 100%, use the exact maxAmount to avoid dust + if (percentage === 100) { + setUndelegateAmounts((prev) => ({ ...prev, [id]: maxAmount })); + } else { + const amount = (BigInt(maxAmount) * BigInt(percentage)) / BigInt(100); + setUndelegateAmounts((prev) => ({ ...prev, [id]: amount.toString() })); + } + }; + + const handleInputChange = (id: number, value: string) => { + try { + const index = position.delegation.delegatedTo.indexOf(id); + const maxAmount = position.delegation.amounts[index]; + const maxAmountFormatted = formatUnits(BigInt(maxAmount), 18); + + if (!value || value === '') { + setUndelegateAmounts((prev) => ({ ...prev, [id]: '0' })); + return; + } + + // If input equals max formatted value, use exact maxAmount + if (value === maxAmountFormatted) { + setUndelegateAmounts((prev) => ({ ...prev, [id]: maxAmount })); + return; + } + + const parsedAmount = parseUnits(value, 18); + if (parsedAmount > BigInt(maxAmount)) { + setUndelegateAmounts((prev) => ({ ...prev, [id]: maxAmount })); + } else { + setUndelegateAmounts((prev) => ({ + ...prev, + [id]: parsedAmount.toString() + })); + } + } catch { + // Invalid input - keep previous value + } + }; + + // Helper function to set max amount + const setMaxAmount = (id: number) => { + const index = position.delegation.delegatedTo.indexOf(id); + const maxAmount = position.delegation.amounts[index]; + setUndelegateAmounts((prev) => ({ ...prev, [id]: maxAmount })); + }; + + const selectedIds = position.delegation.delegatedTo.filter( + (id) => selectedPositions[id] + ); + const amounts = selectedIds.map((id) => undelegateAmounts[id] || '0'); + const selectedCount = Object.values(selectedPositions).filter(Boolean).length; + const hasValidAmounts = selectedIds.every( + (id) => BigInt(undelegateAmounts[id] || '0') > BigInt(0) + ); + + const onUndelegateSubmit = async () => { + if (!address || selectedIds.length === 0) return { success: false }; + + const success = await handleUndelegate({ + toIds: selectedIds, + amounts, + id: position.id + }); + + if (success) { + onSuccess?.(); + onClose(); + } + + return { success }; + }; + + return ( + + + + Undelegate veION Positions + + Select positions and amounts to undelegate from your veION position + #{position.id} + + + +
+
+ {position.delegation.delegatedTo.map((delegatedId, index) => { + const maxAmount = position.delegation.amounts[index]; + const currentAmount = undelegateAmounts[delegatedId] || '0'; + const isSelected = selectedPositions[delegatedId]; + const isOpen = openPositions[delegatedId]; + const maxAmountFormatted = formatUnits(BigInt(maxAmount), 18); + const currentAmountFormatted = formatUnits( + BigInt(currentAmount), + 18 + ); + const percentage = Number( + (BigInt(currentAmount) * BigInt(100)) / BigInt(maxAmount) + ); + + return ( +
+ +
+ + togglePosition(delegatedId, checked as boolean) + } + className="h-4 w-4" + /> +
+
+
+ + + {maxAmountFormatted} BLP + +
+ {isSelected && ( + + + + )} +
+
+
+ + +
+
+
+ + handleInputChange(delegatedId, e.target.value) + } + min={0} + max={Number(maxAmountFormatted)} + step={0.0001} + /> + +
+ BLP + + ({percentage}%) + +
+ + handleSliderChange(delegatedId, values) + } + max={100} + step={1} + className="w-full" + /> +
+
+
+
+ ); + })} +
+ +
+ + + Undelegating will remove your voting power delegation for the + selected amounts + +
+
+ + +
+ + +
+
+
+
+ ); +} diff --git a/packages/ui/hooks/veion/useVeIONLocks.ts b/packages/ui/hooks/veion/useVeIONLocks.ts index e6eef34ab5..05c2117096 100644 --- a/packages/ui/hooks/veion/useVeIONLocks.ts +++ b/packages/ui/hooks/veion/useVeIONLocks.ts @@ -156,15 +156,73 @@ export function useVeIONLocks({ isLoading: boolean; }; + // Then let's check our calls + const delegationAmountCalls = tokenIds + .flatMap((tokenId, tokenIdIndex) => + chainConfig.lpTypes.flatMap((lpType, lpTypeIndex) => { + const delegateesResult = + delegateesResults?.[ + tokenIdIndex * chainConfig.lpTypes.length + lpTypeIndex + ]; + + return delegateesResult?.status === 'success' && delegateesResult.result + ? delegateesResult.result.map((delegateeId) => { + return { + address: veIonContract, + abi: iveIonAbi, + functionName: 's_delegations', + args: [BigInt(tokenId), delegateeId, lpType], + chainId + }; + }) + : []; + }) + ) + .filter(Boolean); + + const { + data: delegationAmountResults, + refetch: refetchDelegationAmounts, + isLoading: isLoadingDelegationAmounts + } = useReadContracts({ + contracts: delegationAmountCalls + }) as { + data: ContractResult[] | undefined; + refetch: () => Promise; + isLoading: boolean; + }; + const processedDelegateesResults = delegateesResults?.map((result, index) => { if (result.status !== 'success' || !result.result) return null; + const delegatedTo = result.result; + + const startIndex = delegateesResults + .slice(0, index) + .reduce((acc, prevResult) => { + return ( + acc + + (prevResult?.status === 'success' + ? prevResult.result?.length ?? 0 + : 0) + ); + }, 0); + + const amounts = delegatedTo.map((delegateeId, i) => { + const amountResult = delegationAmountResults?.[startIndex + i]; + + if (amountResult?.status === 'success' && amountResult.result) { + return amountResult.result.toString(); + } + return '0'; + }); + return { delegatedTo: result.result, readyToDelegate: false, delegatedTokenIds: result.result, - delegatedAmounts: [] + delegatedAmounts: amounts }; }) || []; @@ -227,7 +285,8 @@ export function useVeIONLocks({ refetchBalances(), refetchSupplies(), refetchDelegatees(), - refetchIonPrices() + refetchIonPrices(), + refetchDelegationAmounts() ]); }, [ refetchTokenIds, @@ -236,10 +295,10 @@ export function useVeIONLocks({ refetchBalances, refetchSupplies, refetchDelegatees, - refetchIonPrices + refetchIonPrices, + refetchDelegationAmounts ]); - // Calculate overall loading state const isLoading = isLoadingTokenIds || isLoadingAssets || @@ -248,7 +307,8 @@ export function useVeIONLocks({ isLoadingSupplies || isLoadingDelegatees || isLoadingTokenPrices || - isLoadingIonPrices; + isLoadingIonPrices || + isLoadingDelegationAmounts; return { myLocks: allLocks diff --git a/packages/ui/hooks/veion/useVeIONManage.ts b/packages/ui/hooks/veion/useVeIONManage.ts index 39f59c65c7..265eb3fb10 100644 --- a/packages/ui/hooks/veion/useVeIONManage.ts +++ b/packages/ui/hooks/veion/useVeIONManage.ts @@ -12,9 +12,11 @@ import { getAvailableStakingToken } from '@ui/utils/getStakingTokens'; import { useContractWrite } from '../useContractWrite'; +import { iveIonAbi } from '@ionicprotocol/sdk'; + export function useVeIONManage(chain: number) { const veIonContract = getVeIonContract(chain); - const { write, isPending } = useContractWrite(); + const { write } = useContractWrite(); const publicClient = usePublicClient(); const { address } = useAccount(); const { data: walletClient } = useWalletClient(); @@ -40,7 +42,7 @@ export function useVeIONManage(chain: number) { } return { address: veIonContract.address, - abi: veIonContract.abi, + abi: iveIonAbi, functionName, args }; @@ -208,7 +210,7 @@ export function useVeIONManage(chain: number) { try { const tokenIds = await publicClient.readContract({ address: veIonContract.address, - abi: veIonContract.abi, + abi: iveIonAbi, functionName: 'getOwnedTokenIds', args: [ownerAddress] }); @@ -217,14 +219,14 @@ export function useVeIONManage(chain: number) { tokenIds.map(async (id) => { const [assets, balances, boosts] = await publicClient.readContract({ address: veIonContract.address, - abi: veIonContract.abi, + abi: iveIonAbi, functionName: 'balanceOfNFT', args: [id] }); const lockInfo = await publicClient.readContract({ address: veIonContract.address, - abi: veIonContract.abi, + abi: iveIonAbi, functionName: 's_locked', args: [id, 2] }); @@ -439,51 +441,66 @@ export function useVeIONManage(chain: number) { } } - function undelegate({ - fromTokenId, - toTokenIds, - lpToken, - amounts - }: { - fromTokenId: number; - toTokenIds: number[]; - lpToken: `0x${string}`; - amounts: string[]; - }) { - return write( - getContractConfig('undelegate', [ - fromTokenId, - toTokenIds, - lpToken, - amounts - ]), - { - successMessage: 'Successfully undelegated voting power', - errorMessage: 'Failed to undelegate voting power' - } - ); - } - async function handleUndelegate({ - toTokenIds, - amounts + toIds, + amounts, + id }: { - toTokenIds: number[]; + toIds: number[]; amounts: string[]; + id: number | string; }) { - if (!address || !selectedManagePosition || !tokenAddress) return false; + if (!address || !tokenAddress) return false; + + const fromTokenId = BigInt(id); + const toTokenIds = toIds.map((id) => BigInt(id)); try { - await undelegate({ - fromTokenId: +selectedManagePosition.id, - toTokenIds, - lpToken: tokenAddress as `0x${string}`, - amounts + if (!veIonContract || !publicClient) { + console.error('Contract or public client not initialized'); + return false; + } + + // Simulate the transaction + const { request } = await publicClient.simulateContract({ + account: address, + address: veIonContract.address, + abi: iveIonAbi, + functionName: 'removeDelegatees', + args: [ + fromTokenId, + toTokenIds, + tokenAddress as `0x${string}`, + amounts.map((amount) => BigInt(amount)) + ] }); + + // If simulation succeeds, proceed with the actual transaction + await write( + getContractConfig('removeDelegatees', [ + fromTokenId, + toTokenIds, + tokenAddress as `0x${string}`, + amounts.map((amount) => BigInt(amount)) + ]), + { + successMessage: 'Successfully undelegated voting power', + errorMessage: 'Failed to undelegate voting power' + } + ); + await locks.refetch?.(); return true; } catch (error) { - console.error('Error undelegating position:', error); + console.error('Error in handleUndelegate:', { + error, + params: { + fromTokenId: selectedManagePosition?.id, + toTokenIds, + lpToken: tokenAddress, + amounts + } + }); return false; } } @@ -500,6 +517,7 @@ export function useVeIONManage(chain: number) { handleUnlockPermanent, handleLockPermanent, handleWithdraw, - handleDelegate + handleDelegate, + handleUndelegate }; } diff --git a/packages/ui/lib/utils.ts b/packages/ui/lib/utils.ts index 2819a830d2..bd0c391ddd 100644 --- a/packages/ui/lib/utils.ts +++ b/packages/ui/lib/utils.ts @@ -1,6 +1,6 @@ -import { clsx, type ClassValue } from 'clsx'; -import { twMerge } from 'tailwind-merge'; +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); + return twMerge(clsx(inputs)) } diff --git a/packages/ui/package.json b/packages/ui/package.json index b783c06cf8..d4bf68f3d6 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -17,6 +17,7 @@ "@netlify/plugin-nextjs": "^5.8.1", "@radix-ui/react-alert-dialog": "^1.1.4", "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-hover-card": "^1.1.2", @@ -40,12 +41,12 @@ "@tanstack/react-table": "^8.20.5", "@wagmi/connectors": "^5.3.3", "chart.js": "^4.4.3", - "class-variance-authority": "^0.7.0", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", "graphql": "^16.9.0", "graphql-request": "^7.1.2", - "lucide-react": "^0.454.0", + "lucide-react": "^0.474.0", "mathjs": "^13.2.0", "millify": "^6.1.0", "next": "^15.0.3", @@ -58,7 +59,7 @@ "react-dom": "19.0.0-rc.1", "react-hot-toast": "^2.4.1", "react-loader-spinner": "^6.1.6", - "tailwind-merge": "^2.5.4", + "tailwind-merge": "^3.0.1", "tailwind-scrollbar-hide": "^1.1.7", "tailwindcss-animate": "^1.0.7", "viem": "^2.21.34", diff --git a/yarn.lock b/yarn.lock index e32edc8a33..836e1f2f64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2394,6 +2394,7 @@ __metadata: "@netlify/plugin-nextjs": "npm:^5.8.1" "@radix-ui/react-alert-dialog": "npm:^1.1.4" "@radix-ui/react-checkbox": "npm:^1.1.2" + "@radix-ui/react-collapsible": "npm:^1.1.2" "@radix-ui/react-dialog": "npm:^1.1.2" "@radix-ui/react-dropdown-menu": "npm:^2.1.2" "@radix-ui/react-hover-card": "npm:^1.1.2" @@ -2422,7 +2423,7 @@ __metadata: "@wagmi/connectors": "npm:^5.3.3" autoprefixer: "npm:^10.4.19" chart.js: "npm:^4.4.3" - class-variance-authority: "npm:^0.7.0" + class-variance-authority: "npm:^0.7.1" clsx: "npm:^2.1.1" date-fns: "npm:^4.1.0" eslint: "npm:^8" @@ -2437,7 +2438,7 @@ __metadata: eslint-plugin-unused-imports: "npm:^3.0.0" graphql: "npm:^16.9.0" graphql-request: "npm:^7.1.2" - lucide-react: "npm:^0.454.0" + lucide-react: "npm:^0.474.0" mathjs: "npm:^13.2.0" millify: "npm:^6.1.0" next: "npm:^15.0.3" @@ -2451,7 +2452,7 @@ __metadata: react-dom: "npm:19.0.0-rc.1" react-hot-toast: "npm:^2.4.1" react-loader-spinner: "npm:^6.1.6" - tailwind-merge: "npm:^2.5.4" + tailwind-merge: "npm:^3.0.1" tailwind-scrollbar-hide: "npm:^1.1.7" tailwindcss: "npm:^3.4.4" tailwindcss-animate: "npm:^1.0.7" @@ -5609,6 +5610,32 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-collapsible@npm:^1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-collapsible@npm:1.1.2" + dependencies: + "@radix-ui/primitive": "npm:1.1.1" + "@radix-ui/react-compose-refs": "npm:1.1.1" + "@radix-ui/react-context": "npm:1.1.1" + "@radix-ui/react-id": "npm:1.1.0" + "@radix-ui/react-presence": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.0.1" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + 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 + checksum: 10/623f6d45816548e2ac764839cfac4c2d0189cf8d87079283e2734a2d67503395963239b07f655f835b8e438866fea1791da1515ed31c496ed1ccd2ae60c690aa + languageName: node + linkType: hard + "@radix-ui/react-collection@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-collection@npm:1.1.0" @@ -11642,12 +11669,12 @@ __metadata: languageName: node linkType: hard -"class-variance-authority@npm:^0.7.0": - version: 0.7.0 - resolution: "class-variance-authority@npm:0.7.0" +"class-variance-authority@npm:^0.7.1": + version: 0.7.1 + resolution: "class-variance-authority@npm:0.7.1" dependencies: - clsx: "npm:2.0.0" - checksum: 10/06646e82953e577fb8834100d763f1e5ecb808b8a1fba6244e94b20603865b134ff2296e30432449793baaeb02282cce617afba6981afe18b6846f1f8e9485ca + clsx: "npm:^2.1.1" + checksum: 10/27a1face3f5ee88caa7813743461977aef5110830dc8e8960e7c9570598b37ffb8b2760b4e5d738dc827b0eb9058ddd204e9002e9af0ef39b6851f6ea9aa82c1 languageName: node linkType: hard @@ -11859,13 +11886,6 @@ __metadata: languageName: node linkType: hard -"clsx@npm:2.0.0": - version: 2.0.0 - resolution: "clsx@npm:2.0.0" - checksum: 10/943766d1b02fee3538c871e56638d87f973fbc2d6291ce221215ea436fdecb9be97ad323f411839c2d52c45640c449b1a53fbfe7e8b3d529b4e263308b630c9a - languageName: node - linkType: hard - "clsx@npm:^1.2.1": version: 1.2.1 resolution: "clsx@npm:1.2.1" @@ -18972,6 +18992,15 @@ __metadata: languageName: node linkType: hard +"lucide-react@npm:^0.474.0": + version: 0.474.0 + resolution: "lucide-react@npm:0.474.0" + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/66370cb601cda7f5df78ed11d14b20b7f2dfac128a88a098f6e70ff98e4e8abc63d4ad7b3dc9ecf9814c5578c9d224fc158127d3ee4df770016cc356464011ec + languageName: node + linkType: hard + "luxon@npm:^3.2.1": version: 3.5.0 resolution: "luxon@npm:3.5.0" @@ -25026,10 +25055,10 @@ __metadata: languageName: node linkType: hard -"tailwind-merge@npm:^2.5.4": - version: 2.5.4 - resolution: "tailwind-merge@npm:2.5.4" - checksum: 10/2bf6585a30c0ab2e4e8c4bfe0d0a14edbf8e1d88bb8ce68c79f7185e8a7e410893903a98f5819cf8843f1afbe87639e1989af28456cbb0240ddec3468f303727 +"tailwind-merge@npm:^3.0.1": + version: 3.0.1 + resolution: "tailwind-merge@npm:3.0.1" + checksum: 10/bad61589b78530ce1d49a369b089ff606ed56d7d5aa67f57c04144751abbee3d844652a22595bfa21a4509d0d06946a9bc8c7e9ef74cd5fcf10c51925a2f14af languageName: node linkType: hard