Skip to content

Develop#74

Merged
ucswift merged 4 commits intomasterfrom
develop
Jan 20, 2026
Merged

Develop#74
ucswift merged 4 commits intomasterfrom
develop

Conversation

@ucswift
Copy link
Copy Markdown
Member

@ucswift ucswift commented Jan 19, 2026

Summary by CodeRabbit

  • New Features

    • Comprehensive call detail view with tabs, map integration, keyboard shortcuts, modals, and quick actions
    • Call editing interface with validated form, address lookup, map picker, dispatch selection, keyboard shortcuts, and save flow
    • New call creation form with geocoding/address selection, location picker, dispatch recipient selection, validation, and submit flow
  • Style

    • Simplified loading animation timing for consistent dots appearance

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 19, 2026

📝 Walkthrough

Walkthrough

Adds three new web components for Call Detail, Edit Call, and New Call plus a small loading animation change and a runtime dependency addition. Features include maps, geocoding, zod/react-hook-form forms, dispatch selection modals, keyboard shortcuts, and responsive two-column layouts. (49 words)

Changes

Cohort / File(s) Summary
Call Detail (web)
src/app/call/[id].web.tsx
New default-exported CallDetailWeb: loads and renders detailed call data with tabs (info, contact, protocols, dispatched, timeline), map (StaticMap) and routing, modals (notes/images/files), keyboard shortcuts (Esc, Ctrl/Cmd+E, 1–5), action buttons (set active/edit), analytics/error logging, and responsive two-column layout.
Edit Call (web)
src/app/call/[id]/edit.web.tsx
New EditCallWeb: zod + react-hook-form driven edit UI with address lookup (Google Geocoding), full-screen map picker, dispatch recipient selection modal, keyboard shortcuts (Ctrl/Cmd+S, Esc), validation, submission/update flow, toasts, and responsive two-column layout.
New Call (web)
src/app/call/new/index.web.tsx
New default-exported NewCallWeb: creation form with zod validation, geocoding/what3words/plus-code lookups, address selection modal, full-screen map picker, dispatch selection modal, keyboard shortcuts (Ctrl/Cmd+Enter, Esc), submission mapping to API, toasts, and responsive two-column UI.
Loading UI tweak
src/components/common/loading.tsx
Simplified dots loading variant by removing per-dot animationDelay inline style; dots now animate with identical timing.
Dependencies
package.json
Adds runtime dependency dompurify.

Sequence Diagrams

sequenceDiagram
    participant User
    participant FormUI as "New/Edit Call UI"
    participant Validator as "Zod Validation"
    participant Store as "Store / Loader"
    participant API as "Backend API"
    participant Toast as "Toast Service"
    participant Nav as "Navigation"

    User->>FormUI: Fill fields and submit (Ctrl/Cmd+Enter or Ctrl/Cmd+S)
    FormUI->>Validator: Validate form
    alt valid
        FormUI->>Store: Ensure priorities/types loaded
        Store-->>FormUI: priorities/types
        FormUI->>API: createCall / updateCall (payload)
        alt API success
            API-->>FormUI: success response
            FormUI->>Toast: show success
            FormUI->>Nav: redirect to calls list / detail
        else API error
            API-->>FormUI: error
            FormUI->>Toast: show error
        end
    else invalid
        Validator-->>FormUI: validation errors
        FormUI->>Toast: display validation errors
    end
Loading
sequenceDiagram
    participant User
    participant DetailUI as "Call Detail UI"
    participant Store as "Store / Loader"
    participant Maps as "Map Service (StaticMap / External)"
    participant Modal as "Modal Service"

    User->>DetailUI: Open call detail page
    DetailUI->>Store: fetch call data
    Store-->>DetailUI: call data + metadata
    DetailUI->>DetailUI: render tabs & map
    User->>DetailUI: request directions / open map
    DetailUI->>Maps: render StaticMap / open external maps link
    Maps-->>DetailUI: map rendered / link opened
    User->>DetailUI: open notes/images/files
    DetailUI->>Modal: open corresponding modal
    Modal-->>DetailUI: modal result (close/update)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 I hopped through props and polygons bright,
Forms and maps stitched neat in the night.
Modals unfurled, validations true,
New calls, edits, details — a carrot or two.
Code nibbled, now ready — hop to deploy delight!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Develop" is vague and generic, failing to describe any specific changes in the pull request. Revise the title to clearly summarize the main changes, such as 'Add web-based call management interfaces' or 'Implement call detail and editing views for web platform'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@src/app/call/`[id].web.tsx:
- Around line 226-228: The code renders user-controlled HTML via
dangerouslySetInnerHTML using call.Note in three places (the div inside the Note
View and the other two occurrences), creating an XSS risk; fix by installing and
importing DOMPurify, sanitize call.Note (e.g., const safeNote =
DOMPurify.sanitize(call.Note || '<em>No notes</em>')) before passing it to
dangerouslySetInnerHTML (or alternatively render as plain text if HTML is not
required), and apply the same sanitization for the other two usages to ensure
all call.Note outputs are cleaned (update imports and replace raw call.Note with
the sanitized variable wherever dangerouslySetInnerHTML is used).
- Around line 128-147: The keyboard shortcut useEffect registers handleKeyDown
which closes over handleEditCall, router, setActiveTab and TabKey but has an
empty dependency array causing stale closures; update the effect to include its
dependencies (at minimum handleEditCall, router, setActiveTab) and ensure
handleEditCall is memoized with useCallback (so it updates when callId or
routing context changes) and then reference the memoized handleEditCall inside
useEffect so Ctrl+E navigates to the correct edit page and the tab number
handling via setActiveTab remains fresh.

In `@src/app/call/`[id]/edit.web.tsx:
- Around line 296-318: The useEffect that registers keyboard shortcuts
references handleSubmit and onSubmit but doesn't declare them as dependencies;
wrap your onSubmit handler in useCallback (including any state/props it uses)
and then add both onSubmit and handleSubmit to the useEffect dependency array so
the effect updates when the submit handler changes; ensure the existing state
setters (setShowLocationPicker, setShowAddressSelection, setShowDispatchModal)
and router usage remain correct and that handleKeyDown is referencing the
memoized onSubmit inside the effect.
- Around line 332-333: The code is using a non-null assertion on callId when
invoking useCallDetailStore.getState().updateCall (callId!), which can be
undefined because id from useLocalSearchParams may be missing; add a guard
clause that checks the callId (or id) is present before calling updateCall and
bail out (return or show error) if it’s undefined so you never call updateCall
with an undefined callId; locate the call to
useCallDetailStore.getState().updateCall and the variable that holds the id
(from useLocalSearchParams) and wrap the updateCall invocation in an if
(!callId) { /* handle missing id */ } block.

In `@src/app/call/new/index.web.tsx`:
- Around line 282-306: The keyboard shortcut effect is missing dependencies
which can cause stale closures; update the dependency array for the useEffect
that defines handleKeyDown to include handleSubmit and onSubmit (and router if
router.back is used inside) in addition to showLocationPicker,
showAddressSelection, and showDispatchModal so the effect re-registers when
those handlers change; locate the useEffect block in index.web.tsx that
references handleKeyDown/handleSubmit/onSubmit/router.back and add handleSubmit,
onSubmit (and router) to the dependencies.
🧹 Nitpick comments (6)
src/components/common/loading.tsx (1)

53-56: Consider restoring staggered animation for the loading dots.

Removing the animationDelay style causes all three dots to pulse simultaneously rather than in a wave pattern. Staggered animation typically provides better visual feedback that something is loading.

💡 Suggested staggered animation
 {[1, 2, 3].map((i) => (
-  <Box key={i} className={`bg-primary rounded-full ${size === 'small' ? 'size-2' : size === 'large' ? 'size-3' : 'size-4'} animate-pulse`} />
+  <Box 
+    key={i} 
+    className={`bg-primary rounded-full ${size === 'small' ? 'size-2' : size === 'large' ? 'size-3' : 'size-4'} animate-pulse`}
+    style={{ animationDelay: `${(i - 1) * 150}ms` }}
+  />
 ))}
