Skip to content

Conversation

@krisxcrash
Copy link
Contributor

@krisxcrash krisxcrash commented Jan 8, 2026

Overview

SDK-194
Implements business rule guards to prevent cancelling payrolls when the cancellation window has expired. Users can now only cancel processed payrolls before 4:00 PM PT on the payroll deadline date.

Problem

Previously, the cancel payroll UI was always available for processed payrolls regardless of timing, and the cutoff time was incorrectly set to 3:30 PM PT instead of 4:00 PM PT. This could allow users to attempt cancellations that would fail at the API level.

Solution

  • Added centralized canCancelPayroll() helper function that enforces business rules
  • Updated both PayrollOverview and PayrollHistory components to conditionally render cancel UI
  • Fixed cutoff time from 3:30 PM to 4:00 PM PT
  • Properly handles DST transitions for Pacific Time

Business Rules Enforced

A payroll can only be cancelled when ALL of the following are true:

  • ✅ Payroll has been processed (processed === true)
  • ✅ Current time is before 4:00 PM PT on the payroll_deadline date
  • payrollStatusMeta.cancellable is not explicitly false

Changes Made

Core Logic

  • src/components/Payroll/helpers.ts
    • Added getPacificTimeOffset() - Handles DST transitions
    • Added canCancelPayroll() - Centralized cancellation eligibility logic
    • Both functions properly handle Pacific Time zone conversions

UI Components

  • src/components/Payroll/PayrollHistory/PayrollHistoryPresentation.tsx

    • Removed duplicate local implementation
    • Now uses shared canCancelPayroll() helper
    • Cancel menu option only renders when cancellation is allowed
  • src/components/Payroll/PayrollOverview/PayrollOverview.tsx

    • Computes canCancel using shared helper
    • Passes flag to presentation component
  • src/components/Payroll/PayrollOverview/PayrollOverviewPresentation.tsx

    • Added canCancel prop
    • Cancel button conditionally renders based on eligibility

Tests

  • src/components/Payroll/helpers.test.ts

    • Added 10 comprehensive test cases covering:
      • Processed vs unprocessed payrolls
      • Before/after 4 PM PT cutoff
      • DST transitions (PDT and PST)
      • Edge cases (exactly at cutoff, missing fields)
  • src/components/Payroll/PayrollHistory/PayrollHistory.test.tsx

    • Updated 3 tests to mock system time before cutoff
    • Updated fixture to use processed payroll
  • src/test/mocks/fixtures/payroll-history-unprocessed-test-data.json

    • Changed processed: falseprocessed: true to match requirements

Testing

  • ✅ All 73 helper function tests passing
  • ✅ All 23 PayrollHistory tests passing
  • ✅ All 16 PayrollOverview tests passing
  • ✅ Zero linter errors
  • ✅ Manually verified DST transitions work correctly

Acceptance Criteria Met

  • User can only cancel payroll if processed AND before 4 PM PT deadline
  • Cancel option not shown in PayrollHistory hamburger menu when ineligible
  • Cancel button not shown in PayrollOverview when ineligible

Technical Notes

  • Uses setUTCHours() for proper timezone handling
  • Follows existing repository patterns (helpers in component directory)
  • DRY principle - eliminated code duplication
  • Comprehensive JSDoc documentation added

Example Behavior

Before 4 PM PT on deadline:

  • ✅ Cancel button visible
  • ✅ Cancel menu option visible

After 4 PM PT on deadline:

  • ❌ Cancel button hidden
  • ❌ Cancel menu option hidden

Unprocessed payroll:

  • ❌ Cancel button hidden
  • ❌ Cancel menu option hidden

@krisxcrash krisxcrash changed the title feat: cancel payroll - WIP feat: add payroll cancellation guards Jan 8, 2026
@krisxcrash krisxcrash marked this pull request as ready for review January 8, 2026 19:26
const now = new Date()
const deadline = new Date(payroll.payrollDeadline)

const nowInPT = new Date(now.getTime() + getPacificTimeOffset(now) * 60 * 60 * 1000)
Copy link
Member

Choose a reason for hiding this comment

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

Nit: i wonder if we could export this constant from dateFormatting and use that instead of hard coding these numbers here

const MS_PER_HOUR = 1000 * 60 * 60

Copy link
Contributor Author

Choose a reason for hiding this comment

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

great idea! I will update that.

@krisxcrash krisxcrash enabled auto-merge (squash) January 8, 2026 22:39
@dryrunsecurity
Copy link

dryrunsecurity bot commented Jan 8, 2026

DryRun Security

This pull request contains client-side enforcement of a business-critical 4:00 PM PT payroll cancellation deadline using the device clock (new Date()), and a fragile timezone/DST calculation that relies on the client's local timezone (getPacificTimeOffset), making the deadline check bypassable by changing the device clock and unreliable for users outside Pacific Time.

Insecure client-side enforcement of business-critical deadline and fragile timezone logic. in src/components/Payroll/helpers.ts
Vulnerability Insecure client-side enforcement of business-critical deadline and fragile timezone logic.
Description The canCancelPayroll function enforces a business-critical 4:00 PM PT cancellation deadline using the client's local system clock (new Date()). This allows a user to bypass the restriction by simply modifying their device's clock. Furthermore, the getPacificTimeOffset function incorrectly calculates Daylight Saving Time (DST) transitions by using local Date constructors (new Date(year, 2, 1)), which rely on the client's local timezone. This results in incorrect UTC offsets for users outside of the Pacific Time zone, causing the deadline check to be unreliable and potentially allowing or blocking cancellations incorrectly based on the user's geographical location.

const now = new Date()
const deadline = new Date(payroll.payrollDeadline)
const nowInPT = new Date(now.getTime() + getPacificTimeOffset(now) * MS_PER_HOUR)


All finding details can be found in the DryRun Security Dashboard.

@krisxcrash krisxcrash merged commit 685734b into main Jan 8, 2026
6 checks passed
@krisxcrash krisxcrash deleted the kw/fix/SDK-194 branch January 8, 2026 22:41
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.

3 participants