290 lines
11 KiB
TypeScript
290 lines
11 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useAuth } from '../context/AuthContext';
|
|
import Header from '../components/Header';
|
|
import RewardCard from '../components/RewardCard';
|
|
import {
|
|
fetchInventoryItems,
|
|
getRewardById,
|
|
type InventoryItem,
|
|
type Reward,
|
|
formatDate
|
|
} from '../lib/rewards';
|
|
|
|
interface InventoryItemWithReward extends InventoryItem {
|
|
reward: Reward | null;
|
|
}
|
|
|
|
export default function InventoryPage() {
|
|
const { user } = useAuth();
|
|
const [inventoryItems, setInventoryItems] = useState<InventoryItemWithReward[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [selectedItem, setSelectedItem] = useState<InventoryItemWithReward | null>(null);
|
|
const [showRedeemModal, setShowRedeemModal] = useState(false);
|
|
const [showTradeModal, setShowTradeModal] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const loadInventory = async () => {
|
|
if (!user?.uid) return;
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
// Fetch inventory items
|
|
const items = await fetchInventoryItems(user.uid);
|
|
|
|
// Fetch reward details for each item
|
|
const itemsWithRewards = await Promise.all(
|
|
items.map(async (item) => {
|
|
try {
|
|
const reward = await getRewardById(item.reward_id);
|
|
return { ...item, reward };
|
|
} catch (error) {
|
|
console.error(`Error fetching reward ${item.reward_id}:`, error);
|
|
return { ...item, reward: null };
|
|
}
|
|
})
|
|
);
|
|
|
|
setInventoryItems(itemsWithRewards);
|
|
} catch (err) {
|
|
console.error('Error loading inventory:', err);
|
|
setError(err instanceof Error ? err.message : 'Failed to load inventory');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
loadInventory();
|
|
}, [user?.uid]);
|
|
|
|
const handleRedeem = (item: InventoryItemWithReward) => {
|
|
setSelectedItem(item);
|
|
setShowRedeemModal(true);
|
|
};
|
|
|
|
const handleTrade = (item: InventoryItemWithReward) => {
|
|
setSelectedItem(item);
|
|
setShowTradeModal(true);
|
|
};
|
|
|
|
const handleRedeemConfirm = () => {
|
|
// TODO: Implement redeem functionality
|
|
console.log('Redeeming item:', selectedItem);
|
|
alert('Redeem functionality coming soon!');
|
|
setShowRedeemModal(false);
|
|
setSelectedItem(null);
|
|
};
|
|
|
|
const handleTradeConfirm = () => {
|
|
// TODO: Implement trade functionality
|
|
console.log('Trading item:', selectedItem);
|
|
alert('Trade functionality coming soon!');
|
|
setShowTradeModal(false);
|
|
setSelectedItem(null);
|
|
};
|
|
|
|
if (!user) {
|
|
return (
|
|
<>
|
|
<Header />
|
|
<div className="min-h-screen bg-gradient-to-b from-background to-background/95 pt-16">
|
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8 text-center">
|
|
<h1 className="text-2xl font-bold mb-4">Access Denied</h1>
|
|
<p className="text-foreground/60">Please sign in to view your inventory.</p>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Header />
|
|
<div className="min-h-screen bg-gradient-to-b from-background to-background/95 pt-16">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
{/* Page Header */}
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold mb-2">My Inventory</h1>
|
|
<p className="text-foreground/60">
|
|
Manage your collected rewards and items
|
|
</p>
|
|
</div>
|
|
|
|
{/* Loading State */}
|
|
{isLoading && (
|
|
<div className="text-center py-12">
|
|
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600 mb-4"></div>
|
|
<p className="text-foreground/60">Loading your inventory...</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Error State */}
|
|
{error && (
|
|
<div className="text-center py-12">
|
|
<div className="text-6xl mb-4">❌</div>
|
|
<h2 className="text-2xl font-bold mb-2">Oops!</h2>
|
|
<p className="text-foreground/60 mb-6">
|
|
{error}
|
|
</p>
|
|
<button
|
|
onClick={() => window.location.reload()}
|
|
className="px-6 py-3 rounded-lg bg-gradient-to-r from-purple-600 to-pink-600 text-white font-semibold hover:opacity-90 transition-opacity"
|
|
>
|
|
Try Again
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Inventory Grid */}
|
|
{!isLoading && !error && (
|
|
<>
|
|
{inventoryItems.length === 0 ? (
|
|
<div className="text-center py-12">
|
|
<div className="text-6xl mb-4">📦</div>
|
|
<h2 className="text-2xl font-bold mb-2">Empty Inventory</h2>
|
|
<p className="text-foreground/60 mb-6">
|
|
You haven't collected any rewards yet.
|
|
</p>
|
|
<button
|
|
onClick={() => window.location.href = '/boxes'}
|
|
className="px-6 py-3 rounded-lg bg-gradient-to-r from-purple-600 to-pink-600 text-white font-semibold hover:opacity-90 transition-opacity"
|
|
>
|
|
Open Some Boxes
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{inventoryItems.map((item) => (
|
|
<div
|
|
key={item.id}
|
|
className="bg-background/50 rounded-xl p-6 border border-foreground/10 hover:border-foreground/20 transition-colors"
|
|
>
|
|
{/* Reward Card */}
|
|
{item.reward ? (
|
|
<RewardCard reward={item.reward} showValue={false} className="mb-4" />
|
|
) : (
|
|
<div className="bg-foreground/5 rounded-lg p-4 mb-4 text-center">
|
|
<div className="text-4xl mb-2">❓</div>
|
|
<p className="text-foreground/60 text-sm">Reward details unavailable</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Item Details */}
|
|
<div className="space-y-2 mb-4">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-foreground/60">Receipt ID:</span>
|
|
<span
|
|
className="font-mono text-xs truncate ml-2 max-w-[120px]"
|
|
title={item.id}
|
|
>
|
|
{item.id}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-foreground/60">Unlocked:</span>
|
|
<span className="text-foreground/80">{formatDate(item.unlocked_on)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={() => handleRedeem(item)}
|
|
className="flex-1 px-3 py-2 rounded-lg bg-gradient-to-r from-green-600 to-emerald-600 text-white text-sm font-medium hover:opacity-90 transition-opacity"
|
|
>
|
|
Redeem
|
|
</button>
|
|
<button
|
|
onClick={() => handleTrade(item)}
|
|
className="flex-1 px-3 py-2 rounded-lg bg-gradient-to-r from-blue-600 to-cyan-600 text-white text-sm font-medium hover:opacity-90 transition-opacity"
|
|
>
|
|
Trade
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Redeem Modal */}
|
|
{showRedeemModal && selectedItem && (
|
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
|
<div className="bg-background rounded-2xl p-6 max-w-md w-full">
|
|
<div className="text-center">
|
|
<div className="text-4xl mb-4">🎁</div>
|
|
<h2 className="text-2xl font-bold mb-2">Redeem Item</h2>
|
|
<p className="text-foreground/60 mb-6">
|
|
Are you sure you want to redeem this item?
|
|
</p>
|
|
|
|
{selectedItem.reward && (
|
|
<div className="mb-6">
|
|
<RewardCard reward={selectedItem.reward} showValue={false} />
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-3">
|
|
<button
|
|
onClick={() => setShowRedeemModal(false)}
|
|
className="flex-1 px-4 py-3 rounded-lg border border-foreground/20 hover:bg-foreground/5 transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={handleRedeemConfirm}
|
|
className="flex-1 px-4 py-3 rounded-lg bg-gradient-to-r from-green-600 to-emerald-600 text-white font-semibold hover:opacity-90 transition-opacity"
|
|
>
|
|
Redeem
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Trade Modal */}
|
|
{showTradeModal && selectedItem && (
|
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
|
<div className="bg-background rounded-2xl p-6 max-w-md w-full">
|
|
<div className="text-center">
|
|
<div className="text-4xl mb-4">🔄</div>
|
|
<h2 className="text-2xl font-bold mb-2">Trade Item</h2>
|
|
<p className="text-foreground/60 mb-6">
|
|
Are you sure you want to trade this item?
|
|
</p>
|
|
|
|
{selectedItem.reward && (
|
|
<div className="mb-6">
|
|
<RewardCard reward={selectedItem.reward} showValue={false} />
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-3">
|
|
<button
|
|
onClick={() => setShowTradeModal(false)}
|
|
className="flex-1 px-4 py-3 rounded-lg border border-foreground/20 hover:bg-foreground/5 transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={handleTradeConfirm}
|
|
className="flex-1 px-4 py-3 rounded-lg bg-gradient-to-r from-blue-600 to-cyan-600 text-white font-semibold hover:opacity-90 transition-opacity"
|
|
>
|
|
Trade
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
}
|