src/app/call/[id].web.tsx (2)

107-116: Add validation for parsed coordinates.

parseFloat returns NaN for malformed input, and the Geolocation string split doesn't trim whitespace. Invalid coordinates could cause map rendering issues.

🛡️ Defensive parsing
   useEffect(() => {
     if (call) {
       if (call.Latitude && call.Longitude) {
-        setCoordinates({ latitude: parseFloat(call.Latitude), longitude: parseFloat(call.Longitude) });
+        const lat = parseFloat(call.Latitude);
+        const lng = parseFloat(call.Longitude);
+        if (!isNaN(lat) && !isNaN(lng) && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
+          setCoordinates({ latitude: lat, longitude: lng });
+        }
       } else if (call.Geolocation) {
-        const [lat, lng] = call.Geolocation.split(',');
-        setCoordinates({ latitude: parseFloat(lat), longitude: parseFloat(lng) });
+        const parts = call.Geolocation.split(',').map(s => s.trim());
+        if (parts.length === 2) {
+          const lat = parseFloat(parts[0]);
+          const lng = parseFloat(parts[1]);
+          if (!isNaN(lat) && !isNaN(lng) && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
+            setCoordinates({ latitude: lat, longitude: lng });
+          }
+        }
       }
     }
   }, [call]);

42-42: Unused height variable.

height is destructured from useWindowDimensions() but never used.

-const { width, height } = useWindowDimensions();
+const { width } = useWindowDimensions();
src/app/call/[id]/edit.web.tsx (1)

63-131: Extract shared WebInput component to avoid duplication.

WebInput is nearly identical in edit.web.tsx and new/index.web.tsx. Extract it to a shared component file to improve maintainability.

Consider creating src/components/common/web-input.tsx:

// src/components/common/web-input.tsx
export const WebInput: React.FC<WebInputProps> = ({ ... }) => { ... };
export const WebSelect: React.FC<WebSelectProps> = ({ ... }) => { ... };

Then import in both files:

import { WebInput, WebSelect } from '@/components/common/web-input';
src/app/call/new/index.web.tsx (2)

228-228: Remove unused nameInputRef.

nameInputRef is declared but never used in the component.

-const nameInputRef = useRef<HTMLInputElement>(null);

18-23: Remove unused imports.

Button, ButtonText, HStack, and VStack are imported but not used in the component.

-import { Button, ButtonText } from '@/components/ui/button';
 import { Card } from '@/components/ui/card';
 import { FocusAwareStatusBar } from '@/components/ui/focus-aware-status-bar';
-import { HStack } from '@/components/ui/hstack';
 import { Text } from '@/components/ui/text';
-import { VStack } from '@/components/ui/vstack';

Comment thread src/app/call/[id].web.tsx Outdated
Comment thread src/app/call/[id].web.tsx
Comment on lines +296 to +318
// Keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
handleSubmit(onSubmit)();
}
if (e.key === 'Escape') {
if (showLocationPicker) {
setShowLocationPicker(false);
} else if (showAddressSelection) {
setShowAddressSelection(false);
} else if (showDispatchModal) {
setShowDispatchModal(false);
} else {
router.back();
}
}
};

window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [showLocationPicker, showAddressSelection, showDispatchModal]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing dependencies in keyboard shortcut useEffect.

handleSubmit and onSubmit are used in the effect but not listed as dependencies. While react-hook-form's handleSubmit is typically stable, onSubmit captures local state.

🔧 Add missing dependencies
   useEffect(() => {
     const handleKeyDown = (e: KeyboardEvent) => {
       if ((e.ctrlKey || e.metaKey) && e.key === 's') {
         e.preventDefault();
         handleSubmit(onSubmit)();
       }
       // ... rest of handler
     };

     window.addEventListener('keydown', handleKeyDown);
     return () => window.removeEventListener('keydown', handleKeyDown);
-  }, [showLocationPicker, showAddressSelection, showDispatchModal]);
+  }, [showLocationPicker, showAddressSelection, showDispatchModal, handleSubmit, onSubmit]);

Wrap onSubmit in useCallback with appropriate dependencies.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
handleSubmit(onSubmit)();
}
if (e.key === 'Escape') {
if (showLocationPicker) {
setShowLocationPicker(false);
} else if (showAddressSelection) {
setShowAddressSelection(false);
} else if (showDispatchModal) {
setShowDispatchModal(false);
} else {
router.back();
}
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [showLocationPicker, showAddressSelection, showDispatchModal]);
// Keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
handleSubmit(onSubmit)();
}
if (e.key === 'Escape') {
if (showLocationPicker) {
setShowLocationPicker(false);
} else if (showAddressSelection) {
setShowAddressSelection(false);
} else if (showDispatchModal) {
setShowDispatchModal(false);
} else {
router.back();
}
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [showLocationPicker, showAddressSelection, showDispatchModal, handleSubmit, onSubmit]);
🤖 Prompt for AI Agents
In `@src/app/call/`[id]/edit.web.tsx around lines 296 - 318, The useEffect that
registers keyboard shortcuts references handleSubmit and onSubmit but doesn't
declare them as dependencies; wrap your onSubmit handler in useCallback
(including any state/props it uses) and then add both onSubmit and handleSubmit
to the useEffect dependency array so the effect updates when the submit handler
changes; ensure the existing state setters (setShowLocationPicker,
setShowAddressSelection, setShowDispatchModal) and router usage remain correct
and that handleKeyDown is referencing the memoized onSubmit inside the effect.

