diff --git a/package.json b/package.json index 60c747534..37025473d 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@types/react-dom": "^19.2.1", "@vitejs/plugin-react-swc": "^4.1.0", "eslint": "^9.37.0", - "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-hooks": "^7.0.0", "eslint-plugin-react-refresh": "^0.4.23", "globals": "^16.4.0", "knip": "^5.64.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ade9a488..70a4d3991 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -175,8 +175,8 @@ importers: specifier: ^9.37.0 version: 9.37.0(jiti@2.6.1) eslint-plugin-react-hooks: - specifier: ^5.2.0 - version: 5.2.0(eslint@9.37.0(jiti@2.6.1)) + specifier: ^7.0.0 + version: 7.0.0(eslint@9.37.0(jiti@2.6.1)) eslint-plugin-react-refresh: specifier: ^0.4.23 version: 0.4.23(eslint@9.37.0(jiti@2.6.1)) @@ -2044,9 +2044,9 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-plugin-react-hooks@5.2.0: - resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} - engines: {node: '>=10'} + eslint-plugin-react-hooks@7.0.0: + resolution: {integrity: sha512-fNXaOwvKwq2+pXiRpXc825Vd63+KM4DLL40Rtlycb8m7fYpp6efrTp1sa6ZbP/Ap58K2bEKFXRmhURE+CJAQWw==} + engines: {node: '>=18'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 @@ -2251,6 +2251,12 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2986,6 +2992,12 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -4854,9 +4866,16 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@5.2.0(eslint@9.37.0(jiti@2.6.1)): + eslint-plugin-react-hooks@7.0.0(eslint@9.37.0(jiti@2.6.1)): dependencies: + '@babel/core': 7.28.0 + '@babel/parser': 7.28.0 eslint: 9.37.0(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.1.12 + zod-validation-error: 4.0.2(zod@4.1.12) + transitivePeerDependencies: + - supports-color eslint-plugin-react-refresh@0.4.23(eslint@9.37.0(jiti@2.6.1)): dependencies: @@ -5064,6 +5083,12 @@ snapshots: dependencies: function-bind: 1.1.2 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -5664,6 +5689,10 @@ snapshots: yocto-queue@0.1.0: {} + zod-validation-error@4.0.2(zod@4.1.12): + dependencies: + zod: 4.1.12 + zod@3.25.76: {} zod@4.1.12: {} diff --git a/src/components/data-table/bulk-actions.tsx b/src/components/data-table/bulk-actions.tsx index 85843d324..ea805cfd9 100644 --- a/src/components/data-table/bulk-actions.tsx +++ b/src/components/data-table/bulk-actions.tsx @@ -41,7 +41,11 @@ export function DataTableBulkActions({ useEffect(() => { if (selectedCount > 0) { const message = `${selectedCount} ${entityName}${selectedCount > 1 ? 's' : ''} selected. Bulk actions toolbar is available.` - setAnnouncement(message) + + // Use queueMicrotask to defer state update and avoid cascading renders + queueMicrotask(() => { + setAnnouncement(message) + }) // Clear announcement after a delay const timer = setTimeout(() => setAnnouncement(''), 3000) diff --git a/src/components/long-text.tsx b/src/components/long-text.tsx index ec465d26f..74bf47d20 100644 --- a/src/components/long-text.tsx +++ b/src/components/long-text.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react' +import { useRef, useState } from 'react' import { cn } from '@/lib/utils' import { Popover, @@ -26,18 +26,17 @@ export function LongText({ const ref = useRef(null) const [isOverflown, setIsOverflown] = useState(false) - useEffect(() => { - if (checkOverflow(ref.current)) { - setIsOverflown(true) - return + // Use ref callback to check overflow when element is mounted + const refCallback = (node: HTMLDivElement | null) => { + ref.current = node + if (node && checkOverflow(node)) { + queueMicrotask(() => setIsOverflown(true)) } - - setIsOverflown(false) - }, []) + } if (!isOverflown) return ( -
+
{children}
) @@ -48,7 +47,7 @@ export function LongText({ -
+
{children}
@@ -61,7 +60,7 @@ export function LongText({
-
+
{children}
diff --git a/src/features/auth/sign-in/components/user-auth-form.tsx b/src/features/auth/sign-in/components/user-auth-form.tsx index e3fb831af..0c3406911 100644 --- a/src/features/auth/sign-in/components/user-auth-form.tsx +++ b/src/features/auth/sign-in/components/user-auth-form.tsx @@ -54,19 +54,19 @@ export function UserAuthForm({ function onSubmit(data: z.infer) { setIsLoading(true) - // Mock successful authentication - const mockUser = { - accountNo: 'ACC001', - email: data.email, - role: ['user'], - exp: Date.now() + 24 * 60 * 60 * 1000, // 24 hours from now - } - toast.promise(sleep(2000), { loading: 'Signing in...', success: () => { setIsLoading(false) + // Mock successful authentication with expiry computed at success time + const mockUser = { + accountNo: 'ACC001', + email: data.email, + role: ['user'], + exp: Date.now() + 24 * 60 * 60 * 1000, // 24 hours from now + } + // Set user and access token auth.setUser(mockUser) auth.setAccessToken('mock-access-token') diff --git a/src/features/chats/components/new-chat.tsx b/src/features/chats/components/new-chat.tsx index 8d121b076..0e8a71af5 100644 --- a/src/features/chats/components/new-chat.tsx +++ b/src/features/chats/components/new-chat.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import { Check, X } from 'lucide-react' import { showSubmittedData } from '@/lib/show-submitted-data' import { Badge } from '@/components/ui/badge' @@ -41,14 +41,16 @@ export function NewChat({ users, onOpenChange, open }: NewChatProps) { setSelectedUsers(selectedUsers.filter((user) => user.id !== userId)) } - useEffect(() => { - if (!open) { + const handleOpenChange = (newOpen: boolean) => { + onOpenChange(newOpen) + // Reset selected users when dialog closes + if (!newOpen) { setSelectedUsers([]) } - }, [open]) + } return ( - + New message diff --git a/src/features/dashboard/components/analytics.tsx b/src/features/dashboard/components/analytics.tsx index 794de2c09..87a680a64 100644 --- a/src/features/dashboard/components/analytics.tsx +++ b/src/features/dashboard/components/analytics.tsx @@ -8,47 +8,6 @@ import { import { AnalyticsChart } from './analytics-chart' export function Analytics() { - type Item = { name: string; value: number } - - const SimpleBarList = ({ - items, - valueFormatter, - barClass, - }: { - items: Item[] - valueFormatter: (n: number) => string - barClass: string - }) => { - const max = Math.max(...items.map((i) => i.value), 1) - return ( -
    - {items.map((i) => { - const width = `${Math.round((i.value / max) * 100)}%` - return ( -
  • -
    -
    - {i.name} -
    -
    -
    -
    -
    -
    - {valueFormatter(i.value)} -
    -
  • - ) - })} -
- ) - } return (
@@ -191,3 +150,40 @@ export function Analytics() {
) } + +function SimpleBarList({ + items, + valueFormatter, + barClass, +}: { + items: { name: string; value: number }[] + valueFormatter: (n: number) => string + barClass: string +}) { + const max = Math.max(...items.map((i) => i.value), 1) + return ( +
    + {items.map((i) => { + const width = `${Math.round((i.value / max) * 100)}%` + return ( +
  • +
    +
    + {i.name} +
    +
    +
    +
    +
    +
    + {valueFormatter(i.value)} +
    +
  • + ) + })} +
+ ) +}