DEV Community

Nebula
Nebula

Posted on

frontend3

import { useState, useEffect, useCallback } from "react";
import { Search, Filter, X, RefreshCw, AlertCircle } from "lucide-react";
import styled from 'styled-components';
import { motion, AnimatePresence } from 'framer-motion';

// Design tokens
const colors = {
  primary: '#667eea',
  primaryDark: '#5a67d8',
  secondary: '#764ba2',
  success: '#48bb78',
  error: '#ff6b6b',
  warning: '#f6ad55',
  text: '#1a202c',
  textSecondary: '#4a5568',
  textMuted: '#a0aec0',
  background: 'rgba(255, 255, 255, 0.95)',
  border: 'rgba(102, 126, 234, 0.2)',
  borderHover: '#667eea',
  glass: 'rgba(255, 255, 255, 0.95)',
  glassLight: 'rgba(255, 255, 255, 0.8)',
  shadow: 'rgba(0, 0, 0, 0.1)',
  shadowHover: 'rgba(102, 126, 234, 0.1)'
};

const gradients = {
  primary: `linear-gradient(135deg, ${colors.primary}, ${colors.secondary})`,
  success: `linear-gradient(135deg, ${colors.success}, #38b2ac)`,
  error: `linear-gradient(135deg, ${colors.error}, #ee5a52)`,
  warning: `linear-gradient(135deg, ${colors.warning}, #ff8c00)`
};

const spacing = {
  xs: '4px',
  sm: '8px',
  md: '12px',
  lg: '16px',
  xl: '20px',
  xxl: '24px',
  xxxl: '32px'
};

// Styled Components
const Container = styled(motion.div)`
  padding: ${spacing.xxxl};
  max-width: 1400px;
  margin: 0 auto;
  min-height: calc(100vh - 120px);

  h2 {
    font-size: 32px;
    font-weight: 800;
    color: ${colors.text};
    margin-bottom: ${spacing.xxxl};
    background: ${gradients.primary};
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    text-align: center;
  }

  @media (max-width: 768px) {
    padding: ${spacing.xl};

    h2 {
      font-size: 28px;
      margin-bottom: ${spacing.xxl};
    }
  }
`;

const FilterSection = styled(motion.div)`
  background: ${colors.glass};
  backdrop-filter: blur(20px);
  border-radius: 20px;
  padding: ${spacing.xxl};
  margin-bottom: ${spacing.xxxl};
  box-shadow: 0 8px 32px ${colors.shadow};
  border: 1px solid rgba(255, 255, 255, 0.2);
  position: relative;
  overflow: hidden;

  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background: ${gradients.primary};
  }
`;

const SearchContainer = styled.div`
  position: relative;
  margin-bottom: ${spacing.xl};

  svg {
    position: absolute;
    left: ${spacing.lg};
    top: 50%;
    transform: translateY(-50%);
    color: ${colors.textMuted};
    z-index: 1;
  }
`;

const SearchInput = styled.input`
  width: 100%;
  padding: ${spacing.lg} ${spacing.lg} ${spacing.lg} 48px;
  border: 2px solid ${colors.border};
  border-radius: 12px;
  font-size: 16px;
  font-weight: 500;
  color: ${colors.text};
  background: ${colors.glassLight};
  backdrop-filter: blur(10px);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);

  &:focus {
    outline: none;
    border-color: ${colors.borderHover};
    box-shadow: 0 0 0 4px ${colors.shadowHover};
    background: ${colors.background};
  }

  &::placeholder {
    color: ${colors.textMuted};
  }
`;

const FiltersGrid = styled.div`
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: ${spacing.lg};
  margin-bottom: ${spacing.xl};

  @media (max-width: 768px) {
    grid-template-columns: 1fr;
  }
`;

const FilterSelect = styled.select`
  padding: ${spacing.lg} ${spacing.xl};
  border: 2px solid ${colors.border};
  border-radius: 12px;
  font-size: 16px;
  font-weight: 500;
  color: ${colors.text};
  background: ${colors.glassLight};
  backdrop-filter: blur(10px);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  cursor: pointer;

  &:focus {
    outline: none;
    border-color: ${colors.borderHover};
    box-shadow: 0 0 0 4px ${colors.shadowHover};
    background: ${colors.background};
  }

  option {
    background: white;
    color: ${colors.text};
    padding: ${spacing.md};
  }
`;

const ButtonGroup = styled.div`
  display: flex;
  gap: ${spacing.md};
  justify-content: flex-end;

  @media (max-width: 768px) {
    justify-content: stretch;
  }
`;

const Button = styled(motion.button)`
  display: flex;
  align-items: center;
  gap: ${spacing.sm};
  padding: ${spacing.md} ${spacing.xl};
  border-radius: 12px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  border: 2px solid transparent;

  ${props => props.variant === 'primary' ? `
    background: ${gradients.primary};
    color: white;
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4);

    &:hover:not(:disabled) {
      transform: translateY(-2px);
      box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5);
    }
  ` : `
    background: transparent;
    color: ${colors.textSecondary};
    border-color: #e2e8f0;

    &:hover:not(:disabled) {
      background: #f7fafc;
      border-color: #cbd5e0;
    }
  `}

  &:active {
    transform: translateY(0);
  }

  &:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }

  @media (max-width: 768px) {
    flex: 1;
    justify-content: center;
  }
`;

const TableContainer = styled.div`
  background: ${colors.glass};
  backdrop-filter: blur(20px);
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 8px 32px ${colors.shadow};
  border: 1px solid rgba(255, 255, 255, 0.2);
  position: relative;

  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background: ${gradients.primary};
  }
`;

const Table = styled.table`
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;

  th {
    background: ${gradients.primary};
    color: white;
    padding: ${spacing.xl} ${spacing.lg};
    text-align: left;
    font-weight: 600;
    font-size: 14px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    border: none;
    white-space: nowrap;
  }

  td {
    padding: ${spacing.xl} ${spacing.lg};
    color: ${colors.textSecondary};
    font-weight: 500;
    border-bottom: 1px solid rgba(226, 232, 240, 0.5);
    vertical-align: middle;
  }

  tr:hover {
    background: ${colors.shadowHover};
  }

  tr:nth-child(even) {
    background: rgba(248, 250, 252, 0.5);
  }

  @media (max-width: 1024px) {
    font-size: 14px;

    th, td {
      padding: ${spacing.md} ${spacing.sm};
    }
  }

  @media (max-width: 768px) {
    display: block;
    overflow-x: auto;
    white-space: nowrap;
  }
`;

const StatusBadge = styled.span`
  padding: 6px ${spacing.md};
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;

  ${props => props.status === 'SUCCESS' && `
    background: linear-gradient(135deg, rgba(72, 187, 120, 0.2), rgba(56, 178, 172, 0.2));
    color: ${colors.success};
    border: 1px solid rgba(72, 187, 120, 0.3);
  `}

  ${props => props.status === 'PENDING' && `
    background: linear-gradient(135deg, rgba(255, 193, 7, 0.2), rgba(255, 152, 0, 0.2));
    color: ${colors.warning};
    border: 1px solid rgba(255, 193, 7, 0.3);
  `}

  ${props => props.status === 'FAILED' && `
    background: linear-gradient(135deg, rgba(255, 107, 107, 0.2), rgba(238, 90, 82, 0.2));
    color: ${colors.error};
    border: 1px solid rgba(255, 107, 107, 0.3);
  `}
`;

const TypeBadge = styled.span`
  padding: 6px ${spacing.md};
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;

  ${props => props.type === 'DEPOSIT' && `
    background: linear-gradient(135deg, rgba(72, 187, 120, 0.2), rgba(56, 178, 172, 0.2));
    color: ${colors.success};
    border: 1px solid rgba(72, 187, 120, 0.3);
  `}

  ${props => props.type === 'WITHDRAW' && `
    background: linear-gradient(135deg, rgba(255, 107, 107, 0.2), rgba(238, 90, 82, 0.2));
    color: ${colors.error};
    border: 1px solid rgba(255, 107, 107, 0.3);
  `}

  ${props => props.type === 'TRANSFER' && `
    background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(118, 75, 162, 0.2));
    color: ${colors.primary};
    border: 1px solid rgba(102, 126, 234, 0.3);
  `}

  ${props => props.type === 'LOAN' && `
    background: linear-gradient(135deg, rgba(255, 193, 7, 0.2), rgba(255, 152, 0, 0.2));
    color: ${colors.warning};
    border: 1px solid rgba(255, 193, 7, 0.3);
  `}
`;

const LoadingSpinner = styled(motion.div)`
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 64px;
  color: ${colors.primary};

  svg {
    animation: spin 1s linear infinite;
  }

  @keyframes spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
  }
`;

const ErrorMessage = styled(motion.div)`
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: ${spacing.lg};
  padding: 64px ${spacing.xxxl};
  text-align: center;
  color: ${colors.error};

  h3 {
    margin: 0;
    font-size: 20px;
    font-weight: 600;
  }

  p {
    margin: 0;
    color: ${colors.textSecondary};
    line-height: 1.5;
  }
`;

const EmptyState = styled(motion.div)`
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: ${spacing.lg};
  padding: 64px ${spacing.xxxl};
  text-align: center;
  color: ${colors.textMuted};

  h3 {
    margin: 0;
    font-size: 20px;
    font-weight: 600;
  }

  p {
    margin: 0;
    line-height: 1.5;
  }
`;

// Mock useAuth hook for demonstration
const useAuth = () => ({
  user: { id: 'user123' }
});

// Mock API function
const api = {
  get: async (endpoint) => {
    // Simulate API delay
    await new Promise(resolve => setTimeout(resolve, 1000));

    // Mock data
    return {
      data: [
        {
          srNo: 1,
          transactionId: "TX123456",
          date: "2023-10-01",
          type: "DEPOSIT",
          amount: 1000,
          fromAccount: "87452147890",
          toAccount: "87452147891",
          status: "SUCCESS",
          description: "Deposit to savings account",
        },
        {
          srNo: 2,
          transactionId: "TX123457",
          date: "2023-10-02",
          type: "WITHDRAW",
          amount: 500,
          fromAccount: "87452147891",
          toAccount: "",
          status: "SUCCESS",
          description: "ATM withdrawal",
        },
        {
          srNo: 3,
          transactionId: "TX123458",
          date: "2023-10-03",
          type: "TRANSFER",
          amount: 750,
          fromAccount: "87452147890",
          toAccount: "87452147892",
          status: "PENDING",
          description: "Transfer to checking account",
        },
        {
          srNo: 4,
          transactionId: "TX123459",
          date: "2023-10-04",
          type: "LOAN",
          amount: 5000,
          fromAccount: "",
          toAccount: "87452147890",
          status: "FAILED",
          description: "Personal loan disbursement",
        },
      ]
    };
  }
};

function TransactionsPage() {
  const [transactions, setTransactions] = useState([]);
  const [filteredTransactions, setFilteredTransactions] = useState([]);
  const [searchTerm, setSearchTerm] = useState("");
  const [accountFilter, setAccountFilter] = useState("");
  const [statusFilter, setStatusFilter] = useState("");
  const [typeFilter, setTypeFilter] = useState("");
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const { user } = useAuth();

  // Fetch transactions
  const fetchTransactions = useCallback(async () => {
    if (!user?.id) return;

    try {
      setLoading(true);
      setError(null);
      const response = await api.get(`/transactions/user/${user.id}`);
      setTransactions(response.data);
      setFilteredTransactions(response.data);
    } catch (err) {
      setError('Failed to load transactions. Please try again.');
      console.error('Error fetching transactions:', err);
    } finally {
      setLoading(false);
    }
  }, [user?.id]);

  // Initial load
  useEffect(() => {
    fetchTransactions();
  }, [fetchTransactions]);

  // Handle search
  const handleSearch = useCallback((e) => {
    const value = e.target.value;
    setSearchTerm(value);

    if (!value.trim()) {
      applyFilters(transactions, accountFilter, statusFilter, typeFilter);
      return;
    }

    const filtered = transactions.filter((transaction) => {
      const searchLower = value.toLowerCase();
      return (
        transaction.transactionId?.toLowerCase().includes(searchLower) ||
        transaction.fromAccount?.toLowerCase().includes(searchLower) ||
        transaction.toAccount?.toLowerCase().includes(searchLower) ||
        transaction.description?.toLowerCase().includes(searchLower)
      );
    });

    applyFilters(filtered, accountFilter, statusFilter, typeFilter);
  }, [transactions, accountFilter, statusFilter, typeFilter]);

  // Apply filters
  const applyFilters = useCallback((data, account, status, type) => {
    let filtered = [...data];

    if (account) {
      filtered = filtered.filter(
        (transaction) =>
          transaction.fromAccount?.includes(account) ||
          transaction.toAccount?.includes(account)
      );
    }

    if (status) {
      filtered = filtered.filter(
        (transaction) => transaction.status === status
      );
    }

    if (type) {
      filtered = filtered.filter(
        (transaction) => transaction.type === type
      );
    }

    setFilteredTransactions(filtered);
  }, []);

  // Handle filter changes
  useEffect(() => {
    const baseData = searchTerm.trim() 
      ? transactions.filter((transaction) => {
          const searchLower = searchTerm.toLowerCase();
          return (
            transaction.transactionId?.toLowerCase().includes(searchLower) ||
            transaction.fromAccount?.toLowerCase().includes(searchLower) ||
            transaction.toAccount?.toLowerCase().includes(searchLower) ||
            transaction.description?.toLowerCase().includes(searchLower)
          );
        })
      : transactions;

    applyFilters(baseData, accountFilter, statusFilter, typeFilter);
  }, [accountFilter, statusFilter, typeFilter, transactions, searchTerm, applyFilters]);

  // Clear all filters
  const clearFilters = () => {
    setSearchTerm("");
    setAccountFilter("");
    setStatusFilter("");
    setTypeFilter("");
    setFilteredTransactions(transactions);
  };

  // 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', {
      year: 'numeric',
      month: 'short',
      day: 'numeric'
    });
  };

  // Get unique account numbers for filter
  const uniqueAccounts = [...new Set([
    ...transactions.map(t => t.fromAccount).filter(Boolean),
    ...transactions.map(t => t.toAccount).filter(Boolean)
  ])];

  if (loading) {
    return (
      <Container
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        transition={{ duration: 0.6 }}
      >
        <h2>All Transactions</h2>
        <TableContainer>
          <LoadingSpinner
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
          >
            <RefreshCw size={32} />
            <p style={{ marginLeft: spacing.lg, fontSize: '18px', fontWeight: '500' }}>
              Loading transactions...
            </p>
          </LoadingSpinner>
        </TableContainer>
      </Container>
    );
  }

  if (error) {
    return (
      <Container
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        transition={{ duration: 0.6 }}
      >
        <h2>All Transactions</h2>
        <TableContainer>
          <ErrorMessage
            initial={{ opacity: 0, scale: 0.95 }}
            animate={{ opacity: 1, scale: 1 }}
          >
            <AlertCircle size={48} />
            <h3>Something went wrong</h3>
            <p>{error}</p>
            <Button
              variant="primary"
              onClick={fetchTransactions}
              whileHover={{ scale: 1.02 }}
              whileTap={{ scale: 0.98 }}
            >
              <RefreshCw size={16} />
              Try Again
            </Button>
          </ErrorMessage>
        </TableContainer>
      </Container>
    );
  }

  return (
    <Container
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.6 }}
    >
      <h2>All Transactions</h2>

      <FilterSection
        initial={{ opacity: 0, scale: 0.95 }}
        animate={{ opacity: 1, scale: 1 }}
        transition={{ delay: 0.1, duration: 0.5 }}
      >
        <SearchContainer>
          <Search size={20} />
          <SearchInput
            type="text"
            placeholder="Search transactions by ID, account, or description..."
            value={searchTerm}
            onChange={handleSearch}
          />
        </SearchContainer>

        <FiltersGrid>
          <FilterSelect
            value={accountFilter}
            onChange={(e) => setAccountFilter(e.target.value)}
          >
            <option value="">Filter by account number</option>
            {uniqueAccounts.map(account => (
              <option key={account} value={account}>{account}</option>
            ))}
          </FilterSelect>

          <FilterSelect
            value={statusFilter}
            onChange={(e) => setStatusFilter(e.target.value)}
          >
            <option value="">Filter by status</option>
            <option value="SUCCESS">SUCCESS</option>
            <option value="PENDING">PENDING</option>
            <option value="FAILED">FAILED</option>
          </FilterSelect>

          <FilterSelect
            value={typeFilter}
            onChange={(e) => setTypeFilter(e.target.value)}
          >
            <option value="">Filter by transaction type</option>
            <option value="DEPOSIT">DEPOSIT</option>
            <option value="WITHDRAW">WITHDRAW</option>
            <option value="TRANSFER">TRANSFER</option>
            <option value="LOAN">LOAN</option>
          </FilterSelect>
        </FiltersGrid>

        <ButtonGroup>
          <Button
            variant="secondary"
            onClick={clearFilters}
            whileHover={{ scale: 1.02 }}
            whileTap={{ scale: 0.98 }}
          >
            <X size={16} />
            Clear Filters
          </Button>
          <Button
            variant="primary"
            onClick={fetchTransactions}
            whileHover={{ scale: 1.02 }}
            whileTap={{ scale: 0.98 }}
          >
            <RefreshCw size={16} />
            Refresh
          </Button>
        </ButtonGroup>
      </FilterSection>

      <TableContainer>
        {filteredTransactions.length === 0 ? (
          <EmptyState
            initial={{ opacity: 0, scale: 0.95 }}
            animate={{ opacity: 1, scale: 1 }}
          >
            <Filter size={48} />
            <h3>No transactions found</h3>
            <p>
              {searchTerm || accountFilter || statusFilter || typeFilter
                ? "Try adjusting your search or filter criteria"
                : "No transactions available for your account"}
            </p>
          </EmptyState>
        ) : (
          <Table>
            <thead>
              <tr>
                <th>Sr No</th>
                <th>Transaction ID</th>
                <th>Date</th>
                <th>Type</th>
                <th>Amount</th>
                <th>From Account</th>
                <th>To Account</th>
                <th>Status</th>
                <th>Description</th>
              </tr>
            </thead>
            <tbody>
              {filteredTransactions.map((transaction) => (
                <motion.tr
                  key={transaction.srNo}
                  initial={{ opacity: 0, y: 10 }}
                  animate={{ opacity: 1, y: 0 }}
                  transition={{ duration: 0.3 }}
                >
                  <td>{transaction.srNo}</td>
                  <td>
                    <code style={{ 
                      background: colors.shadowHover, 
                      padding: '4px 8px', 
                      borderRadius: '6px',
                      fontSize: '14px',
                      fontWeight: '600'
                    }}>
                      {transaction.transactionId}
                    </code>
                  </td>
                  <td>{formatDate(transaction.date)}</td>
                  <td>
                    <TypeBadge type={transaction.type}>
                      {transaction.type}
                    </TypeBadge>
                  </td>
                  <td style={{ 
                    fontWeight: '600', 
                    color: transaction.type === 'WITHDRAW' ? colors.error : colors.success 
                  }}>
                    {formatCurrency(transaction.amount)}
                  </td>
                  <td>
                    {transaction.fromAccount ? (
                      <code style={{ 
                        background: colors.shadowHover, 
                        padding: '4px 8px', 
                        borderRadius: '6px',
                        fontSize: '14px',
                        fontWeight: '600'
                      }}>
                        {transaction.fromAccount}
                      </code>
                    ) : '-'}
                  </td>
                  <td>
                    {transaction.toAccount ? (
                      <code style={{ 
                        background: colors.shadowHover, 
                        padding: '4px 8px', 
                        borderRadius: '6px',
                        fontSize: '14px',
                        fontWeight: '600'
                      }}>
                        {transaction.toAccount}
                      </code>
                    ) : '-'}
                  </td>
                  <td>
                    <StatusBadge status={transaction.status}>
                      {transaction.status}
                    </StatusBadge>
                  </td>
                  <td>{transaction.description}</td>
                </motion.tr>
              ))}
            </tbody>
          </Table>
        )}
      </TableContainer>
    </Container>
  );
}

export default TransactionsPage;
Enter fullscreen mode Exit fullscreen mode

------------------- TRANSACTIONS PAGE ----------------

/* eslint-disable no-unused-vars */
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { motion } from 'framer-motion';
import { AlertCircle, Filter, RefreshCw, Search, X } from 'lucide-react';
import { useAuth } from '@/contexts/AuthContext';
import api from '@/services/api';
import { useQueryClient } from '@tanstack/react-query';
import toast from 'react-hot-toast';
import { useFeatureFlag } from '@/contexts/FeatureFlagContext';

function TransactionsPage() {
  const [transactions, setTransactions] = useState([]);
  const [filteredTransactions, setFilteredTransactions] = useState([]);
  const [searchTerm, setSearchTerm] = useState("");
  const [accountFilter, setAccountFilter] = useState("");
  const [statusFilter, setStatusFilter] = useState("");
  const [typeFilter, setTypeFilter] = useState("");
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [userAccounts, setUser Accounts] = useState([]);
  const [accountsLoading, setAccountsLoading] = useState(true);

  const { user } = useAuth();

  const { getFlag } = useFeatureFlag();
  const useFilters = getFlag('useFilters');

  // Fetch transactions
  const fetchTransactions = useCallback(async () => {
    if (!user?.id) return;

    try {
      setLoading(true);
      setError(null);
      const response = await api.get(`/transactions/user/${user.id}`);
      console.log("TRANSACTIONS", response.data.data);
      setTransactions(response.data.data);
      setFilteredTransactions(response.data.data);
    } catch (err) {
      setError('Failed to load transactions. Please try again.');
      console.error('Error fetching transactions:', err);
    } finally {
      setLoading(false);
    }
  }, [user?.id]);

  // Fetch user accounts
  const fetchUser Accounts = useCallback(async () => {
    if (!user?.id) return;

    try {
      setAccountsLoading(true);
      const response = await api.get(`/accounts/user/${user.id}`);
      setUser Accounts(response.data.data);
    } catch (err) {
      console.error("Error fetching user accounts:", err);
      toast.error("Failed to load user accounts");
    } finally {
      setAccountsLoading(false);
    }
  }, [user?.id]);

  // Initial load
  useEffect(() => {
    fetchTransactions();
    fetchUser Accounts();
  }, [fetchTransactions, fetchUser Accounts]);

  // Apply filters
  const applyFilters = useCallback((data, account, status, type) => {
    let filtered = [...data];

    if (account) {
      filtered = filtered.filter(
        (transaction) =>
          transaction.fromAccount?.accountNumber.includes(account) ||
          transaction.toAccount?.accountNumber.includes(account)
      );
    }

    if (status) {
      filtered = filtered.filter(
        (transaction) => transaction.status === status
      );
    }

    if (type) {
      filtered = filtered.filter(
        (transaction) => transaction.type === type
      );
    }

    setFilteredTransactions(filtered);
  }, []);

  // Handle search
  const handleSearch = useCallback((e) => {
    const value = e.target.value;
    setSearchTerm(value);

    if (!value.trim()) {
      applyFilters(transactions, accountFilter, statusFilter, typeFilter);
      return;
    }

    const filtered = transactions.filter((transaction) => {
      const searchLower = value.toLowerCase();
      return (
        transaction.id.toString().includes(searchLower) ||
        transaction.fromAccount?.accountNumber.toLowerCase().includes(searchLower) ||
        transaction.toAccount?.accountNumber.toLowerCase().includes(searchLower) ||
        transaction.description?.toLowerCase().includes(searchLower)
      );
    });

    applyFilters(filtered, accountFilter, statusFilter, typeFilter);
  }, [transactions, applyFilters, accountFilter, statusFilter, typeFilter]);

  // Handle filter changes
  useEffect(() => {
    const baseData = searchTerm.trim() 
      ? transactions.filter((transaction) => {
          const searchLower = searchTerm.toLowerCase();
          return (
            transaction.id?.toString().toLowerCase().includes(searchLower) ||
            transaction.fromAccount?.accountNumber.toLowerCase().includes(searchLower) ||
            transaction.toAccount?.accountNumber.toLowerCase().includes(searchLower) ||
            transaction.description?.toLowerCase().includes(searchLower)
          );
        })
      : transactions;

    applyFilters(baseData, accountFilter, statusFilter, typeFilter);
  }, [accountFilter, statusFilter, typeFilter, transactions, searchTerm, applyFilters]);

  // Clear all filters
  const clearFilters = () => {
    setSearchTerm("");
    setAccountFilter("");
    setStatusFilter("");
    setTypeFilter("");
    setFilteredTransactions(transactions);
  };

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

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

  console.log("Transactions - ", transactions);

  if (loading) {
    return (
      <Container
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        transition={{ duration: 0.6 }}
      >
        <h2>All Transactions</h2>
        <TableContainer>
          <LoadingSpinner
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
          >
            <RefreshCw size={32} />
            <p style={{ marginLeft: spacing.lg, fontSize: '18px', fontWeight: '500' }}>
              Loading transactions...
            </p>
          </LoadingSpinner>
        </TableContainer>
      </Container>
    );
  }

  if (error) {
    return (
      <Container
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        transition={{ duration: 0.6 }}
      >
        <h2>All Transactions</h2>
        <TableContainer>
          <ErrorMessage
            initial={{ opacity: 0, scale: 0.95 }}
            animate={{ opacity: 1, scale: 1 }}
          >
            <AlertCircle size={48} />
            <h3>Something went wrong</h3>
            <p>{error}</p>
            <Button
              variant="primary"
              onClick={fetchTransactions}
              whileHover={{ scale: 1.02 }}
              whileTap={{ scale: 0.98 }}
            >
              <RefreshCw size={16} />
              Try Again
            </Button>
          </ErrorMessage>
        </TableContainer>
      </Container>
    );
  }

  return (
    <Container
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.6 }}
    >
      {useFilters ? (
        <FilterSection
          initial={{ opacity: 0, scale: 0.95 }}
          animate={{ opacity: 1, scale: 1 }}
          transition={{ delay: 0.1, duration: 0.5 }}
        >
          <SearchContainer>
            <Search size={20} />
            <SearchInput
              type="text"
              placeholder="Search transactions by ID, account, or description..."
              value={searchTerm}
              onChange={handleSearch}
            />
          </SearchContainer>

          <FiltersGrid>
            <FilterSelect
              value={accountFilter}
              onChange={(e) => setAccountFilter(e.target.value)}
            >
              <option value="">Filter by account number</option>
              {(Array.isArray(userAccounts) ? userAccounts : []).map((account) => (
                <option key={account.accountNumber} value={account.accountNumber}>
                  {account.accountNumber} ({account.accountType})
                </option>
              ))}
            </FilterSelect>

            <FilterSelect
              value={statusFilter}
              onChange={(e) => setStatusFilter(e.target.value)}
            >
              <option value="">Filter by status</option>
              <option value="SUCCESS">SUCCESS</option>
              <option value="PENDING">PENDING</option>
              <option value="FAILED">FAILED</option>
            </FilterSelect>

            <FilterSelect
              value={typeFilter}
              onChange={(e) => setTypeFilter(e.target.value)}
            >
              <option value="">Filter by transaction type</option>
              <option value="DEPOSIT">DEPOSIT</option>
              <option value="WITHDRAWAL">WITHDRAW</option>
              <option value="TRANSFER">TRANSFER</option>
              <option value="LOAN">LOAN</option>
            </FilterSelect>
          </FiltersGrid>

          <ButtonGroup>
            <Button
              variant="secondary"
              onClick={clearFilters}
              whileHover={{ scale: 1.02 }}
              whileTap={{ scale: 0.98 }}
            >
              <X size={16} />
              Clear Filters
            </Button>
            <Button
              variant="primary"
              onClick={fetchTransactions}
              whileHover={{ scale: 1.02 }}
              whileTap={{ scale: 0.98 }}
            >
              <RefreshCw size={16} />
              Refresh
            </Button>
          </ButtonGroup>
        </FilterSection>
      ) : (
        <Message
          initial={{ opacity: 0, scale: 0.95 }}
          animate={{ opacity: 1, scale: 1 }}
          transition={{ delay: 0.1, duration: 0.5 }}
        >
          Filtering feature has been taken down temporarily
        </Message>
      )}

      <h2>All Transactions</h2>

      <TableContainer>
        {filteredTransactions.length === 0 ? (
          <EmptyState
            initial={{ opacity: 0, scale: 0.95 }}
            animate={{ opacity: 1, scale: 1 }}
          >
            <Filter size={48} />
            <h3>No transactions found</h3>
            <p>
              {searchTerm || accountFilter || statusFilter || typeFilter
                ? "Try adjusting your search or filter criteria"
                : "No transactions available for your account"}
            </p>
          </EmptyState>
        ) : (
          <Table>
            <thead>
              <tr>
                <th>Sr No</th>
                <th>Transaction ID</th>
                <th>Date</th>
                <th>Type</th>
                <th>Amount</th>
                <th>From Account</th>
                <th>To Account</th>
                <th>Status</th>
                <th>Description</th>
              </tr>
            </thead>
            <tbody>
              {filteredTransactions.map((transaction, idx) => (
                <motion.tr
                  key={transaction.srNo}
                  initial={{ opacity: 0, y: 10 }}
                  animate={{ opacity: 1, y: 0 }}
                  transition={{ duration: 0.3 }}
                >
                  <td>{idx + 1}</td>
                  <td>
                    <code style={{ 
                      background: colors.shadowHover, 
                      padding: '4px 8px', 
                      borderRadius: '6px',
                      fontSize: '14px',
                      fontWeight: '600'
                    }}>
                      {transaction.id}
                    </code>
                  </td>
                  <td>{formatDate(transaction.timestamp)}</td>
                  <td>
                    <TypeBadge type={transaction.type}>
                      {transaction.type}
                    </TypeBadge>
                  </td>
                  <td style={{ 
                    fontWeight: '600', 
                    color: transaction.type === 'WITHDRAW' ? colors.error : colors.success 
                  }}>
                    {formatCurrency(transaction.amount)}
                  </td>
                  <td>
                    {transaction.fromAccount ? (
                      <code style={{ 
                        background: colors.shadowHover, 
                        padding: '4px 8px', 
                        borderRadius: '6px',
                        fontSize: '14px',
                        fontWeight: '600'
                      }}>
                        {transaction.fromAccount.accountNumber}
                      </code>
                    ) : '-'}
                  </td>
                  <td>
                    {transaction.toAccount ? (
                      <code style={{ 
                        background: colors.shadowHover, 
                        padding: '4px 8px', 
                        borderRadius: '6px',
                        fontSize: '14px',
                        fontWeight: '600'
                      }}>
                        {transaction.toAccount.accountNumber}
                      </code>
                    ) : '-'}
                  </td>
                  <td>
                    <StatusBadge status={transaction.status}>
                      {transaction.status}
                    </StatusBadge>
                  </td>
                  <td>{transaction.description}</td>
                </motion.tr>
              ))}
            </tbody>
          </Table>
        )}
      </TableContainer>
    </Container>
  );
}

export default TransactionsPage;

const colors = {
  primary: "#667eea",
  primaryDark: "#5a67d8",
  secondary: "#764ba2",
  success: "#48bb78",
  error: "#ff6b6b",
  warning: "#f6ad55",
  text: "#1a202c",
  textSecondary: "#4a5568",
  textMuted: "#a0aec0",
  background: "rgba(255,255,255,0.95)",
  border: "rgba(102,126,234,0.2)",
  borderHover: "#667eea",
  glass: "rgba(255,255,255,0.95)",
  glassLight: "rgba(255,255,255,0.8)",
  shadow: "rgba(0,0,0,0.1)",
  shadowHover: "rgba(102,126,234,0.1)"
}

const gradients = {
  primary: `linear-gradient(135deg, ${colors.primary}, ${colors.secondary})`,
  success: `linear-gradient(135deg, ${colors.success}, #38b2ac)`,
  warning: `linear-gradient(135deg, ${colors.warning}, #ff8c00)`,
  error: `linear-gradient(135deg, ${colors.error}, #ee5a52)`,
}

const spacing = {
  xs: "4px",
  sm: "8px",
  md: "12px",
  lg: "16px",
  xl: "20px",
  xxl: "2px",
  xxxl: "32px",
}
Enter fullscreen mode Exit fullscreen mode

-------------------TRANSACTIONS CARD LAYOUT---------------

// Add this styled component with your other styled components
const CardsContainer = styled.div`
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
  gap: ${spacing.xl};
  padding: ${spacing.lg};
`;

const TransactionCard = styled(motion.div)`
  background: ${colors.glass};
  border: 1px solid ${colors.border};
  border-radius: 16px;
  padding: ${spacing.xl};
  backdrop-filter: blur(10px);
  box-shadow: 0 8px 32px ${colors.shadow};
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;

  &:hover {
    transform: translateY(-4px);
    box-shadow: 0 12px 40px rgba(102, 126, 234, 0.15);
    border-color: ${colors.borderHover};
  }

  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background: ${gradients.primary};
  }
`;

const CardHeader = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: ${spacing.lg};
`;

const CardTitle = styled.div`
  display: flex;
  flex-direction: column;
  gap: ${spacing.xs};
`;

const TransactionId = styled.code`
  background: ${colors.shadowHover};
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  color: ${colors.text};
  font-family: 'Monaco', 'Menlo', monospace;
`;

const Amount = styled.div`
  font-size: 24px;
  font-weight: 700;
  color: ${props => props.type === 'WITHDRAW' ? colors.error : colors.success};
  text-align: right;
`;

const CardBody = styled.div`
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: ${spacing.lg};
  margin-bottom: ${spacing.lg};
`;

const InfoGroup = styled.div`
  display: flex;
  flex-direction: column;
  gap: ${spacing.sm};
`;

const InfoLabel = styled.span`
  font-size: 12px;
  font-weight: 600;
  color: ${colors.textMuted};
  text-transform: uppercase;
  letter-spacing: 0.5px;
`;

const InfoValue = styled.span`
  font-size: 14px;
  font-weight: 500;
  color: ${colors.text};
  word-break: break-all;
`;

const AccountCode = styled.code`
  background: ${colors.shadowHover};
  padding: 4px 8px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 600;
  font-family: 'Monaco', 'Menlo', monospace;
`;

const CardFooter = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: ${spacing.md};
  border-top: 1px solid ${colors.border};
`;

const Description = styled.p`
  margin: 0;
  font-size: 14px;
  color: ${colors.textSecondary};
  font-style: italic;
  flex: 1;
  margin-right: ${spacing.md};
`;

const DateTime = styled.span`
  font-size: 12px;
  color: ${colors.textMuted};
  font-weight: 500;
`;

// Replace your table rendering section with this:
{filteredTransactions.length === 0 ? (
  <EmptyState
    initial={{ opacity: 0, scale: 0.95 }}
    animate={{ opacity: 1, scale: 1 }}
  >
    <Filter size={48} />
    <h3>No transactions found</h3>
    <p>
      {searchTerm || accountFilter || statusFilter || typeFilter
        ? "Try adjusting your search or filter criteria"
        : "No transactions available for your account"}
    </p>
  </EmptyState>
) : (
  <CardsContainer>
    {filteredTransactions.map((transaction, idx) => (
      <TransactionCard
        key={transaction.id}
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        transition={{ duration: 0.3, delay: idx * 0.05 }}
        whileHover={{ scale: 1.02 }}
      >
        <CardHeader>
          <CardTitle>
            <TransactionId>#{transaction.id}</TransactionId>
            <TypeBadge type={transaction.type}>
              {transaction.type}
            </TypeBadge>
          </CardTitle>
          <Amount type={transaction.type}>
            {formatCurrency(transaction.amount)}
          </Amount>
        </CardHeader>

        <CardBody>
          <InfoGroup>
            <InfoLabel>From Account</InfoLabel>
            <InfoValue>
              {transaction.fromAccount ? (
                <AccountCode>{transaction.fromAccount.accountNumber}</AccountCode>
              ) : (
                <span style={{ color: colors.textMuted }}>-</span>
              )}
            </InfoValue>
          </InfoGroup>

          <InfoGroup>
            <InfoLabel>To Account</InfoLabel>
            <InfoValue>
              {transaction.toAccount ? (
                <AccountCode>{transaction.toAccount.accountNumber}</AccountCode>
              ) : (
                <span style={{ color: colors.textMuted }}>-</span>
              )}
            </InfoValue>
          </InfoGroup>

          <InfoGroup>
            <InfoLabel>Status</InfoLabel>
            <InfoValue>
              <StatusBadge status={transaction.status}>
                {transaction.status}
              </StatusBadge>
            </InfoValue>
          </InfoGroup>

          <InfoGroup>
            <InfoLabel>Serial No.</InfoLabel>
            <InfoValue>#{idx + 1}</InfoValue>
          </InfoGroup>
        </CardBody>

        <CardFooter>
          <Description>
            {transaction.description || "No description provided"}
          </Description>
          <DateTime>
            {formatDate(transaction.timestamp)}
          </DateTime>
        </CardFooter>
      </TransactionCard>
    ))}
  </CardsContainer>
)}
Enter fullscreen mode Exit fullscreen mode

-------------FEATURE TOGGLE COMPONENT------------

import { useFeatureFlag } from '@/contexts/FeatureFlagContext';
import React from 'react';
import styled, { keyframes } from 'styled-components';

const ripple = keyframes`
  0% {
    transform: scale(0);
    opacity: 1;
  }
  100% {
    transform: scale(4);
    opacity: 0;
  }
`;

const ToggleContainer = styled.div`
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.5rem 0;
`;

const Label = styled.span`
  font-size: 0.9rem;
  font-weight: 600;
  color: ${({ isEnabled }) => (isEnabled ? '#1e293b' : '#64748b')};
  transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  letter-spacing: -0.025em;
  user-select: none;
`;

const ToggleButton = styled.button`
  position: relative;
  display: inline-flex;
  height: 1.875rem;
  width: 3.25rem;
  align-items: center;
  border-radius: 1.875rem;
  border: none;
  cursor: pointer;
  outline: none;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  background: ${({ isEnabled }) =>
    isEnabled
      ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
      : 'linear-gradient(135deg, #e2e8f0 0%, #cbd5e1 100%)'};
  box-shadow: ${({ isEnabled }) =>
    isEnabled
      ? '0 4px 12px rgba(102, 126, 234, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.2)'
      : '0 2px 8px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.8)'};

  &:hover {
    transform: translateY(-1px);
    box-shadow: ${({ isEnabled }) =>
      isEnabled
        ? '0 6px 16px rgba(102, 126, 234, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.2)'
        : '0 4px 12px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.8)'};
  }

  &:active {
    transform: translateY(0);
    transition: transform 0.1s;
  }

  &:focus-visible {
    outline: 2px solid #667eea;
    outline-offset: 2px;
  }

  &::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    background: rgba(255, 255, 255, 0.3);
    border-radius: inherit;
    transform: translate(-50%, -50%) scale(0);
    opacity: 0;
    transition: all 0.3s ease;
  }

  &:active::before {
    animation: ${ripple} 0.6s ease-out;
  }
`;

const ToggleTrack = styled.div`
  position: absolute;
  inset: 2px;
  border-radius: inherit;
  background: ${({ isEnabled }) =>
    isEnabled ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)'};
  transition: background 0.3s ease;
`;

const ToggleCircle = styled.span`
  position: relative;
  display: inline-block;
  height: 1.375rem;
  width: 1.375rem;
  border-radius: 50%;
  background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2), 0 1px 3px rgba(0, 0, 0, 0.1);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  transform: translateX(${({ isEnabled }) => (isEnabled ? '1.375rem' : '0.25rem')});
  z-index: 2;

  &::before {
    content: '';
    position: absolute;
    inset: 1px;
    border-radius: inherit;
    background: ${({ isEnabled }) =>
      isEnabled
        ? 'linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%)'
        : 'linear-gradient(135deg, #ffffff 0%, #f1f5f9 100%)'};
    transition: background 0.3s ease;
  }

  &::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 0.375rem;
    height: 0.375rem;
    border-radius: 50%;
    background: ${({ isEnabled }) =>
      isEnabled
        ? 'radial-gradient(circle, #667eea 0%, #764ba2 100%)'
        : 'radial-gradient(circle, #94a3b8 0%, #64748b 100%)'};
    transform: translate(-50%, -50%);
    opacity: ${({ isEnabled }) => (isEnabled ? 1 : 0.6)};
    transition: all 0.3s ease;
    z-index: 1;
  }
`;

const StatusIndicator = styled.div`
  position: absolute;
  top: -0.25rem;
  right: ${({ isEnabled }) => (isEnabled ? '-0.25rem' : 'auto')};
  left: ${({ isEnabled }) => (isEnabled ? 'auto' : '-0.25rem')};
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: ${({ isEnabled }) => (isEnabled ? '#10b981' : '#ef4444')};
  box-shadow: 0 0 0 2px white, 0 1px 3px rgba(0, 0, 0, 0.1);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  opacity: ${({ isEnabled }) => (isEnabled ? 1 : 0.8)};
`;

const FeatureToggle = ({ flagName, label }) => {
  const { getFlag, toggleFlag } = useFeatureFlag();
  const isEnabled = getFlag(flagName);

  return (
    <ToggleContainer>
      <Label isEnabled={isEnabled}>{label}</Label>
      <ToggleButton 
        onClick={() => toggleFlag(flagName)} 
        role="switch" 
        aria-checked={isEnabled}
        isEnabled={isEnabled}
      >
        <ToggleTrack isEnabled={isEnabled} />
        <ToggleCircle isEnabled={isEnabled} />
        <StatusIndicator isEnabled={isEnabled} />
      </ToggleButton>
    </ToggleContainer>
  );
};

export default FeatureToggle;
Enter fullscreen mode Exit fullscreen mode

------------------GLOBAL FEATURE FLAG IMPLEMENTATION----------------

// 1. FeatureFlagContext.js - Context for managing feature flags
import React, { createContext, useContext, useState } from 'react';

const FeatureFlagContext = createContext();

export const FeatureFlagProvider = ({ children }) => {
  const [flags, setFlags] = useState({
    useCardLayout: false, // Flag for card vs table layout
    // Add more flags here as needed
    // darkMode: false,
    // newNavigation: false,
  });

  const toggleFlag = (flagName) => {
    setFlags(prevFlags => ({
      ...prevFlags,
      [flagName]: !prevFlags[flagName]
    }));
  };

  const setFlag = (flagName, value) => {
    setFlags(prevFlags => ({
      ...prevFlags,
      [flagName]: value
    }));
  };

  const getFlag = (flagName) => {
    return flags[flagName] || false;
  };

  return (
    <FeatureFlagContext.Provider value={{
      flags,
      toggleFlag,
      setFlag,
      getFlag
    }}>
      {children}
    </FeatureFlagContext.Provider>
  );
};

export const useFeatureFlag = () => {
  const context = useContext(FeatureFlagContext);
  if (!context) {
    throw new Error('useFeatureFlag must be used within a FeatureFlagProvider');
  }
  return context;
};

// 2. FeatureToggle.js - Toggle button component for header
import React from 'react';
import { useFeatureFlag } from './FeatureFlagContext';

const FeatureToggle = ({ flagName, label, className = '' }) => {
  const { getFlag, toggleFlag } = useFeatureFlag();
  const isEnabled = getFlag(flagName);

  return (
    <div className={`flex items-center space-x-2 ${className}`}>
      <span className="text-sm font-medium text-gray-700">{label}</span>
      <button
        onClick={() => toggleFlag(flagName)}
        className={`
          relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
          ${isEnabled ? 'bg-blue-600' : 'bg-gray-200'}
        `}
        role="switch"
        aria-checked={isEnabled}
      >
        <span
          className={`
            inline-block h-4 w-4 transform rounded-full bg-white transition-transform
            ${isEnabled ? 'translate-x-6' : 'translate-x-1'}
          `}
        />
      </button>
    </div>
  );
};

export default FeatureToggle;

// 3. Header.js - Updated header component with feature toggle
import React from 'react';
import FeatureToggle from './FeatureToggle';

const Header = () => {
  return (
    <header className="bg-white shadow-sm border-b border-gray-200 px-6 py-4">
      <div className="flex justify-between items-center">
        <div className="flex items-center">
          <h1 className="text-xl font-semibold text-gray-900">Banking Dashboard</h1>
        </div>

        <div className="flex items-center space-x-4">
          {/* Other header items like user menu, notifications etc. */}
          <div className="flex items-center space-x-2">
            <span className="text-sm text-gray-500">View:</span>
            <FeatureToggle 
              flagName="useCardLayout" 
              label="Card Layout" 
            />
          </div>
        </div>
      </div>
    </header>
  );
};

export default Header;

// 4. App.js - Root component wrapped with FeatureFlagProvider
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { FeatureFlagProvider } from './FeatureFlagContext';
import Layout from './Layout';
import DataPage from './DataPage';
// Import other pages

function App() {
  return (
    <FeatureFlagProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Layout />}>
            <Route path="data" element={<DataPage />} />
            {/* Other routes */}
          </Route>
        </Routes>
      </Router>
    </FeatureFlagProvider>
  );
}

export default App;

// 5. Layout.js - Main layout component
import React from 'react';
import { Outlet } from 'react-router-dom';
import Header from './Header';
import Sidebar from './Sidebar';

const Layout = () => {
  return (
    <div className="flex h-screen bg-gray-50">
      <Sidebar />
      <div className="flex-1 flex flex-col overflow-hidden">
        <Header />
        <main className="flex-1 overflow-auto p-6">
          <Outlet />
        </main>
      </div>
    </div>
  );
};

export default Layout;

// 6. DataPage.js - Example page that uses the feature flag
import React from 'react';
import { useFeatureFlag } from './FeatureFlagContext';

const DataPage = () => {
  const { getFlag } = useFeatureFlag();
  const useCardLayout = getFlag('useCardLayout');

  // Sample data
  const data = [
    { id: 1, name: 'John Doe', account: '****1234', balance: '$5,000', status: 'Active' },
    { id: 2, name: 'Jane Smith', account: '****5678', balance: '$12,500', status: 'Active' },
    { id: 3, name: 'Bob Johnson', account: '****9012', balance: '$2,750', status: 'Pending' },
    { id: 4, name: 'Alice Brown', account: '****3456', balance: '$8,900', status: 'Active' },
  ];

  // Table View Component
  const TableView = () => (
    <div className="bg-white shadow rounded-lg overflow-hidden">
      <div className="px-6 py-4 border-b border-gray-200">
        <h2 className="text-lg font-medium text-gray-900">Account Data - Table View</h2>
      </div>
      <div className="overflow-x-auto">
        <table className="min-w-full divide-y divide-gray-200">
          <thead className="bg-gray-50">
            <tr>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                Name
              </th>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                Account
              </th>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                Balance
              </th>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                Status
              </th>
            </tr>
          </thead>
          <tbody className="bg-white divide-y divide-gray-200">
            {data.map((item) => (
              <tr key={item.id} className="hover:bg-gray-50">
                <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
                  {item.name}
                </td>
                <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                  {item.account}
                </td>
                <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
                  {item.balance}
                </td>
                <td className="px-6 py-4 whitespace-nowrap">
                  <span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
                    item.status === 'Active' 
                      ? 'bg-green-100 text-green-800' 
                      : 'bg-yellow-100 text-yellow-800'
                  }`}>
                    {item.status}
                  </span>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );

  // Card View Component
  const CardView = () => (
    <div>
      <div className="mb-6">
        <h2 className="text-lg font-medium text-gray-900">Account Data - Card View</h2>
      </div>
      <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
        {data.map((item) => (
          <div key={item.id} className="bg-white shadow rounded-lg p-6 hover:shadow-md transition-shadow">
            <div className="flex items-center justify-between mb-4">
              <h3 className="text-lg font-medium text-gray-900">{item.name}</h3>
              <span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
                item.status === 'Active' 
                  ? 'bg-green-100 text-green-800' 
                  : 'bg-yellow-100 text-yellow-800'
              }`}>
                {item.status}
              </span>
            </div>
            <div className="space-y-2">
              <div className="flex justify-between">
                <span className="text-sm text-gray-500">Account:</span>
                <span className="text-sm font-medium text-gray-900">{item.account}</span>
              </div>
              <div className="flex justify-between">
                <span className="text-sm text-gray-500">Balance:</span>
                <span className="text-sm font-medium text-gray-900">{item.balance}</span>
              </div>
            </div>
            <div className="mt-4 pt-4 border-t border-gray-200">
              <button className="text-blue-600 hover:text-blue-800 text-sm font-medium">
                View Details →
              </button>
            </div>
          </div>
        ))}
      </div>
    </div>
  );

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-bold text-gray-900">Customer Accounts</h1>
        <div className="text-sm text-gray-500">
          Current view: {useCardLayout ? 'Card Layout' : 'Table Layout'}
        </div>
      </div>

      {/* Conditional rendering based on feature flag */}
      {useCardLayout ? <CardView /> : <TableView />}
    </div>
  );
};

export default DataPage;

// 7. Sidebar.js - Example sidebar component
import React from 'react';
import { Link, useLocation } from 'react-router-dom';

const Sidebar = () => {
  const location = useLocation();

  const menuItems = [
    { path: '/', label: 'Dashboard', icon: '📊' },
    { path: '/data', label: 'Accounts', icon: '👥' },
    { path: '/transactions', label: 'Transactions', icon: '💳' },
    { path: '/settings', label: 'Settings', icon: '⚙️' },
  ];

  return (
    <div className="bg-gray-800 w-64 flex flex-col">
      <div className="flex items-center justify-center h-16 bg-gray-900">
        <span className="text-white text-lg font-semibold">BankApp</span>
      </div>
      <nav className="flex-1 px-4 py-6 space-y-2">
        {menuItems.map((item) => (
          <Link
            key={item.path}
            to={item.path}
            className={`flex items-center px-4 py-2 text-sm rounded-lg transition-colors ${
              location.pathname === item.path
                ? 'bg-gray-700 text-white'
                : 'text-gray-300 hover:bg-gray-700 hover:text-white'
            }`}
          >
            <span className="mr-3">{item.icon}</span>
            {item.label}
          </Link>
        ))}
      </nav>
    </div>
  );
};

export default Sidebar;
Enter fullscreen mode Exit fullscreen mode

------------------------ TRANSACTIONS IN THE PROFILE PAGE----------------

  1. Additional Imports (at the top with other imports)
import { useState } from 'react';
import { FaTimes } from 'react-icons/fa';
Enter fullscreen mode Exit fullscreen mode
  1. New Styled Components (add after the existing styled components)
const ModalOverlay = styled(motion.div)`
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 20px;
`;

const ModalContent = styled(motion.div)`
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(20px);
  border-radius: 20px;
  padding: 32px;
  width: 90%;
  max-width: 1000px;
  max-height: 80vh;
  overflow-y: auto;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(255, 255, 255, 0.3);
  position: relative;
`;

const ModalHeader = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 24px;
  padding-bottom: 16px;
  border-bottom: 2px solid rgba(102, 126, 234, 0.1);

  h3 {
    font-size: 24px;
    font-weight: 700;
    background: linear-gradient(135deg, #667eea, #764ba2);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    margin: 0;
  }
`;

const CloseButton = styled(motion.button)`
  background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
  border: 1px solid rgba(102, 126, 234, 0.2);
  border-radius: 50%;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: #667eea;
  transition: all 0.3s ease;

  &:hover {
    background: linear-gradient(135deg, #667eea, #764ba2);
    color: white;
    transform: rotate(90deg);
  }
`;

const TransactionTable = styled(motion.table)`
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
`;

const StatusBadge = styled.span`
  padding: 6px 12px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;

  ${props => {
    switch (props.status) {
      case 'SUCCESS':
        return `
          background: rgba(34, 197, 94, 0.1);
          color: #059669;
          border: 1px solid rgba(34, 197, 94, 0.2);
        `;
      case 'FAILURE':
        return `
          background: rgba(239, 68, 68, 0.1);
          color: #dc2626;
          border: 1px solid rgba(239, 68, 68, 0.2);
        `;
      case 'PENDING':
        return `
          background: rgba(245, 158, 11, 0.1);
          color: #d97706;
          border: 1px solid rgba(245, 158, 11, 0.2);
        `;
      default:
        return `
          background: rgba(156, 163, 175, 0.1);
          color: #6b7280;
          border: 1px solid rgba(156, 163, 175, 0.2);
        `;
    }
  }}
`;

const TypeBadge = styled.span`
  padding: 6px 12px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;

  ${props => {
    switch (props.type) {
      case 'DEPOSIT':
        return `
          background: rgba(34, 197, 94, 0.1);
          color: #059669;
          border: 1px solid rgba(34, 197, 94, 0.2);
        `;
      case 'WITHDRAWAL':
        return `
          background: rgba(239, 68, 68, 0.1);
          color: #dc2626;
          border: 1px solid rgba(239, 68, 68, 0.2);
        `;
      case 'TRANSFER':
        return `
          background: rgba(59, 130, 246, 0.1);
          color: #2563eb;
          border: 1px solid rgba(59, 130, 246, 0.2);
        `;
      case 'LOAN':
        return `
          background: rgba(168, 85, 247, 0.1);
          color: #7c3aed;
          border: 1px solid rgba(168, 85, 247, 0.2);
        `;
      default:
        return `
          background: rgba(156, 163, 175, 0.1);
          color: #6b7280;
          border: 1px solid rgba(156, 163, 175, 0.2);
        `;
    }
  }}
`;

const EmptyState = styled(motion.div)`
  text-align: center;
  padding: 60px 20px;
  color: #6b7280;

  h4 {
    font-size: 20px;
    font-weight: 600;
    margin-bottom: 8px;
    color: #374151;
  }

  p {
    font-size: 16px;
    margin: 0;
  }
`;
Enter fullscreen mode Exit fullscreen mode
  1. State Management (add inside the Profile component, after the existing useQuery)
const [selectedAccount, setSelectedAccount] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);

const { data: transactions, isLoading: isTransactionsLoading } = useQuery({
  queryKey: ['transactions', selectedAccount],
  queryFn: async () => {
    if (!selectedAccount) return [];
    try {
      const response = await api.get(`/transactions/${selectedAccount}`);
      return response.data.data;
    } catch (error) {
      console.error('Error fetching transactions', error);
      toast.error("Error fetching transactions");
      return [];
    }
  },
  enabled: !!selectedAccount
});
Enter fullscreen mode Exit fullscreen mode
  1. Helper Functions (add inside the Profile component)
const openTransactionModal = (accountNumber) => {
  setSelectedAccount(accountNumber);
  setIsModalOpen(true);
};

const closeTransactionModal = () => {
  setIsModalOpen(false);
  setSelectedAccount(null);
};

const formatTransactionAmount = (amount, type) => {
  const formattedAmount = formatCurrency(amount);
  if (type === 'DEPOSIT') return `+${formattedAmount}`;
  if (type === 'WITHDRAWAL') return `-${formattedAmount}`;
  return formattedAmount;
};
Enter fullscreen mode Exit fullscreen mode
  1. Update the View Transactions Button (replace the existing ActionLink for transactions)
<ActionLink 
  variants={actionLinkVariants} 
  onClick={() => openTransactionModal(account.accountNumber)}
>
  <FaEye /> View Transactions
</ActionLink>
Enter fullscreen mode Exit fullscreen mode
  1. Add the Modal (add this right before the closing tag)
{isModalOpen && (
  <ModalOverlay
    initial={{ opacity: 0 }}
    animate={{ opacity: 1 }}
    exit={{ opacity: 0 }}
    onClick={closeTransactionModal}
  >
    <ModalContent
      initial={{ opacity: 0, scale: 0.8, y: 50 }}
      animate={{ opacity: 1, scale: 1, y: 0 }}
      exit={{ opacity: 0, scale: 0.8, y: 50 }}
      onClick={(e) => e.stopPropagation()}
    >
      <ModalHeader>
        <h3>Transaction History - {selectedAccount}</h3>
        <CloseButton
          whileHover={{ scale: 1.1 }}
          whileTap={{ scale: 0.9 }}
          onClick={closeTransactionModal}
        >
          <FaTimes />
        </CloseButton>
      </ModalHeader>

      {isTransactionsLoading ? (
        <div style={{ textAlign: 'center', padding: '40px' }}>
          Loading transactions...
        </div>
      ) : transactions?.length === 0 ? (
        <EmptyState
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
        >
          <h4>No Transactions Found</h4>
          <p>This account has no transaction history yet.</p>
        </EmptyState>
      ) : (
        <TransactionTable
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
        >
          <thead>
            <tr>
              <TableHeader>Date</TableHeader>
              <TableHeader>Type</TableHeader>
              <TableHeader>From Account</TableHeader>
              <TableHeader>To Account</TableHeader>
              <TableHeader>Amount</TableHeader>
              <TableHeader>Description</TableHeader>
              <TableHeader>Status</TableHeader>
            </tr>
          </thead>
          <tbody>
            {transactions?.map((transaction) => (
              <TableRow key={transaction.id}>
                <TableCell>{formatDate(transaction.timestamp)}</TableCell>
                <TableCell>
                  <TypeBadge type={transaction.type}>
                    {transaction.type}
                  </TypeBadge>
                </TableCell>
                <TableCell>
                  {transaction.fromAccount ? (
                    <code style={{
                      background: 'rgba(102,126,234,0.1)',
                      padding: '4px 8px',
                      borderRadius: '6px',
                      fontSize: '12px'
                    }}>
                      {transaction.fromAccount.accountNumber}
                    </code>
                  ) : (
                    <span style={{ color: '#9ca3af', fontStyle: 'italic' }}>
                      External
                    </span>
                  )}
                </TableCell>
                <TableCell>
                  {transaction.toAccount ? (
                    <code style={{
                      background: 'rgba(102,126,234,0.1)',
                      padding: '4px 8px',
                      borderRadius: '6px',
                      fontSize: '12px'
                    }}>
                      {transaction.toAccount.accountNumber}
                    </code>
                  ) : (
                    <span style={{ color: '#9ca3af', fontStyle: 'italic' }}>
                      External
                    </span>
                  )}
                </TableCell>
                <TableCell>
                  <span style={{ 
                    fontWeight: 'bold',
                    color: transaction.type === 'DEPOSIT' ? '#059669' : 
                           transaction.type === 'WITHDRAWAL' ? '#dc2626' : '#374151'
                  }}>
                    {formatTransactionAmount(transaction.amount, transaction.type)}
                  </span>
                </TableCell>
                <TableCell>{transaction.description || 'No description'}</TableCell>
                <TableCell>
                  <StatusBadge status={transaction.status}>
                    {transaction.status}
                  </StatusBadge>
                </TableCell>
              </TableRow>
            ))}
          </tbody>
        </TransactionTable>
      )}
    </ModalContent>
  </ModalOverlay>
)}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)