feat: remove OTP gate from transactions, fix categories auth, add implementation plan
- Remove OtpGateGuard from transactions controller (OTP verified at login) - Fix categories controller to use authenticated user instead of TEMP_USER_ID - Add comprehensive implementation plan document - Update .env.example with WEB_APP_URL - Prepare for admin dashboard development
This commit is contained in:
@@ -18,7 +18,8 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { Plus, Search, Edit, Trash2, Wallet } from "lucide-react"
|
||||
import { Plus, Search, Edit, Trash2, Wallet, Filter, X } from "lucide-react"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import axios from "axios"
|
||||
import { WalletDialog } from "@/components/dialogs/WalletDialog"
|
||||
import {
|
||||
@@ -50,7 +51,9 @@ export function Wallets() {
|
||||
const [wallets, setWallets] = useState<Wallet[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
const [kindFilter, setKindFilter] = useState<string>("all")
|
||||
const [currencyFilter, setCurrencyFilter] = useState<string>("all")
|
||||
const [showFilters, setShowFilters] = useState(false)
|
||||
const [walletDialogOpen, setWalletDialogOpen] = useState(false)
|
||||
const [editingWallet, setEditingWallet] = useState<Wallet | null>(null)
|
||||
|
||||
@@ -90,14 +93,21 @@ export function Wallets() {
|
||||
setEditingWallet(null)
|
||||
}
|
||||
|
||||
const clearFilters = () => {
|
||||
setSearchTerm("")
|
||||
setKindFilter("all")
|
||||
setCurrencyFilter("all")
|
||||
}
|
||||
|
||||
// Filter wallets
|
||||
const filteredWallets = useMemo(() => {
|
||||
return wallets.filter(wallet => {
|
||||
const matchesSearch = wallet.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
const matchesCurrency = currencyFilter === "all" || wallet.currency === currencyFilter
|
||||
return matchesSearch && matchesCurrency
|
||||
const matchesKind = kindFilter === "all" || wallet.kind === kindFilter
|
||||
const matchesCurrency = currencyFilter === "all" || wallet.currency === currencyFilter || wallet.unit === currencyFilter
|
||||
return matchesSearch && matchesKind && matchesCurrency
|
||||
})
|
||||
}, [wallets, searchTerm, currencyFilter])
|
||||
}, [wallets, searchTerm, kindFilter, currencyFilter])
|
||||
|
||||
// Get unique currencies for filter
|
||||
const availableCurrencies = useMemo(() => {
|
||||
@@ -147,6 +157,10 @@ export function Wallets() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2 sm:flex-shrink-0">
|
||||
<Button variant="outline" onClick={() => setShowFilters(!showFilters)}>
|
||||
<Filter className="mr-2 h-4 w-4" />
|
||||
{showFilters ? 'Hide Filters' : 'Show Filters'}
|
||||
</Button>
|
||||
<Button onClick={() => setWalletDialogOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Add Wallet
|
||||
@@ -196,46 +210,113 @@ export function Wallets() {
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Filters</CardTitle>
|
||||
{showFilters && (
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-base">Filters</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={clearFilters}
|
||||
className="h-8 text-xs"
|
||||
>
|
||||
<X className="h-3 w-3 mr-1" />
|
||||
Clear All
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-1">
|
||||
<CardContent className="space-y-4">
|
||||
{/* Row 1: Search, Type, Currency */}
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
{/* Search */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-medium text-muted-foreground">Search Wallet</Label>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search wallets..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-8"
|
||||
className="pl-9 h-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Select value={currencyFilter} onValueChange={setCurrencyFilter}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Filter by currency" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Currencies</SelectItem>
|
||||
{availableCurrencies.map(currency => (
|
||||
<SelectItem key={currency} value={currency}>
|
||||
{currency}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* Type Filter */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-medium text-muted-foreground">Type</Label>
|
||||
<Select value={kindFilter} onValueChange={setKindFilter}>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectValue placeholder="All types" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Types</SelectItem>
|
||||
<SelectItem value="money">Money</SelectItem>
|
||||
<SelectItem value="asset">Asset</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Currency Filter */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-medium text-muted-foreground">Currency/Unit</Label>
|
||||
<Select value={currencyFilter} onValueChange={setCurrencyFilter}>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectValue placeholder="All currencies" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Currencies/Units</SelectItem>
|
||||
{availableCurrencies.map(currency => (
|
||||
<SelectItem key={currency} value={currency}>
|
||||
{currency}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Active Filters Badge */}
|
||||
{(searchTerm || kindFilter !== "all" || currencyFilter !== "all") && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{searchTerm && (
|
||||
<div className="inline-flex items-center gap-1 px-2.5 py-1 bg-primary/10 text-primary rounded-md text-xs">
|
||||
<Search className="h-3 w-3" />
|
||||
{searchTerm}
|
||||
<button onClick={() => setSearchTerm("")} className="ml-1 hover:bg-primary/20 rounded-full p-0.5">
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{kindFilter !== "all" && (
|
||||
<div className="inline-flex items-center gap-1 px-2.5 py-1 bg-primary/10 text-primary rounded-md text-xs">
|
||||
Type: {kindFilter === "money" ? "Money" : "Asset"}
|
||||
<button onClick={() => setKindFilter("all")} className="ml-1 hover:bg-primary/20 rounded-full p-0.5">
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{currencyFilter !== "all" && (
|
||||
<div className="inline-flex items-center gap-1 px-2.5 py-1 bg-primary/10 text-primary rounded-md text-xs">
|
||||
Currency: {currencyFilter}
|
||||
<button onClick={() => setCurrencyFilter("all")} className="ml-1 hover:bg-primary/20 rounded-full p-0.5">
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Wallets Table */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Wallets ({filteredWallets.length})</CardTitle>
|
||||
<CardDescription>
|
||||
{searchTerm || currencyFilter !== "all"
|
||||
{filteredWallets.length !== wallets.length
|
||||
? `Filtered from ${wallets.length} total wallets`
|
||||
: "All your wallets"
|
||||
}
|
||||
@@ -246,8 +327,8 @@ export function Wallets() {
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Currency/Unit</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Created</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
@@ -256,7 +337,7 @@ export function Wallets() {
|
||||
{filteredWallets.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} className="text-center py-8">
|
||||
{searchTerm || currencyFilter !== "all"
|
||||
{filteredWallets.length !== wallets.length
|
||||
? "No wallets match your filters"
|
||||
: "No wallets found. Create your first wallet!"
|
||||
}
|
||||
@@ -266,6 +347,13 @@ export function Wallets() {
|
||||
filteredWallets.map((wallet) => (
|
||||
<TableRow key={wallet.id}>
|
||||
<TableCell className="font-medium text-nowrap">{wallet.name}</TableCell>
|
||||
<TableCell>
|
||||
{wallet.kind === 'money' ? (
|
||||
<Badge variant="outline" className="text-nowrap">{wallet.currency}</Badge>
|
||||
) : (
|
||||
<Badge variant="outline" className="text-nowrap">{wallet.unit}</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant="outline"
|
||||
@@ -274,13 +362,6 @@ export function Wallets() {
|
||||
{wallet.kind}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{wallet.kind === 'money' ? (
|
||||
<Badge variant="outline" className="text-nowrap">{wallet.currency}</Badge>
|
||||
) : (
|
||||
<Badge variant="outline" className="text-nowrap">{wallet.unit}</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{new Date(wallet.createdAt).toLocaleDateString()}
|
||||
</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user