Skip to content

feat(recruiter): add advance candidate confirmation dialog#1405

Merged
Sachinchaurasiya360 merged 1 commit into
Sachinchaurasiya360:mainfrom
YAXH64:feature/advance-candidate-confirmation
Jun 5, 2026
Merged

feat(recruiter): add advance candidate confirmation dialog#1405
Sachinchaurasiya360 merged 1 commit into
Sachinchaurasiya360:mainfrom
YAXH64:feature/advance-candidate-confirmation

Conversation

@YAXH64
Copy link
Copy Markdown
Contributor

@YAXH64 YAXH64 commented Jun 4, 2026

Pull Request

Description

Adds a confirmation step before recruiters can advance candidates to the next hiring stage.

Previously, clicking the Advance button immediately triggered the advancement action, which could lead to accidental candidate progression and incorrect hiring pipeline states. This PR introduces a confirmation dialog that requires explicit user confirmation before the candidate is advanced.

Changes Made

  • Added a confirmation modal before candidate advancement.

  • Introduced pendingAdvanceApp state to manage the selected candidate awaiting confirmation.

  • Refactored the recruiter applications workflow so advancement only occurs after explicit confirmation.

  • Enhanced the reusable ConfirmDialog component:

    • Added loading state support.
    • Added configurable confirm button variants.
    • Added keyboard accessibility (Escape key support).
    • Added support for custom dialog content through children.
  • Prevented duplicate submissions through UI and handler-level guards.

  • Improved error handling by keeping the dialog open when advancement fails, allowing recruiters to retry.

User Impact

Recruiters can no longer accidentally advance candidates with a single misclick. Candidate progression now requires an explicit confirmation step, improving data integrity and hiring workflow reliability.

Related Issue

Fixes #1134

Type of Change

  • Bug Fix
  • Feature
  • Enhancement
  • Documentation

Testing

Manual Testing

  1. Open Recruiter Dashboard.

  2. Navigate to a job's Applications page.

  3. Click Advance on any candidate.

  4. Verify a confirmation dialog appears.

  5. Click Cancel and confirm no status change occurs.

  6. Reopen the dialog and click Confirm Advance.

  7. Verify the candidate advances successfully.

  8. Verify success toast appears.

  9. Simulate an API failure and verify:

    • Error toast appears.
    • Dialog remains open.
    • User can retry.
  10. Rapidly click Confirm and verify only a single request is sent.

  11. Press Escape while the dialog is open and verify it closes correctly (when not loading).

Screenshots / Video

Before

  • Clicking Advance immediately moved the candidate to the next round.

After

  • Clicking Advance opens a confirmation dialog.
  • Candidate advances only after explicit confirmation.

Attach screenshots or a short screen recording demonstrating:

  • Opening the confirmation dialog
  • Cancel flow
  • Successful advance flow

Checklist

  • Code follows project guidelines
  • No new compile/type errors
  • Tested manually (include steps above)
  • No .env, credentials, or node_modules committed
  • Docs updated (not required)
  • Screenshot or video attached for every UI change

Summary by CodeRabbit

  • New Features
    • Enhanced confirmation dialogs with support for custom content and configurable button variants
    • Added visual loading feedback (spinners) and disabled interactions during processing
    • Candidate advancement workflow in recruiter module now requires confirmation before proceeding
    • Improved keyboard accessibility with Escape key support and automatic focus management

@github-actions github-actions Bot added quality:exceptional Exceptional implementation quality scope:frontend Changes to client-side / UI code labels Jun 4, 2026
@github-actions github-actions Bot added bug Something isn't working gssoc level:intermediate Requires moderate project understanding gssoc:approved Approved for GSSoC scoring labels Jun 4, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

Hi @YAXH64, thanks for contributing to InternHack! 🎉

I have automatically:

  • 👤 Assigned this PR to you.
  • 🏷️ Applied the gssoc:approved label.

Our workflows will now analyze your changes to classify:

  • 📈 PR Difficulty: level:*
  • 🧩 PR Type: type:*
  • 🌟 PR Quality: quality:*

Tip

Ensure your PR description references the issue it resolves (e.g. Closes #123). This allows the bot to inherit any additional labels from that issue!

Happy coding! 🚀

@github-actions github-actions Bot added type:design UI/UX or design-related updates type:feature New feature implementation labels Jun 4, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

ConfirmDialog is enhanced to accept optional description, confirmVariant, loading, and custom children props with improved dismiss interactions (focus management, Escape key handling, disabled backdrop during loading). ApplicationsList integrates the enhanced dialog to require confirmation before advancing a candidate, replacing the direct Advance button click with a dialog-gated confirmation flow.

Changes

Application Advancement Confirmation

Layer / File(s) Summary
ConfirmDialog contract and UI updates
client/src/components/ui/ConfirmDialog.tsx
ConfirmDialogProps makes description optional and adds confirmVariant, loading, and children props. Dialog focuses cancel button on open, registers Escape key dismissal (gated by loading), and disables backdrop clicks during loading. UI conditionally renders custom children or fallback description paragraph, disables action buttons during loading, applies variant-based confirm button styling, and displays a Loader2 spinner.
ApplicationsList confirmation integration
client/src/module/recruiter/applications/ApplicationsList.tsx
Imports enhanced ConfirmDialog and introduces pendingAdvanceApp state to track the selected application. Per-row Advance button now opens the dialog instead of directly calling handleAdvance. A rendered ConfirmDialog controls the confirmation flow with cancel and confirm handlers; confirm triggers handleAdvance for the pending application, then clears pendingAdvanceApp to reset dialog state.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AdvanceButton as Row Advance Button
  participant ConfirmDialog
  participant HandleAdvance as handleAdvance()
  participant API as PATCH /advance
  
  User->>AdvanceButton: Click Advance
  AdvanceButton->>ConfirmDialog: Set pendingAdvanceApp
  ConfirmDialog->>ConfirmDialog: Focus cancel, show dialog
  User->>ConfirmDialog: Click Confirm
  ConfirmDialog->>HandleAdvance: Trigger for pending app
  HandleAdvance->>API: PATCH request
  API-->>HandleAdvance: Response
  HandleAdvance->>ConfirmDialog: Clear pendingAdvanceApp
  ConfirmDialog->>User: Close dialog, show toast
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • Sachinchaurasiya360/InternHack#1269: Both modify ApplicationsList around the Advance workflow—this PR adds ConfirmDialog gating, while the linked PR adds in-flight request loading state management for the same action.
  • Sachinchaurasiya360/InternHack#204: Both update ConfirmDialog to gate destructive actions behind confirmation.
  • Sachinchaurasiya360/InternHack#1309: Both modify ApplicationsList advance flow; this PR adds confirmation gating while the linked PR updates the handleAdvance mutation cache invalidation.

Suggested labels

type:feature, scope:frontend, level:intermediate

Suggested reviewers

  • Sachinchaurasiya360

Poem

🐰 A click once hasty, now halts with care,
A dialog whispers, "Are you sure there?"
With focus and spinners, the flow grows wise,
No accidents now shall cloud the skies!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding a confirmation dialog before advancing candidates in the recruiter module.
Description check ✅ Passed The description covers all required template sections: detailed changes, related issue (#1134), type of change, comprehensive manual testing steps, and a complete checklist.
Linked Issues check ✅ Passed The PR fully addresses issue #1134 by implementing a confirmation dialog before candidate advancement, preventing accidental misclicks and data pollution as required.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the confirmation dialog feature: ConfirmDialog component enhancements and ApplicationsList integration. No extraneous modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

🧹 Nitpick comments (3)
client/src/components/ui/ConfirmDialog.tsx (2)

37-46: ⚡ Quick win

Optimize effect dependencies to prevent unnecessary listener re-registration.

The Escape key effect depends on onCancel, which will likely be passed as an inline arrow function by callers (as seen in ApplicationsList.tsx line 88). This causes the event listener to be removed and re-added on every parent render while the dialog is open, even when open and loading haven't changed.

♻️ Proposed fix using useCallback pattern

Document that callers should memoize handlers, or wrap the handler internally:

  // Keyboard navigation & Escape key dismiss
  useEffect(() => {
    if (!open) return;
    const handleKeyDown = (e: KeyboardEvent) => {
-      if (e.key === "Escape" && !loading) {
-        onCancel();
-      }
+      if (e.key === "Escape" && !loading) onCancel();
    };
    document.addEventListener("keydown", handleKeyDown);
    return () => document.removeEventListener("keydown", handleKeyDown);
-  }, [open, loading, onCancel]);
+  }, [open, loading, onCancel]);

