Extract reusable UI components to reduce DRY violations

- Created StatusBadge component: Standardizes status badge display
  - Supports tradeStatus prop for trade-specific styling
  - Supports variant prop for simple badges (success/error/ready)
  - Eliminates repetitive badge style combinations

- Created EmptyState component: Standardizes empty state display
  - Handles loading and empty states consistently
  - Supports message, hint, and action props
  - Used across trades, invites, admin pages

- Created ConfirmationButton component: Standardizes confirmation flows
  - Two-step confirmation pattern (action -> confirm/cancel)
  - Supports different variants (danger/success/primary)
  - Handles loading states automatically
  - Used for cancel, complete, no-show actions

- Migrated pages to use new components:
  - trades/page.tsx: StatusBadge, EmptyState, ConfirmationButton
  - trades/[id]/page.tsx: StatusBadge
  - invites/page.tsx: StatusBadge, EmptyState
  - admin/trades/page.tsx: StatusBadge, EmptyState, ConfirmationButton
  - admin/invites/page.tsx: StatusBadge

Benefits:
- Eliminated ~50+ lines of repetitive badge styling code
- Consistent UI patterns across all pages
- Easier to maintain and update styling
- Better type safety

All tests passing (32 frontend, 33 e2e)
This commit is contained in:
counterweight 2025-12-25 21:40:07 +01:00
parent b86b506d72
commit 1a47b3643f
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
9 changed files with 309 additions and 425 deletions

View file

@ -0,0 +1,97 @@
import { buttonStyles } from "../styles/shared";
interface ConfirmationButtonProps {
/** Whether confirmation mode is active */
isConfirming: boolean;
/** Callback when user confirms the action */
onConfirm: () => void;
/** Callback when user cancels the confirmation */
onCancel: () => void;
/** Callback when user clicks the initial action button (to enter confirmation mode) */
onActionClick: () => void;
/** Label for the initial action button */
actionLabel: string;
/** Label for the confirm button (default: "Confirm") */
confirmLabel?: string;
/** Label for the cancel button (default: "No") */
cancelLabel?: string;
/** Whether the action is in progress (shows "..." on confirm button) */
isLoading?: boolean;
/** Style variant for the confirm button */
confirmVariant?: "danger" | "success" | "primary";
/** Custom style for the action button */
actionButtonStyle?: React.CSSProperties;
/** Custom style for the confirm button */
confirmButtonStyle?: React.CSSProperties;
}
/**
* Confirmation button component that shows a two-step confirmation flow.
* Initially shows an action button. When clicked, shows Confirm/Cancel buttons.
*/
export function ConfirmationButton({
isConfirming,
onConfirm,
onCancel,
onActionClick,
actionLabel,
confirmLabel = "Confirm",
cancelLabel = "No",
isLoading = false,
confirmVariant = "primary",
actionButtonStyle,
confirmButtonStyle,
}: ConfirmationButtonProps) {
if (isConfirming) {
let confirmStyle: React.CSSProperties = buttonStyles.primaryButton;
if (confirmVariant === "danger") {
confirmStyle = {
...buttonStyles.primaryButton,
background: "rgba(239, 68, 68, 0.9)",
borderColor: "rgba(239, 68, 68, 1)",
};
} else if (confirmVariant === "success") {
confirmStyle = {
...buttonStyles.primaryButton,
background: "rgba(34, 197, 94, 0.9)",
borderColor: "rgba(34, 197, 94, 1)",
};
}
return (
<>
<button
onClick={(e) => {
e.stopPropagation();
onConfirm();
}}
disabled={isLoading}
style={{ ...confirmStyle, ...confirmButtonStyle }}
>
{isLoading ? "..." : confirmLabel}
</button>
<button
onClick={(e) => {
e.stopPropagation();
onCancel();
}}
style={buttonStyles.secondaryButton}
>
{cancelLabel}
</button>
</>
);
}
return (
<button
onClick={(e) => {
e.stopPropagation();
onActionClick();
}}
style={actionButtonStyle || buttonStyles.secondaryButton}
>
{actionLabel}
</button>
);
}