feat: implement search functionality and enhance job filtering in applications dashboard#47
Conversation
…lications dashboard
|
🚅 Deployed to the applirank-pr-47 environment in applirank
|
📝 WalkthroughWalkthroughThe applications dashboard page is redesigned from a table to a card-based layout with a single active status filter, debounced client-side search, job dropdown filter, and consolidated sorting options. Server-side utilities update Better Auth URL/trusted origins resolution and environment parsing; swipe view guards current application binding with a resolved ID ref. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/pages/dashboard/applications/index.vue`:
- Around line 21-27: debounceTimer is declared uninitialized and cleared
unguarded which can break TS and leave timeouts after unmount; change its
declaration to include undefined (e.g. let debounceTimer: ReturnType<typeof
setTimeout> | undefined = undefined), guard clearTimeout calls (if
(debounceTimer) clearTimeout(debounceTimer)), ensure assignments set
debounceTimer to the setTimeout return, and add an onBeforeUnmount(() => { if
(debounceTimer) clearTimeout(debounceTimer) }) to clean up the pending timeout
when the component is destroyed; references: debounceTimer variable and the
watch(...) callback.
| let debounceTimer: ReturnType<typeof setTimeout> | ||
| watch(searchInput, (val) => { | ||
| clearTimeout(debounceTimer) | ||
| debounceTimer = setTimeout(() => { | ||
| debouncedSearch.value = val.trim().toLowerCase() | ||
| }, 250) | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check the debounce timer initialization and usage around lines 20-32
echo "=== Lines 20-32 (debounce timer section) ==="
nl -ba app/pages/dashboard/applications/index.vue | sed -n '20,32p'
echo ""
echo "=== Lines 58-66 (mentioned as 'Also applies to: 61-63') ==="
nl -ba app/pages/dashboard/applications/index.vue | sed -n '58,66p'
echo ""
echo "=== Checking for onUnmounted imports and usage ==="
rg "onUnmounted" app/pages/dashboard/applications/index.vueRepository: reqcore-inc/applirank
Length of output: 169
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check the debounce timer initialization and usage around lines 20-32
echo "=== Lines 20-32 (debounce timer section) ==="
sed -n '20,32p' app/pages/dashboard/applications/index.vue | cat -n
echo ""
echo "=== Lines 58-66 (mentioned as 'Also applies to: 61-63') ==="
sed -n '58,66p' app/pages/dashboard/applications/index.vue | cat -n
echo ""
echo "=== Checking for onUnmounted imports and usage ==="
rg "onUnmounted" app/pages/dashboard/applications/index.vue
echo ""
echo "=== Checking for cleanup on unmount ==="
rg -A 5 "onUnmounted|onMounted" app/pages/dashboard/applications/index.vueRepository: reqcore-inc/applirank
Length of output: 2166
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== All onUnmounted handlers in the file ==="
rg -B 2 -A 3 "onUnmounted" app/pages/dashboard/applications/index.vue
echo ""
echo "=== Total occurrences of onUnmounted ==="
rg -c "onUnmounted" app/pages/dashboard/applications/index.vue
echo ""
echo "=== Check if debounceTimer is mentioned anywhere else ==="
rg "debounceTimer" app/pages/dashboard/applications/index.vueRepository: reqcore-inc/applirank
Length of output: 843
Initialize and clean up the debounce timer defensively.
Line 21 declares debounceTimer without initialization, and Line 23 calls clearTimeout(debounceTimer) unguarded. Additionally, there is no timeout cleanup on component unmount, risking stale callbacks after the component is destroyed. This breaks strict TypeScript builds and can cause runtime bugs.
🛠️ Proposed fix
-let debounceTimer: ReturnType<typeof setTimeout>
+let debounceTimer: ReturnType<typeof setTimeout> | undefined
watch(searchInput, (val) => {
- clearTimeout(debounceTimer)
+ if (debounceTimer) clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => {
debouncedSearch.value = val.trim().toLowerCase()
}, 250)
})
+
+onUnmounted(() => {
+ if (debounceTimer) clearTimeout(debounceTimer)
+})📝 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.
| let debounceTimer: ReturnType<typeof setTimeout> | |
| watch(searchInput, (val) => { | |
| clearTimeout(debounceTimer) | |
| debounceTimer = setTimeout(() => { | |
| debouncedSearch.value = val.trim().toLowerCase() | |
| }, 250) | |
| }) | |
| let debounceTimer: ReturnType<typeof setTimeout> | undefined | |
| watch(searchInput, (val) => { | |
| if (debounceTimer) clearTimeout(debounceTimer) | |
| debounceTimer = setTimeout(() => { | |
| debouncedSearch.value = val.trim().toLowerCase() | |
| }, 250) | |
| }) | |
| onUnmounted(() => { | |
| if (debounceTimer) clearTimeout(debounceTimer) | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/pages/dashboard/applications/index.vue` around lines 21 - 27,
debounceTimer is declared uninitialized and cleared unguarded which can break TS
and leave timeouts after unmount; change its declaration to include undefined
(e.g. let debounceTimer: ReturnType<typeof setTimeout> | undefined = undefined),
guard clearTimeout calls (if (debounceTimer) clearTimeout(debounceTimer)),
ensure assignments set debounceTimer to the setTimeout return, and add an
onBeforeUnmount(() => { if (debounceTimer) clearTimeout(debounceTimer) }) to
clean up the pending timeout when the component is destroyed; references:
debounceTimer variable and the watch(...) callback.
| <input | ||
| v-model="searchInput" | ||
| type="text" | ||
| placeholder="Search by candidate name, email, or job title…" | ||
| class="w-full rounded-lg border border-surface-200 dark:border-surface-800 bg-white dark:bg-surface-900 pl-10 pr-3 py-2.5 text-sm text-surface-900 dark:text-surface-100 placeholder:text-surface-400 focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-brand-500 transition-colors" | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
Add accessibility semantics to search and dropdown controls.
The new controls are keyboard-clickable, but they’re missing key a11y semantics (programmatic label on search; expanded/menu relationships on custom dropdowns), which hurts screen-reader navigation.
♿ Proposed fix
+ <label for="applications-search" class="sr-only">Search applications</label>
<input
+ id="applications-search"
v-model="searchInput"
type="text"
placeholder="Search by candidate name, email, or job title…"
+ aria-label="Search applications"
class="w-full rounded-lg border border-surface-200 dark:border-surface-800 bg-white dark:bg-surface-900 pl-10 pr-3 py-2.5 text-sm text-surface-900 dark:text-surface-100 placeholder:text-surface-400 focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-brand-500 transition-colors"
/> <button
+ type="button"
+ aria-haspopup="menu"
+ :aria-expanded="jobDropdownOpen"
+ aria-controls="job-filter-menu"
class="inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm transition-colors"
:class="activeJobId
...
<div
v-if="jobDropdownOpen"
+ id="job-filter-menu"
+ role="menu"
class="absolute left-0 top-full mt-1 z-20 w-64 max-h-56 overflow-y-auto rounded-lg border border-surface-200 dark:border-surface-800 bg-white dark:bg-surface-900 shadow-lg py-1"
> <button
+ type="button"
+ aria-haspopup="menu"
+ :aria-expanded="sortDropdownOpen"
+ aria-controls="sort-menu"
class="inline-flex items-center gap-1.5 text-xs text-surface-500 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-200 transition-colors"
`@click`="sortDropdownOpen = !sortDropdownOpen"
>
...
<div
v-if="sortDropdownOpen"
+ id="sort-menu"
+ role="menu"
class="absolute right-0 top-full mt-1 z-20 w-48 rounded-lg border border-surface-200 dark:border-surface-800 bg-white dark:bg-surface-900 shadow-lg py-1"
>Also applies to: 235-333
…d improve application detail fetching logic
…ecks and enhance origin resolution logic
There was a problem hiding this comment.
🧹 Nitpick comments (1)
server/utils/env.ts (1)
43-47: Consider simplifying the schema chain.The
.pipe(z.string())is redundant sinceemptyToUndefinedalready validates asz.string()internally. Additionally, when using.default([]), the preceding.optional()is superfluous since.default()already handles undefined values.♻️ Proposed simplification
BETTER_AUTH_TRUSTED_ORIGINS: emptyToUndefined - .pipe(z.string()) .transform(value => value.split(',').map(origin => origin.trim()).filter(Boolean)) - .optional() .default([]),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/utils/env.ts` around lines 43 - 47, Remove the redundant .pipe(z.string()) and the unnecessary .optional() in the BETTER_AUTH_TRUSTED_ORIGINS schema chain: keep emptyToUndefined, apply .transform(value => value.split(',').map(origin => origin.trim()).filter(Boolean)) and then .default([]); ensure the transform still expects a string input from emptyToUndefined and that the final schema symbol is BETTER_AUTH_TRUSTED_ORIGINS.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@server/utils/env.ts`:
- Around line 43-47: Remove the redundant .pipe(z.string()) and the unnecessary
.optional() in the BETTER_AUTH_TRUSTED_ORIGINS schema chain: keep
emptyToUndefined, apply .transform(value => value.split(',').map(origin =>
origin.trim()).filter(Boolean)) and then .default([]); ensure the transform
still expects a string input from emptyToUndefined and that the final schema
symbol is BETTER_AUTH_TRUSTED_ORIGINS.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
ARCHITECTURE.mdapp/pages/dashboard/jobs/[id]/swipe.vueserver/utils/auth.tsserver/utils/env.ts
✅ Files skipped from review due to trivial changes (1)
- ARCHITECTURE.md
Summary
Type of change
Validation
DCO
Signed-off-by) viagit commit -sSummary by CodeRabbit
New Features
Style
Documentation