← back to Watches
src/components/FavoritesManager.jsx
147 lines
import React from 'react';
import { motion } from 'framer-motion';
import { FiHeart, FiShare2, FiDownload } from 'react-icons/fi';
import { useLocalStorage } from '../hooks/useLocalStorage';
/**
* Favorites/Watchlist manager with localStorage persistence
* Features: Add/remove favorites, share links, export data
*/
export function useFavorites() {
const [favorites, setFavorites] = useLocalStorage('omega-favorites', []);
const toggleFavorite = (watchId) => {
setFavorites(prev =>
prev.includes(watchId)
? prev.filter(id => id !== watchId)
: [...prev, watchId]
);
};
const isFavorite = (watchId) => favorites.includes(watchId);
const clearFavorites = () => setFavorites([]);
return { favorites, toggleFavorite, isFavorite, clearFavorites };
}
/**
* Favorite button component
*/
export function FavoriteButton({ watchId, isFavorite, onToggle, className = '' }) {
return (
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={(e) => {
e.stopPropagation();
onToggle(watchId);
}}
className={`p-2 rounded-full transition-colors ${className} ${
isFavorite
? 'bg-omega-red text-white hover:bg-red-700'
: 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'
}`}
title={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
>
<FiHeart className={isFavorite ? 'fill-current' : ''} />
</motion.button>
);
}
/**
* Share watch link button
*/
export function ShareButton({ watch, className = '' }) {
const handleShare = async (e) => {
e.stopPropagation();
const url = `${window.location.origin}?watch=${watch.id}`;
const text = `Check out this ${watch.model} by Omega`;
if (navigator.share) {
try {
await navigator.share({ title: watch.model, text, url });
} catch (err) {
if (err.name !== 'AbortError') {
copyToClipboard(url);
}
}
} else {
copyToClipboard(url);
}
};
const copyToClipboard = (text) => {
navigator.clipboard.writeText(text).then(() => {
alert('Link copied to clipboard!');
});
};
return (
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={handleShare}
className={`p-2 rounded-full bg-blue-500 text-white hover:bg-blue-600 transition-colors ${className}`}
title="Share this watch"
>
<FiShare2 />
</motion.button>
);
}
/**
* Export watch data button
*/
export function ExportButton({ watch, format = 'json', className = '' }) {
const handleExport = (e) => {
e.stopPropagation();
if (format === 'json') {
exportJSON(watch);
} else if (format === 'csv') {
exportCSV(watch);
}
};
const exportJSON = (watch) => {
const data = JSON.stringify(watch, null, 2);
const blob = new Blob([data], { type: 'application/json' });
downloadBlob(blob, `${watch.reference}-data.json`);
};
const exportCSV = (watch) => {
const headers = ['Date', 'Price'];
const rows = watch.priceHistory.map(p => [p.date, p.price]);
const csv = [headers, ...rows].map(row => row.join(',')).join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
downloadBlob(blob, `${watch.reference}-prices.csv`);
};
const downloadBlob = (blob, filename) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
return (
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={handleExport}
className={`p-2 rounded-full bg-green-500 text-white hover:bg-green-600 transition-colors ${className}`}
title={`Export as ${format.toUpperCase()}`}
>
<FiDownload />
</motion.button>
);
}
export default { useFavorites, FavoriteButton, ShareButton, ExportButton };