Comment thread src/app/call/[id]/edit.web.tsx Outdated
Comment thread src/app/call/new/index.web.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@package.json`:
- Line 114: Remove the deprecated `@types/dompurify` entry from devDependencies in
package.json and rely on the built-in TypeScript types shipped by the dompurify
package (keep "dompurify": "^3.3.1"); after removing the `@types` package, run
your package manager install to update lockfiles, and adjust any imports/type
references (if present) to import types directly from "dompurify" (e.g., replace
any use of `@types/dompurify` with DOMPurify types from the dompurify package).

In `@src/app/call/new/index.web.tsx`:
- Around line 282-308: The onSubmit function is referenced in the useEffect
dependency array before it is declared; make onSubmit a stable callback and
define it before the useEffect: convert the current const onSubmit = async
(data: FormValues) => { ... } into a useCallback hook (e.g., const onSubmit =
useCallback(async (data: FormValues) => { ... }, [/* include real dependencies
used inside onSubmit */])) and move that declaration above the useEffect that
registers handleKeyDown; then update the useEffect dependency array to include
this new onSubmit callback (and ensure handleSubmit is still included). Also
verify any values used inside onSubmit are listed in its dependency array to
avoid stale closures.
♻️ Duplicate comments (4)
src/app/call/[id].web.tsx (2)

228-229: XSS protection properly implemented with DOMPurify.

The previous review flagged XSS vulnerabilities with dangerouslySetInnerHTML. This has been properly addressed by using DOMPurify.sanitize() for all HTML content rendering (call.Note, protocol.ProtocolText, call.Nature).

Also applies to: 251-251, 339-339


129-148: Keyboard shortcut dependencies properly fixed.

The previous review flagged missing dependencies in the keyboard shortcut useEffect. This has been addressed by:

  1. Wrapping handleEditCall in useCallback (line 78)
  2. Including handleEditCall, router, and setActiveTab in the dependency array
src/app/call/[id]/edit.web.tsx (2)

320-331: Guard clause for missing callId properly implemented.

The previous review flagged the non-null assertion on callId. This has been addressed with a proper guard clause that shows an error toast and returns early if callId is undefined.


296-318: Keyboard shortcut useEffect still missing some dependencies.

The previous review flagged that handleSubmit and onSubmit should be in the dependency array. While the modal state dependencies were added, handleSubmit and onSubmit are still missing.

Since onSubmit captures form state through the data parameter and uses external state like selectedLocation, this could lead to stale closures in edge cases.

♻️ Suggested fix
+  const onSubmit = useCallback(async (data: FormValues) => {
+    // ... existing implementation
+  }, [callId, callPriorities, callTypes, selectedLocation, toast, t, router]);

   // Keyboard shortcuts
   useEffect(() => {
     const handleKeyDown = (e: KeyboardEvent) => {
       if ((e.ctrlKey || e.metaKey) && e.key === 's') {
         e.preventDefault();
         handleSubmit(onSubmit)();
       }
       // ...
     };

     window.addEventListener('keydown', handleKeyDown);
     return () => window.removeEventListener('keydown', handleKeyDown);
-  }, [showLocationPicker, showAddressSelection, showDispatchModal]);
+  }, [showLocationPicker, showAddressSelection, showDispatchModal, handleSubmit, onSubmit]);
🧹 Nitpick comments (3)
src/app/call/[id]/edit.web.tsx (1)

341-349: Consider handling unmatched priority/type more explicitly.

When mapping priority/type names back to IDs, the code uses fallback values (0 for priority, '' for type) if the lookup fails. This could result in submitting invalid data if the lookup fails unexpectedly (e.g., stale store data).

💡 Optional improvement
   const priority = callPriorities.find((p) => p.Name === data.priority);
   const type = callTypes.find((t) => t.Name === data.type);

+  if (!priority || !type) {
+    toast.show({
+      placement: 'top',
+      render: () => (
+        <Box className="rounded-lg bg-red-500 p-4 shadow-lg">
+          <Text className="text-white">{t('call_detail.invalid_priority_or_type')}</Text>
+        </Box>
+      ),
+    });
+    return;
+  }

   await useCallDetailStore.getState().updateCall({
     callId: callId,
     // ...
-    priority: priority?.Id || 0,
-    type: type?.Id || '',
+    priority: priority.Id,
+    type: type.Id,
     // ...
   });
src/app/call/new/index.web.tsx (2)

439-474: What3words search validation and transformation could be improved.

The what3words validation (line 446) checks against lowercase, but if the check passes, the original what3words value is sent to the API without transformation. This could fail if the user entered mixed case that happened to pass the test.

💡 Minor improvement
   const handleWhat3WordsSearch = async (what3words: string) => {
     if (!what3words.trim()) {
       toast.warning(t('calls.what3words_required'));
       return;
     }

-    const w3wRegex = /^[a-z]+\.[a-z]+\.[a-z]+$/;
-    if (!w3wRegex.test(what3words.trim().toLowerCase())) {
+    const normalizedW3w = what3words.trim().toLowerCase();
+    const w3wRegex = /^[a-z]+\.[a-z]+\.[a-z]+$/;
+    if (!w3wRegex.test(normalizedW3w)) {
       toast.warning(t('calls.what3words_invalid_format'));
       return;
     }

     setIsGeocodingWhat3Words(true);
     try {
       const apiKey = config?.W3WKey;
       if (!apiKey) throw new Error('what3words API key not configured');

-      const response = await axios.get<What3WordsResponse>(`https://api.what3words.com/v3/convert-to-coordinates?words=${encodeURIComponent(what3words)}&key=${apiKey}`);
+      const response = await axios.get<What3WordsResponse>(`https://api.what3words.com/v3/convert-to-coordinates?words=${encodeURIComponent(normalizedW3w)}&key=${apiKey}`);

89-195: Consider extracting shared WebInput and WebSelect components.

The WebInput and WebSelect components are nearly identical to those in edit.web.tsx. Consider extracting these into a shared component file (e.g., @/components/ui/web-input.tsx) to reduce duplication and ensure consistency.

