Anil Ollier

...

Contributions


About


import React, { useState, useEffect } from 'react';
import { Wallet, Plus, Minus, DollarSign, TrendingUp, TrendingDown, CreditCard, PiggyBank, AlertCircle, Eye, EyeOff, Trash2, Edit3, Check, X } from 'lucide-react';

const WalletApp = () => {
  // State management
  const [balance, setBalance] = useState(0);
  const [transactions, setTransactions] = useState([]);
  const [showBalance, setShowBalance] = useState(true);
  const [activeTab, setActiveTab] = useState('overview');
  const [newTransaction, setNewTransaction] = useState({
    type: 'expense',
    amount: '',
    description: '',
    category: 'other'
  });
  const [editingTransaction, setEditingTransaction] = useState(null);

  // Categories for transactions
  const categories = {
    income: ['salary', 'freelance', 'investment', 'gift', 'other'],
    expense: ['food', 'transport', 'entertainment', 'utilities', 'shopping', 'healthcare', 'other']
  };

  // Load data from memory on component mount
  useEffect(() => {
    const savedData = JSON.parse(window.walletData || '{"balance": 0, "transactions": []}');
    setBalance(savedData.balance || 0);
    setTransactions(savedData.transactions || []);
  }, []);

  // Save data to memory whenever state changes
  useEffect(() => {
    const dataToSave = { balance, transactions };
    window.walletData = JSON.stringify(dataToSave);
  }, [balance, transactions]);

  // Add new transaction
  const addTransaction = () => {
    if (!newTransaction.amount || !newTransaction.description) {
      alert('Please fill in all fields');
      return;
    }

    const amount = parseFloat(newTransaction.amount);
    const transaction = {
      id: Date.now(),
      ...newTransaction,
      amount,
      date: new Date().toISOString()
    };

    setTransactions(prev => [transaction, ...prev]);
    
    // Update balance
    const balanceChange = newTransaction.type === 'income' ? amount : -amount;
    setBalance(prev => prev + balanceChange);

    // Reset form
    setNewTransaction({
      type: 'expense',
      amount: '',
      description: '',
      category: 'other'
    });
  };

  // Delete transaction
  const deleteTransaction = (id) => {
    const transaction = transactions.find(t => t.id === id);
    if (!transaction) return;

    setTransactions(prev => prev.filter(t => t.id !== id));
    
    // Adjust balance
    const balanceChange = transaction.type === 'income' ? -transaction.amount : transaction.amount;
    setBalance(prev => prev + balanceChange);
  };

  // Edit transaction
  const updateTransaction = (id, updatedTransaction) => {
    const oldTransaction = transactions.find(t => t.id === id);
    if (!oldTransaction) return;

    // Revert old transaction from balance
    const oldBalanceChange = oldTransaction.type === 'income' ? -oldTransaction.amount : oldTransaction.amount;
    
    // Apply new transaction to balance
    const newBalanceChange = updatedTransaction.type === 'income' ? updatedTransaction.amount : -updatedTransaction.amount;
    
    setBalance(prev => prev + oldBalanceChange + newBalanceChange);
    
    setTransactions(prev => prev.map(t => t.id === id ? { ...updatedTransaction, id, date: t.date } : t));
    setEditingTransaction(null);
  };

  // Calculate statistics
  const thisMonthTransactions = transactions.filter(t => {
    const transactionDate = new Date(t.date);
    const now = new Date();
    return transactionDate.getMonth() === now.getMonth() && transactionDate.getFullYear() === now.getFullYear();
  });

  const thisMonthIncome = thisMonthTransactions
    .filter(t => t.type === 'income')
    .reduce((sum, t) => sum + t.amount, 0);
  
  const thisMonthExpenses = thisMonthTransactions
    .filter(t => t.type === 'expense')
    .reduce((sum, t) => sum + t.amount, 0);

  // Format currency
  const formatCurrency = (amount) => {
    return new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD'
    }).format(amount);
  };

  // Format date
  const formatDate = (dateString) => {
    return new Date(dateString).toLocaleDateString('en-US', {
      month: 'short',
      day: 'numeric',
      hour: '2-digit',
      minute: '2-digit'
    });
  };

  return (
    <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
      <div className="max-w-4xl mx-auto p-6">
        {/* Header */}
        <div className="text-center mb-8">
          <div className="flex items-center justify-center mb-4">
            <Wallet className="w-12 h-12 text-indigo-600 mr-3" />
            <h1 className="text-4xl font-bold text-gray-800">Personal Wallet</h1>
          </div>
          <p className="text-gray-600">Your finances, managed locally and securely</p>
        </div>

        {/* Balance Card */}
        <div className="bg-white rounded-2xl shadow-xl p-8 mb-8 border border-gray-200">
          <div className="flex items-center justify-between mb-4">
            <h2 className="text-2xl font-semibold text-gray-800">Current Balance</h2>
            <button
              onClick={() => setShowBalance(!showBalance)}
              className="p-2 hover:bg-gray-100 rounded-full transition-colors"
            >
              {showBalance ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
            </button>
          </div>
          <div className="text-5xl font-bold mb-4">
            {showBalance ? (
              <span className={balance >= 0 ? 'text-green-600' : 'text-red-600'}>
                {formatCurrency(balance)}
              </span>
            ) : (
              <span className="text-gray-400">••••••</span>
            )}
          </div>
          
          {/* Monthly Stats */}
          <div className="grid grid-cols-2 gap-4 mt-6">
            <div className="bg-green-50 p-4 rounded-lg">
              <div className="flex items-center">
                <TrendingUp className="w-5 h-5 text-green-600 mr-2" />
                <span className="text-sm text-green-600 font-medium">This Month Income</span>
              </div>
              <div className="text-2xl font-bold text-green-700">
                {formatCurrency(thisMonthIncome)}
              </div>
            </div>
            <div className="bg-red-50 p-4 rounded-lg">
              <div className="flex items-center">
                <TrendingDown className="w-5 h-5 text-red-600 mr-2" />
                <span className="text-sm text-red-600 font-medium">This Month Expenses</span>
              </div>
              <div className="text-2xl font-bold text-red-700">
                {formatCurrency(thisMonthExpenses)}
              </div>
            </div>
          </div>
        </div>

        {/* Navigation Tabs */}
        <div className="flex bg-white rounded-xl shadow-lg mb-8 p-2">
          <button
            onClick={() => setActiveTab('overview')}
            className={`flex-1 py-3 px-6 rounded-lg font-medium transition-all ${
              activeTab === 'overview' 
                ? 'bg-indigo-600 text-white shadow-md' 
                : 'text-gray-600 hover:bg-gray-100'
            }`}
          >
            <DollarSign className="w-5 h-5 inline mr-2" />
            Overview
          </button>
          <button
            onClick={() => setActiveTab('add')}
            className={`flex-1 py-3 px-6 rounded-lg font-medium transition-all ${
              activeTab === 'add' 
                ? 'bg-indigo-600 text-white shadow-md' 
                : 'text-gray-600 hover:bg-gray-100'
            }`}
          >
            <Plus className="w-5 h-5 inline mr-2" />
            Add Transaction
          </button>
        </div>

        {/* Content based on active tab */}
        {activeTab === 'overview' && (
          <div className="space-y-6">
            {/* Recent Transactions */}
            <div className="bg-white rounded-2xl shadow-lg p-6">
              <h3 className="text-xl font-semibold text-gray-800 mb-6 flex items-center">
                <CreditCard className="w-6 h-6 mr-2 text-indigo-600" />
                Recent Transactions
              </h3>
              
              {transactions.length === 0 ? (
                <div className="text-center py-12">
                  <PiggyBank className="w-16 h-16 text-gray-300 mx-auto mb-4" />
                  <p className="text-gray-500">No transactions yet. Add your first transaction!</p>
                </div>
              ) : (
                <div className="space-y-3">
                  {transactions.slice(0, 10).map((transaction) => (
                    <div key={transaction.id} className="flex items-center justify-between p-4 bg-gray-50 rounded-xl hover:bg-gray-100 transition-colors">
                      {editingTransaction === transaction.id ? (
                        <EditTransactionForm 
                          transaction={transaction}
                          categories={categories}
                          onSave={(updatedTransaction) => updateTransaction(transaction.id, updatedTransaction)}
                          onCancel={() => setEditingTransaction(null)}
                        />
                      ) : (
                        <>
                          <div className="flex items-center flex-1">
                            <div className={`w-10 h-10 rounded-full flex items-center justify-center mr-4 ${
                              transaction.type === 'income' ? 'bg-green-100' : 'bg-red-100'
                            }`}>
                              {transaction.type === 'income' ? (
                                <Plus className="w-5 h-5 text-green-600" />
                              ) : (
                                <Minus className="w-5 h-5 text-red-600" />
                              )}
                            </div>
                            <div>
                              <div className="font-medium text-gray-800">{transaction.description}</div>
                              <div className="text-sm text-gray-500 capitalize">
                                {transaction.category} • {formatDate(transaction.date)}
                              </div>
                            </div>
                          </div>
                          <div className="flex items-center">
                            <div className={`text-lg font-semibold mr-3 ${
                              transaction.type === 'income' ? 'text-green-600' : 'text-red-600'
                            }`}>
                              {transaction.type === 'income' ? '+' : '-'}{formatCurrency(transaction.amount)}
                            </div>
                            <button
                              onClick={() => setEditingTransaction(transaction.id)}
                              className="p-2 text-gray-400 hover:text-indigo-600 hover:bg-indigo-50 rounded-full transition-colors mr-1"
                            >
                              <Edit3 className="w-4 h-4" />
                            </button>
                            <button
                              onClick={() => deleteTransaction(transaction.id)}
                              className="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-full transition-colors"
                            >
                              <Trash2 className="w-4 h-4" />
                            </button>
                          </div>
                        </>
                      )}
                    </div>
                  ))}
                </div>
              )}
            </div>
          </div>
        )}

        {activeTab === 'add' && (
          <div className="bg-white rounded-2xl shadow-lg p-8">
            <h3 className="text-2xl font-semibold text-gray-800 mb-6">Add New Transaction</h3>
            
            <div className="space-y-6">
              {/* Transaction Type */}
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-3">Transaction Type</label>
                <div className="flex space-x-4">
                  <button
                    onClick={() => setNewTransaction({...newTransaction, type: 'income'})}
                    className={`flex-1 py-3 px-6 rounded-xl font-medium transition-all ${
                      newTransaction.type === 'income' 
                        ? 'bg-green-600 text-white shadow-md' 
                        : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
                    }`}
                  >
                    <Plus className="w-5 h-5 inline mr-2" />
                    Income
                  </button>
                  <button
                    onClick={() => setNewTransaction({...newTransaction, type: 'expense'})}
                    className={`flex-1 py-3 px-6 rounded-xl font-medium transition-all ${
                      newTransaction.type === 'expense' 
                        ? 'bg-red-600 text-white shadow-md' 
                        : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
                    }`}
                  >
                    <Minus className="w-5 h-5 inline mr-2" />
                    Expense
                  </button>
                </div>
              </div>

              {/* Amount */}
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-2">Amount</label>
                <div className="relative">
                  <DollarSign className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
                  <input
                    type="number"
                    value={newTransaction.amount}
                    onChange={(e) => setNewTransaction({...newTransaction, amount: e.target.value})}
                    className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
                    placeholder="0.00"
                    step="0.01"
                    min="0"
                  />
                </div>
              </div>

              {/* Description */}
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-2">Description</label>
                <input
                  type="text"
                  value={newTransaction.description}
                  onChange={(e) => setNewTransaction({...newTransaction, description: e.target.value})}
                  className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
                  placeholder="What was this transaction for?"
                />
              </div>

              {/* Category */}
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-2">Category</label>
                <select
                  value={newTransaction.category}
                  onChange={(e) => setNewTransaction({...newTransaction, category: e.target.value})}
                  className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent capitalize"
                >
                  {categories[newTransaction.type].map(category => (
                    <option key={category} value={category} className="capitalize">
                      {category}
                    </option>
                  ))}
                </select>
              </div>

              {/* Submit Button */}
              <button
                onClick={addTransaction}
                className="w-full py-4 bg-indigo-600 text-white rounded-xl font-semibold hover:bg-indigo-700 transition-colors shadow-lg hover:shadow-xl"
              >
                Add Transaction
              </button>
            </div>
          </div>
        )}

        {/* Footer */}
        <div className="text-center mt-12 text-gray-500">
          <div className="flex items-center justify-center mb-2">
            <AlertCircle className="w-4 h-4 mr-2" />
            <span className="text-sm">Your data is stored locally in your browser</span>
          </div>
          <p className="text-xs">No servers, no tracking, complete privacy</p>
        </div>
      </div>
    </div>
  );
};

// Edit Transaction Form Component
const EditTransactionForm = ({ transaction, categories, onSave, onCancel }) => {
  const [editData, setEditData] = useState({
    type: transaction.type,
    amount: transaction.amount.toString(),
    description: transaction.description,
    category: transaction.category
  });

  const handleSave = () => {
    if (!editData.amount || !editData.description) {
      alert('Please fill in all fields');
      return;
    }
    
    onSave({
      ...editData,
      amount: parseFloat(editData.amount)
    });
  };

  return (
    <div className="w-full space-y-3">
      <div className="grid grid-cols-2 gap-3">
        <select
          value={editData.type}
          onChange={(e) => setEditData({...editData, type: e.target.value})}
          className="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-1 focus:ring-indigo-500"
        >
          <option value="income">Income</option>
          <option value="expense">Expense</option>
        </select>
        <input
          type="number"
          value={editData.amount}
          onChange={(e) => setEditData({...editData, amount: e.target.value})}
          className="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-1 focus:ring-indigo-500"
          step="0.01"
          min="0"
        />
      </div>
      <input
        type="text"
        value={editData.description}
        onChange={(e) => setEditData({...editData, description: e.target.value})}
        className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-1 focus:ring-indigo-500"
      />
      <div className="flex justify-between items-center">
        <select
          value={editData.category}
          onChange={(e) => setEditData({...editData, category: e.target.value})}
          className="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-1 focus:ring-indigo-500 capitalize"
        >
          {categories[editData.type].map(category => (
            <option key={category} value={category} className="capitalize">
              {category}
            </option>
          ))}
        </select>
        <div className="flex space-x-2">
          <button
            onClick={handleSave}
            className="p-2 text-green-600 hover:bg-green-50 rounded-full transition-colors"
          >
            <Check className="w-4 h-4" />
          </button>
          <button
            onClick={onCancel}
            className="p-2 text-red-600 hover:bg-red-50 rounded-full transition-colors"
          >
            <X className="w-4 h-4" />
          </button>
        </div>
      </div>
    </div>
  );
};

export default WalletApp;