Alternatively, add a note in the component documentation that onCancel and onConfirm should be memoized with useCallback in parent components to avoid performance overhead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/components/ui/ConfirmDialog.tsx` around lines 37 - 46, The effect
currently includes onCancel in its dependency array causing the keydown listener
to be re-registered whenever a parent passes an inline handler; fix this by
storing the latest onCancel in a ref (e.g. onCancelRef) and update that ref when
onCancel changes, then have the useEffect that registers handleKeyDown depend
only on open and loading and call onCancelRef.current() inside handleKeyDown;
this keeps the listener stable while still invoking the up-to-date onCancel
handler, referencing useEffect, handleKeyDown, onCancel, open, and loading to
locate the code to change.

64-70: 💤 Low value

Consider validating that content is provided.

Since both description and children are now optional, the component could render an empty <p> tag if a caller mistakenly passes neither. While TypeScript doesn't prevent this, a runtime check or dev-time warning could improve developer experience.

🛡️ Proposed validation
        <div id="confirm-desc" className="mt-2 text-sm text-stone-600 dark:text-stone-400">
          {children ? (
            children
          ) : (
-            <p className="leading-relaxed">
-              {description}
-            </p>
+            <p className="leading-relaxed">
+              {description || "Are you sure you want to proceed?"}
+            </p>
          )}
        </div>

Or add a prop validation/type constraint to require at least one.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/components/ui/ConfirmDialog.tsx` around lines 64 - 70, The
ConfirmDialog component can render an empty <p> when both children and
description are missing; add a validation at the start of the ConfirmDialog
function to detect when both props are falsy and either (a) emit a clear
dev-time console.warn (e.g., "ConfirmDialog requires children or description")
or (b) throw an error in strict mode, and optionally strengthen the prop typing
by changing the props type to a discriminated union requiring at least one of
children or description; reference the ConfirmDialog component and its children
and description props when adding the check.
client/src/module/recruiter/applications/ApplicationsList.tsx (1)

222-227: ⚡ Quick win

Consider migrating to the shared Button component.

The Advance button uses inline classes instead of the reusable Button component from client/src/components/ui/button.tsx. While this button structure pre-dates this PR (only the onClick handler was updated here), it would improve consistency to refactor it to use the shared component pattern.

♻️ Proposed refactor using shared Button

Import the Button component:

+import { Button } from "../../../components/ui/button";

Then replace the inline button:

-                          <button onClick={() => setPendingAdvanceApp(app)}
-                            disabled={isAdvancing}
-                            className={`inline-flex min-w-21.5 items-center justify-center gap-1.5 text-xs px-3 py-1.5 bg-black dark:bg-white text-white dark:text-gray-950 rounded-lg hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors ${isAdvancing ? "cursor-not-allowed opacity-70" : ""}`}>
-                            {isAdvancing && <Loader2 className="h-3 w-3 animate-spin" />}
-                            {isAdvancing ? "Advancing" : "Advance"}
-                          </button>
+                          <Button
+                            onClick={() => setPendingAdvanceApp(app)}
+                            disabled={isAdvancing}
+                            variant="primary"
+                            size="sm"
+                          >
+                            {isAdvancing && <Loader2 className="h-3 w-3 animate-spin" />}
+                            {isAdvancing ? "Advancing" : "Advance"}
+                          </Button>

As per coding guidelines: Use the reusable Button component from client/src/components/ui/button.tsx for all buttons with variants (primary, secondary, mono, ghost, danger), modes (button, icon, link), and sizes (sm, md, lg).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/module/recruiter/applications/ApplicationsList.tsx` around lines
222 - 227, Replace the inline <button> in ApplicationsList with the shared
Button component from client/src/components/ui/button.tsx: import Button, then
call <Button onClick={() => setPendingAdvanceApp(app)} disabled={isAdvancing}
size="sm" variant="primary" ...> and move the existing Loader2 and conditional
text inside the Button so the spinner shows when isAdvancing and the label
toggles between "Advancing" and "Advance"; preserve the onClick handler
(setPendingAdvanceApp), the disabled behavior (isAdvancing) and any extra
className needed for spacing, and remove the old inline classes that are now
handled by the shared Button.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@client/src/components/ui/ConfirmDialog.tsx`:
- Around line 37-46: The effect currently includes onCancel in its dependency
array causing the keydown listener to be re-registered whenever a parent passes
an inline handler; fix this by storing the latest onCancel in a ref (e.g.
onCancelRef) and update that ref when onCancel changes, then have the useEffect
that registers handleKeyDown depend only on open and loading and call
onCancelRef.current() inside handleKeyDown; this keeps the listener stable while
still invoking the up-to-date onCancel handler, referencing useEffect,
handleKeyDown, onCancel, open, and loading to locate the code to change.
- Around line 64-70: The ConfirmDialog component can render an empty <p> when
both children and description are missing; add a validation at the start of the
ConfirmDialog function to detect when both props are falsy and either (a) emit a
clear dev-time console.warn (e.g., "ConfirmDialog requires children or
description") or (b) throw an error in strict mode, and optionally strengthen
the prop typing by changing the props type to a discriminated union requiring at
least one of children or description; reference the ConfirmDialog component and
its children and description props when adding the check.

In `@client/src/module/recruiter/applications/ApplicationsList.tsx`:
- Around line 222-227: Replace the inline <button> in ApplicationsList with the
shared Button component from client/src/components/ui/button.tsx: import Button,
then call <Button onClick={() => setPendingAdvanceApp(app)}
disabled={isAdvancing} size="sm" variant="primary" ...> and move the existing
Loader2 and conditional text inside the Button so the spinner shows when
isAdvancing and the label toggles between "Advancing" and "Advance"; preserve
the onClick handler (setPendingAdvanceApp), the disabled behavior (isAdvancing)
and any extra className needed for spacing, and remove the old inline classes
that are now handled by the shared Button.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 63532fcc-4aee-4be1-b619-443a910da03a

📥 Commits

Reviewing files that changed from the base of the PR and between e15a36c and 9faa5fc.

📒 Files selected for processing (2)
  • client/src/components/ui/ConfirmDialog.tsx
  • client/src/module/recruiter/applications/ApplicationsList.tsx

@Sachinchaurasiya360
Copy link
Copy Markdown
Owner

Code Review — PR #1405: Add advance candidate confirmation dialog

Hi @YAXH64, the confirmation dialog implementation is clean and the warning text in the dialog body is helpful. One coordination note.


🟡 Merge conflict: ConfirmDialog.tsx modified identically in PRs #1405, #1406, #1409, and #1410

All four PRs apply the same diff to ConfirmDialog.tsx. Whichever merges second through fourth will fail to apply cleanly.

Coordinate with @Xenon010101 (PR #1410), who also touches ConfirmDialog.tsx. Merge one PR first, then the others can drop the ConfirmDialog.tsx hunk from their diff during rebase.


✅ Logic is correct

  • setPendingAdvanceApp(app) on the Advance button correctly defers the action until confirmation
  • setPendingAdvanceApp(null) in both onCancel and inside handleAdvance's success path correctly clears the dialog
  • The loading={advancingIds.has(pendingAdvanceApp.id)} state correctly disables the dialog while the request is in flight
  • Guard !advancingIds.has(pendingAdvanceApp.id) in onConfirm prevents double-submission

@Sachinchaurasiya360 Sachinchaurasiya360 merged commit 9faa5fc into Sachinchaurasiya360:main Jun 5, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working gssoc:approved Approved for GSSoC scoring gssoc level:intermediate Requires moderate project understanding quality:exceptional Exceptional implementation quality scope:frontend Changes to client-side / UI code type:design UI/UX or design-related updates type:feature New feature implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[recruiter] No confirmation dialog before advancing application to next round

2 participants