Comment thread package.json
Comment on lines +282 to +308
// Keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Ctrl/Cmd + Enter to submit
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
handleSubmit(onSubmit)();
}
// Escape to cancel
if (e.key === 'Escape') {
if (showLocationPicker) {
setShowLocationPicker(false);
} else if (showAddressSelection) {
setShowAddressSelection(false);
} else if (showDispatchModal) {
setShowDispatchModal(false);
} else {
router.back();
}
}
};

window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [showLocationPicker, showAddressSelection, showDispatchModal, handleSubmit, onSubmit, router]);

const onSubmit = async (data: FormValues) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: onSubmit is referenced before declaration.

The static analysis correctly identifies that onSubmit is used in the dependency array (line 306) before it's declared (line 308). Function expressions assigned to const are not hoisted, so this will cause issues.

🐛 Proposed fix - wrap onSubmit in useCallback and move before useEffect
+  const onSubmit = useCallback(async (data: FormValues) => {
+    try {
+      setIsSubmitting(true);
+
+      if (selectedLocation?.latitude && selectedLocation?.longitude) {
+        data.latitude = selectedLocation.latitude;
+        data.longitude = selectedLocation.longitude;
+      }
+
+      const priority = callPriorities.find((p) => p.Name === data.priority);
+      const type = callTypes.find((t) => t.Name === data.type);
+
+      if (!priority) {
+        toast.error(t('calls.invalid_priority'));
+        return;
+      }
+
+      if (!type) {
+        toast.error(t('calls.invalid_type'));
+        return;
+      }
+
+      await createCall({
+        name: data.name,
+        nature: data.nature,
+        priority: priority.Id,
+        type: type.Id,
+        note: data.note,
+        address: data.address,
+        latitude: data.latitude,
+        longitude: data.longitude,
+        what3words: data.what3words,
+        plusCode: data.plusCode,
+        dispatchUsers: data.dispatchSelection?.users,
+        dispatchGroups: data.dispatchSelection?.groups,
+        dispatchRoles: data.dispatchSelection?.roles,
+        dispatchUnits: data.dispatchSelection?.units,
+        dispatchEveryone: data.dispatchSelection?.everyone,
+      });
+
+      toast.success(t('calls.create_success'));
+      router.push('/calls' as Href);
+    } catch (err) {
+      console.error('Error creating call:', err);
+      toast.error(t('calls.create_error'));
+    } finally {
+      setIsSubmitting(false);
+    }
+  }, [selectedLocation, callPriorities, callTypes, toast, t, router]);

   // Keyboard shortcuts
   useEffect(() => {
     const handleKeyDown = (e: KeyboardEvent) => {
       // Ctrl/Cmd + Enter to submit
       if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
         e.preventDefault();
         handleSubmit(onSubmit)();
       }
       // ...
     };

     window.addEventListener('keydown', handleKeyDown);
     return () => window.removeEventListener('keydown', handleKeyDown);
   }, [showLocationPicker, showAddressSelection, showDispatchModal, handleSubmit, onSubmit, router]);

-  const onSubmit = async (data: FormValues) => {
-    // ... move this above the useEffect
-  };
🧰 Tools
🪛 Biome (2.1.2)

[error] 306-306: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

🤖 Prompt for AI Agents
In `@src/app/call/new/index.web.tsx` around lines 282 - 308, The onSubmit function
is referenced in the useEffect dependency array before it is declared; make
onSubmit a stable callback and define it before the useEffect: convert the
current const onSubmit = async (data: FormValues) => { ... } into a useCallback
hook (e.g., const onSubmit = useCallback(async (data: FormValues) => { ... },
[/* include real dependencies used inside onSubmit */])) and move that
declaration above the useEffect that registers handleKeyDown; then update the
useEffect dependency array to include this new onSubmit callback (and ensure
handleSubmit is still included). Also verify any values used inside onSubmit are
listed in its dependency array to avoid stale closures.

@ucswift
Copy link
Copy Markdown
Member Author

ucswift commented Jan 20, 2026

Approve

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is approved.

@ucswift ucswift merged commit 64e3e0c into master Jan 20, 2026
8 of 10 checks passed
This was referenced Jan 20, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Mar 12, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant