47c71e6f54
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 8m47s
- Implemented complete design system with 40+ CSS custom properties - Created 9 UI primitives (GameCard, SectionHeader, StatRow, ManaBar, ElementBadge, ValueDisplay, ActionButton, SkillRow, TooltipInfo) - Redesigned all tabs: Spire, Skills, Stats, Equipment, Crafting, Attunements, Golemancy, Spells, Loot, Achievements, Lab, Debug - Added toast notification system (GameToast) with success/warning/error/info types - Added confirmation dialogs for destructive actions - Removed all dev artifacts and component name labels - Added empty states to all tabs - Replaced emoji icons with Lucide React icons - Added enchantPower placeholder to StatsTab and EquipmentTab - Mobile audit passed at 375px viewport - Build passes with 0 errors, lint passes with 0 errors Sub-tasks completed: - ST1: Design System Implementation - ST2: Global Layout & Header - ST3: Left Panel (Mana Display & Action Area) - ST4: Skills Tab - ST5: Spire Tab & Spire Mode UI - ST6: Stats Tab - ST7: Equipment & Crafting Tabs - ST8: Attunements Tab - ST9: Remaining Tabs - ST10: Toast System & Confirmation Dialogs Documentation: 15+ files in docs/task4/
185 lines
5.0 KiB
TypeScript
185 lines
5.0 KiB
TypeScript
'use client';
|
|
|
|
import { useState, type ReactNode } from 'react';
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from '@/components/ui/alert-dialog';
|
|
import { AlertTriangle, AlertCircle, Info, CheckCircle } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
export type ConfirmDialogVariant = 'danger' | 'warning' | 'info' | 'success';
|
|
|
|
interface ConfirmDialogProps {
|
|
/** Whether the dialog is open */
|
|
open: boolean;
|
|
/** Callback when open state changes */
|
|
onOpenChange: (open: boolean) => void;
|
|
/** Dialog title */
|
|
title: string;
|
|
/** Dialog description/content */
|
|
description: ReactNode;
|
|
/** Cancel button text (default: "Cancel") */
|
|
cancelText?: string;
|
|
/** Confirm button text (default: "Confirm") */
|
|
confirmText?: string;
|
|
/** Dialog variant/type */
|
|
variant?: ConfirmDialogVariant;
|
|
/** Callback when user confirms */
|
|
onConfirm: () => void | Promise<void>;
|
|
/** Callback when user cancels */
|
|
onCancel?: () => void;
|
|
/** Whether the confirm action is destructive */
|
|
destructive?: boolean;
|
|
}
|
|
|
|
const VARIANT_ICONS = {
|
|
danger: AlertTriangle,
|
|
warning: AlertCircle,
|
|
info: Info,
|
|
success: CheckCircle,
|
|
};
|
|
|
|
const VARIANT_TITLE_COLORS = {
|
|
danger: 'text-[var(--color-danger)]',
|
|
warning: 'text-[var(--color-warning)]',
|
|
info: 'text-[var(--color-info)]',
|
|
success: 'text-[var(--color-success)]',
|
|
};
|
|
|
|
const VARIANT_ACTION_COLORS = {
|
|
danger: 'bg-[var(--color-danger)] hover:bg-[var(--interactive-danger-hover)] text-white',
|
|
warning: 'bg-[var(--color-warning)] hover:opacity-90 text-black',
|
|
info: 'bg-[var(--color-info)] hover:opacity-90 text-white',
|
|
success: 'bg-[var(--color-success)] hover:opacity-90 text-white',
|
|
};
|
|
|
|
/**
|
|
* Reusable confirmation dialog component.
|
|
* Uses the existing shadcn/ui AlertDialog.
|
|
*
|
|
* @example
|
|
* <ConfirmDialog
|
|
* open={showDialog}
|
|
* onOpenChange={setShowDialog}
|
|
* title="Delete Item"
|
|
* description="Are you sure you want to delete this item? This action cannot be undone."
|
|
* variant="danger"
|
|
* onConfirm={handleDelete}
|
|
* />
|
|
*/
|
|
export function ConfirmDialog({
|
|
open,
|
|
onOpenChange,
|
|
title,
|
|
description,
|
|
cancelText = 'Cancel',
|
|
confirmText = 'Confirm',
|
|
variant = 'warning',
|
|
onConfirm,
|
|
onCancel,
|
|
destructive = false,
|
|
}: ConfirmDialogProps) {
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const Icon = VARIANT_ICONS[variant];
|
|
const titleColor = VARIANT_TITLE_COLORS[variant];
|
|
const actionClass = destructive ? VARIANT_ACTION_COLORS.danger : VARIANT_ACTION_COLORS[variant];
|
|
|
|
const handleConfirm = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
await onConfirm();
|
|
onOpenChange(false);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
onCancel?.();
|
|
onOpenChange(false);
|
|
};
|
|
|
|
return (
|
|
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
|
<AlertDialogContent className="bg-[var(--bg-elevated)] border-[var(--border-default)] text-[var(--text-primary)]">
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle className={cn('flex items-center gap-2', titleColor)}>
|
|
<Icon className="h-5 w-5" />
|
|
{title}
|
|
</AlertDialogTitle>
|
|
<AlertDialogDescription className="text-[var(--text-secondary)]">
|
|
{description}
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel
|
|
className="bg-[var(--bg-sunken)] border-[var(--border-default)] text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]"
|
|
onClick={handleCancel}
|
|
>
|
|
{cancelText}
|
|
</AlertDialogCancel>
|
|
<AlertDialogAction
|
|
className={cn(actionClass, isLoading && 'opacity-50 cursor-not-allowed')}
|
|
onClick={handleConfirm}
|
|
disabled={isLoading}
|
|
>
|
|
{isLoading ? 'Processing...' : confirmText}
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Hook to easily manage a confirmation dialog state.
|
|
*
|
|
* @example
|
|
* const { dialogProps, showConfirm } = useConfirmDialog();
|
|
*
|
|
* showConfirm({
|
|
* title: "Delete Item",
|
|
* description: "Are you sure?",
|
|
* onConfirm: () => deleteItem(),
|
|
* });
|
|
*/
|
|
export function useConfirmDialog() {
|
|
const [dialogState, setDialogState] = useState<{
|
|
open: boolean;
|
|
props: Omit<ConfirmDialogProps, 'open' | 'onOpenChange'>;
|
|
}>({
|
|
open: false,
|
|
props: {
|
|
title: '',
|
|
description: '',
|
|
onConfirm: () => {},
|
|
},
|
|
});
|
|
|
|
const showConfirm = (props: Omit<ConfirmDialogProps, 'open' | 'onOpenChange'>) => {
|
|
setDialogState({ open: true, props });
|
|
};
|
|
|
|
const dialogProps: ConfirmDialogProps = {
|
|
open: dialogState.open,
|
|
onOpenChange: (open: boolean) => setDialogState(prev => ({ ...prev, open })),
|
|
...dialogState.props,
|
|
};
|
|
|
|
return {
|
|
dialogProps,
|
|
showConfirm,
|
|
ConfirmDialogComponent: <ConfirmDialog {...dialogProps} />,
|
|
};
|
|
}
|
|
|
|
export default ConfirmDialog;
|