A
Anonymous

Voucher Wallet - Copy this React, Tailwind Component to your project

Import React, { useState, useCallback } from 'react'; import { Camera, List, ChevronRight, ChevronDown, ChevronUp, BarChart2, Calendar, Bell, Search } from 'lucide react'; import { Card, CardContent } from '@/components/ui/card'; import { Alert, AlertDescription } from '@/components/ui/alert'; const VoucherApp = () => { const [activeTab, setActiveTab] = useState('dashboard'); const [selectedStore, setSelectedStore] = useState(null); const [monthsToExpiry, setMonthsToExpiry] = useState(null); const [customMonths, setCustomMonths] = useState(''); const [expandedCategories, setExpandedCategories] = useState(new Set()); const [selectedCategories, setSelectedCategories] = useState(new Set()); const [showReminderAlert, setShowReminderAlert] = useState(false); const [reminderMessage, setReminderMessage] = useState(''); const [showFilters, setShowFilters] = useState(false); const [categorySearch, setCategorySearch] = useState(''); // Sample data const allCategories = [ "Grocery", "Electronics", "Clothing", "Food & Beverage", "Entertainment", "Sports", "Home & Garden", "Beauty", "Books", "Toys", "Health", "Travel" ].sort(); const vouchers = [ {"voucher_code": "VOUCHER1", "value": 200, "valid_until": "2024 11 25"}, {"voucher_code": "VOUCHER2", "value": 250, "valid_until": "2025 01 25"}, {"voucher_code": "VOUCHER3", "value": 400, "valid_until": "2024 12 25"}, {"voucher_code": "VOUCHER4", "value": 200, "valid_until": "2024 12 25"}, {"voucher_code": "VOUCHER5", "value": 50, "valid_until": "2024 11 25"} ]; const storeVoucherAssociations = [ {"store_id": "SHOP1", "voucher_code": "VOUCHER4"}, {"store_id": "SHOP1", "voucher_code": "VOUCHER5"}, {"store_id": "SHOP2", "voucher_code": "VOUCHER2"}, {"store_id": "SHOP3", "voucher_code": "VOUCHER3"} ]; const storeCategories = [ {"store_id": "SHOP1", "category": "Grocery"}, {"store_id": "SHOP2", "category": "Electronics"}, {"store_id": "SHOP3", "category": "Clothing"} ]; // Helper functions const isVoucherValidWithinMonths = (validUntil) => { if (!monthsToExpiry) return true; const expiryDate = new Date(validUntil); const today = new Date(); const monthsDiff = (expiryDate.getFullYear() today.getFullYear()) * 12 + (expiryDate.getMonth() today.getMonth()); return monthsDiff <= monthsToExpiry; }; const getFilteredVouchers = () => { return vouchers.filter(v => isVoucherValidWithinMonths(v.valid_until)); }; const getStoreDetails = () => { const storeDetails = {}; storeCategories.forEach(sc => { storeDetails[sc.store_id] = { store_id: sc.store_id, category: sc.category, vouchers: [], total_value: 0 }; }); const filteredVouchers = getFilteredVouchers(); storeVoucherAssociations.forEach(sva => { const voucher = filteredVouchers.find(v => v.voucher_code === sva.voucher_code); if (voucher && storeDetails[sva.store_id]) { storeDetails[sva.store_id].vouchers.push(voucher); storeDetails[sva.store_id].total_value += voucher.value; } }); return storeDetails; }; const getCategoryDetails = () => { const categoryDetails = {}; const storeDetails = getStoreDetails(); Object.values(storeDetails).forEach(store => { if (!categoryDetails[store.category]) { categoryDetails[store.category] = { category: store.category, stores: {}, total_value: 0 }; } categoryDetails[store.category].stores[store.store_id] = store; categoryDetails[store.category].total_value += store.total_value; }); return categoryDetails; }; const toggleCategory = useCallback((category) => { setExpandedCategories(prev => { const newExpanded = new Set(prev); if (newExpanded.has(category)) { newExpanded.delete(category); } else { newExpanded.add(category); } return newExpanded; }); }, []); // UI Components const FilterControls = ({ className = "" }) => { const filteredCategories = allCategories.filter(category => category.toLowerCase().includes(categorySearch.toLowerCase()) ); return ( <div className={`space y 2 ${className}`}> <div className="relative"> <button onClick={() => setShowFilters(!showFilters)} className="w full flex items center justify between p 2 border rounded bg white" > <span className="text sm"> {selectedCategories.size > 0 ? `${selectedCategories.size} categories selected` : 'Filter by Categories'} </span> {showFilters ? <ChevronUp size={16} /> : <ChevronDown size={16} />} </button> {showFilters && ( <div className="absolute z 10 w full mt 1 bg white border rounded shadow lg"> <div className="p 2 border b"> <div className="relative"> <Search size={16} className="absolute left 2 top 2.5 text gray 400" /> <input type="text" value={categorySearch} onChange={(e) => setCategorySearch(e.target.value)} placeholder="Search categories..." className="w full pl 8 pr 2 py 1.5 border rounded text sm" /> </div> </div> <div className="max h 48 overflow y auto"> {filteredCategories.map(category => ( <label key={category} className="flex items center p 2 hover:bg gray 50"> <input type="checkbox" checked={selectedCategories.has(category)} onChange={() => { setSelectedCategories(prev => { const newSelected = new Set(prev); if (newSelected.has(category)) { newSelected.delete(category); } else { newSelected.add(category); } return newSelected; }); }} className="mr 2" /> <span className="text sm">{category}</span> </label> ))} </div> </div> )} </div> <div className="flex items center space x 2"> <input type="text" value={customMonths} onChange={(e) => { const value = e.target.value; if (value === '') { setCustomMonths(''); setMonthsToExpiry(null); } else if (/^\d+$/.test(value) && parseInt(value) > 0) { setCustomMonths(value); setMonthsToExpiry(parseInt(value)); } }} placeholder="Enter months to expiry" className="border rounded p 1.5 text sm w full" /> </div> </div> ); }; const ActionButtons = ({ item, type }) => ( <div className="flex space x 2"> <button onClick={() => { setReminderMessage(`Added ${type} "${item}" to calendar`); setShowReminderAlert(true); setTimeout(() => setShowReminderAlert(false), 3000); }} className="p 1 text blue 600 hover:text blue 800" title="Add to Calendar" > <Calendar size={16} /> </button> <button onClick={() => { setReminderMessage(`Set reminder for ${type} "${item}"`); setShowReminderAlert(true); setTimeout(() => setShowReminderAlert(false), 3000); }} className="p 1 text blue 600 hover:text blue 800" title="Set Reminder" > <Bell size={16} /> </button> </div> ); const renderAnalytics = () => { const categoryDetails = getCategoryDetails(); const filteredVouchers = getFilteredVouchers(); return ( <div className="space y 4"> <FilterControls className="mb 4" /> <Card className="bg white"> <CardContent className="p 4"> <div className="mb 4"> <h3 className="font medium">Total Active Vouchers: {filteredVouchers.length}</h3> </div> <div className="space y 2"> {Object.values(categoryDetails) .filter(category => selectedCategories.size === 0 || selectedCategories.has(category.category) ) .map(category => ( <div key={category.category} className="border rounded p 2"> <div className="flex items center justify between"> <button onClick={() => toggleCategory(category.category)} className="flex items center space x 2" > <span className="font medium">{category.category}</span> <span className="text gray 500">${category.total_value}</span> {expandedCategories.has(category.category) ? <ChevronUp size={16} /> : <ChevronDown size={16} /> } </button> <ActionButtons item={category.category} type="category" /> </div> {expandedCategories.has(category.category) && ( <div className="mt 2 pl 4 space y 2"> {Object.values(category.stores).map(store => ( <div key={store.store_id} className="border l 2 pl 2"> <div className="flex items center justify between"> <span className="font medium text sm"> {store.store_id} (${store.total_value}) </span> <ActionButtons item={store.store_id} type="store" /> </div> <div className="pl 4 space y 1"> {store.vouchers.map(voucher => ( <div key={voucher.voucher_code} className="flex justify between items center text sm"> <div> <span>{voucher.voucher_code}</span> <span className="ml 2 text gray 500"> ${voucher.value} (Expires: {voucher.valid_until}) </span> </div> <ActionButtons item={voucher.voucher_code} type="voucher" /> </div> ))} </div> </div> ))} </div> )} </div> ))} </div> </CardContent> </Card> </div> ); }; return ( <div className="max w 2xl mx auto h screen flex flex col bg gray 50"> <div className="bg blue 600 p 3 text white"> <h1 className="text lg font bold">Voucher Wallet</h1> </div> {showReminderAlert && ( <Alert className="m 2"> <AlertDescription>{reminderMessage}</AlertDescription> </Alert> )} <div className="flex 1 overflow auto p 3"> {activeTab === 'dashboard' && !selectedStore && ( <div className="space y 3"> <FilterControls /> <div className="space y 2"> <h2 className="font semibold">Stores</h2> {Object.values(getStoreDetails()) .filter(store => selectedCategories.size === 0 || selectedCategories.has(store.category) ) .map(store => ( <Card key={store.store_id} className="bg white hover:bg gray 50" > <CardContent className="p 3"> <div className="flex justify between items center"> <div className="flex 1"> <p className="font medium">{store.store_id}</p> <p className="text sm text gray 500">{store.category}</p> <p className="text sm text gray 500"> {store.vouchers.length} voucher{store.vouchers.length !== 1 ? 's' : ''} </p> </div> <div className="flex items center space x 2"> <p className="text lg font bold">${store.total_value}</p> <ActionButtons item={store.store_id} type="store" /> <ChevronRight size={16} className="text gray 400" /> </div> </div> </CardContent> </Card> ))} </div> </div> )} {activeTab === 'analytics' && renderAnalytics()} {activeTab === 'scan' && ( <div className="text center p 8"> <Camera size={48} className="mx auto mb 4 text gray 400" /> <p>Tap to scan voucher QR code or barcode</p> </div> )} </div> <div className="bg white border t grid grid cols 3 p 1"> <button onClick={() => { setActiveTab('dashboard'); setSelectedStore(null); }} className={`flex flex col items center p 2 ${activeTab === 'dashboard' ? 'text blue 600' : 'text gray 600'}`} > <List size={20} /> <span className="text xs">Stores</span> </button> <button onClick={() => setActiveTab('scan')} className

Prompt
Component Preview

About

VoucherWallet - Manage your vouchers with filters, category selection, and reminders. Built with React and Tailwind. Copy code today!

Share

Last updated 1 month ago