Files
goodgo-platform/apps/web/components/comparison/add-to-compare-button.tsx
Ho Ngoc Hai 37fab515b7 feat(web): add property comparison page with side-by-side view
Build a complete property comparison feature at /compare:
- Zustand store with localStorage persistence for selected listings (2-5)
- Side-by-side comparison table (price, area, price/m², amenities, location, etc.)
- Summary statistics banner (price range, area range, price/m² range)
- "Add to Compare" button on property cards and detail pages
- Floating comparison bar for quick access when listings are selected
- Bilingual i18n support (Vietnamese + English)
- 18 unit tests for store logic and comparison stats computation
- Mobile-responsive layout with horizontal scroll on comparison table

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-10 23:55:50 +07:00

63 lines
2.1 KiB
TypeScript

'use client';
import { BarChart3, Check } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { Button, type ButtonProps } from '@/components/ui/button';
import { useComparisonStore } from '@/lib/comparison-store';
interface AddToCompareButtonProps extends Omit<ButtonProps, 'onClick'> {
listingId: string;
compact?: boolean;
}
export function AddToCompareButton({ listingId, compact, ...props }: AddToCompareButtonProps) {
const t = useTranslations('compare');
const isSelected = useComparisonStore((s) => s.isSelected(listingId));
const addToCompare = useComparisonStore((s) => s.addToCompare);
const removeFromCompare = useComparisonStore((s) => s.removeFromCompare);
const canAdd = useComparisonStore((s) => s.canAdd());
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (isSelected) {
removeFromCompare(listingId);
} else {
addToCompare(listingId);
}
};
if (compact) {
return (
<button
onClick={handleClick}
disabled={!isSelected && !canAdd}
className={`inline-flex items-center justify-center rounded-full p-1.5 transition-colors ${
isSelected
? 'bg-primary text-primary-foreground'
: 'bg-background/80 text-muted-foreground hover:bg-accent hover:text-accent-foreground'
} ${!isSelected && !canAdd ? 'opacity-50 cursor-not-allowed' : ''}`}
aria-label={isSelected ? t('removeFromCompare') : t('addToCompare')}
title={isSelected ? t('removeFromCompare') : t('addToCompare')}
>
{isSelected ? <Check className="h-4 w-4" /> : <BarChart3 className="h-4 w-4" />}
</button>
);
}
return (
<Button
variant={isSelected ? 'default' : 'outline'}
size="sm"
onClick={handleClick}
disabled={!isSelected && !canAdd}
className="gap-1.5"
aria-label={isSelected ? t('removeFromCompare') : t('addToCompare')}
{...props}
>
{isSelected ? <Check className="h-4 w-4" /> : <BarChart3 className="h-4 w-4" />}
{isSelected ? t('added') : t('addToCompare')}
</Button>
);
}