Skip to content

refactor: Extract platform alert/confirm utilities to reduce code duplication #160

@claude

Description

@claude

Summary

Platform-specific alert/confirm patterns are duplicated approximately 93 times across 17 files in the codebase.

Related to: #154 (Daily Codebase Review - 2025-12-16)

Problem

The same pattern appears repeatedly throughout the codebase:

const confirmed =
  Platform.OS === 'web'
    ? window.confirm(confirmMessage)
    : await new Promise<boolean>((resolve) => {
        Alert.alert('Confirm', confirmMessage, [
          { text: 'Cancel', style: 'cancel', onPress: () => resolve(false) },
          { text: 'OK', onPress: () => resolve(true) },
        ]);
      });

Affected files (17 total):

  • app/(tabs)/profile.tsx (11 occurrences)
  • components/settings/SettingsContent.tsx (9 occurrences)
  • app/(tabs)/tasks.tsx (5 occurrences)
  • app/(tabs)/manage-tasks.tsx (3 occurrences)
  • components/sheets/LogSlipUpSheet.tsx (2 occurrences)
  • And 12 more files...

Proposed Solution

Create a shared utility module:

lib/alert-utils.ts

import { Alert, Platform } from 'react-native';

/**
 * Shows a platform-appropriate confirmation dialog.
 * 
 * @param title - The dialog title (native only, prepended to message on web)
 * @param message - The confirmation message
 * @param confirmText - Text for confirm button (default: 'OK')
 * @param cancelText - Text for cancel button (default: 'Cancel')
 * @returns Promise resolving to true if confirmed, false if cancelled
 * 
 * @example
 * ```ts
 * const shouldDelete = await confirmAsync('Delete Item', 'Are you sure?');
 * if (shouldDelete) {
 *   await deleteItem();
 * }
 * ```
 */
export async function confirmAsync(
  title: string,
  message: string,
  confirmText = 'OK',
  cancelText = 'Cancel'
): Promise<boolean> {
  if (Platform.OS === 'web') {
    return window.confirm(`${title}\n\n${message}`);
  }

  return new Promise((resolve) => {
    Alert.alert(title, message, [
      { text: cancelText, style: 'cancel', onPress: () => resolve(false) },
      { text: confirmText, onPress: () => resolve(true) },
    ]);
  });
}

/**
 * Shows a platform-appropriate alert dialog.
 * 
 * @param title - The dialog title
 * @param message - The alert message
 * @param buttonText - Text for dismiss button (default: 'OK')
 * 
 * @example
 * ```ts
 * await alertAsync('Error', 'Something went wrong');
 * ```
 */
export async function alertAsync(
  title: string,
  message: string,
  buttonText = 'OK'
): Promise<void> {
  if (Platform.OS === 'web') {
    window.alert(`${title}\n\n${message}`);
    return;
  }

  return new Promise((resolve) => {
    Alert.alert(title, message, [
      { text: buttonText, onPress: () => resolve() },
    ]);
  });
}

/**
 * Shows a destructive confirmation dialog with appropriate styling.
 * 
 * @param title - The dialog title
 * @param message - The confirmation message
 * @param destructiveText - Text for destructive action (default: 'Delete')
 * @param cancelText - Text for cancel button (default: 'Cancel')
 * 
 * @example
 * ```ts
 * const shouldDelete = await confirmDestructiveAsync(
 *   'Delete Account',
 *   'This action cannot be undone.'
 * );
 * ```
 */
export async function confirmDestructiveAsync(
  title: string,
  message: string,
  destructiveText = 'Delete',
  cancelText = 'Cancel'
): Promise<boolean> {
  if (Platform.OS === 'web') {
    return window.confirm(`${title}\n\n${message}\n\nThis action cannot be undone.`);
  }

  return new Promise((resolve) => {
    Alert.alert(title, message, [
      { text: cancelText, style: 'cancel', onPress: () => resolve(false) },
      { text: destructiveText, style: 'destructive', onPress: () => resolve(true) },
    ]);
  });
}

Usage After Refactor

// Before (15 lines per occurrence)
const confirmed =
  Platform.OS === 'web'
    ? window.confirm('Disconnect from sponsor?')
    : await new Promise<boolean>((resolve) => {
        Alert.alert('Disconnect', 'Are you sure?', [
          { text: 'Cancel', style: 'cancel', onPress: () => resolve(false) },
          { text: 'OK', onPress: () => resolve(true) },
        ]);
      });

// After (1 line)
const confirmed = await confirmAsync('Disconnect', 'Disconnect from sponsor?');

Benefits

  • DRY: Eliminates ~93 duplicated code blocks
  • Maintainability: Single place to update alert behavior
  • Consistency: All alerts behave the same way
  • Testability: Easy to mock in tests
  • Type safety: Proper TypeScript typing

Acceptance Criteria

  • Create lib/alert-utils.ts with confirmAsync, alertAsync, confirmDestructiveAsync
  • Add JSDoc documentation with examples
  • Create tests for all utility functions
  • Refactor all occurrences to use new utilities
  • Remove duplicated code from all 17 files
  • All existing tests pass
  • No functional changes to user experience

Priority

MEDIUM - Code quality improvement, significant DRY violation

Estimated Effort

3-4 hours (mostly mechanical refactoring)

Files to Modify

  1. Create: lib/alert-utils.ts
  2. Create: __tests__/lib/alert-utils.test.ts
  3. Refactor: 17 files with duplicated patterns

Metadata

Metadata

Assignees

No one assigned

    Labels

    frontendFrontend/UI related changesrefactorCode refactoring

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions