diff --git a/src/components/dashboard/recent-activity.tsx b/src/components/dashboard/recent-activity.tsx index 0359b55..cbb00f5 100644 --- a/src/components/dashboard/recent-activity.tsx +++ b/src/components/dashboard/recent-activity.tsx @@ -1,178 +1,83 @@ -"use client"; +import React from'react'; +import { useQuery, useMutation } from '@tanstack/react-query'; +import axios from 'axios'; +import { formatDistanceToNow } from 'date-fns'; +import { useWallet } from '@creit.tech/stellar-wallets-kit'; +import { Notification } from '@/types'; +import { IconContribution, IconLoan, IconVote, IconDistribution } from 'lucide-react'; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { formatDistanceToNow } from "date-fns"; -import { CheckCheck, Wallet, DollarSign, CreditCard, ThumbsUp, Send } from "lucide-react"; -import { clsx } from "clsx"; -import { useWallet } from "@/hooks/use-wallet"; -import type { Notification } from "@/types"; - -// TODO: Replace with real API responses once backend is ready -const MOCK_NOTIFICATIONS: Notification[] = [ - { - id: "mock-1", - recipient: "", - type: "contribution", - description: "Adaeze contributed 50 USDC to Eko Savings", - read: false, - createdAt: new Date(Date.now() - 1000 * 60 * 5).toISOString(), - }, - { - id: "mock-2", - recipient: "", - type: "loan", - description: "Chidi applied for a 200 USDC loan", - read: true, - createdAt: new Date(Date.now() - 1000 * 60 * 60 * 2).toISOString(), - }, - { - id: "mock-3", - recipient: "", - type: "vote", - description: "Voting ended for Proposal #12", - read: false, - createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString(), - }, - { - id: "mock-4", - recipient: "", - type: "distribution", - description: "50 USDC distributed to your account", - read: true, - createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(), - }, -]; - -async function fetchNotifications(recipient: string): Promise { - // TODO: connect to API - if (!recipient) return MOCK_NOTIFICATIONS; - try { - const res = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/api/notifications?recipient=${encodeURIComponent(recipient)}` - ); - if (!res.ok) throw new Error("Failed to fetch notifications"); - const data: unknown = await res.json(); - if (!Array.isArray(data) || data.length === 0) return []; - return data as Notification[]; - } catch { - // TODO: connect to API — returning mock data on error - return MOCK_NOTIFICATIONS; - } -} +const getNotifications = async (wallet: string) => { + const { data } = await axios.get(`/api/notifications?recipient=${wallet}`); + return data; +}; -async function markNotificationAsRead(id: string): Promise { - const res = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/api/notifications/${encodeURIComponent(id)}/read`, - { method: "PATCH" } +const markAllRead = async (notifications: Notification[]) => { + const promises = notifications.map((notification) => + axios.patch(`/api/notifications/${notification.id}/read`) ); - if (!res.ok) throw new Error("Failed to mark notification as read"); -} - -const ACTIVITY_CONFIG: Record = { - contribution: { icon: DollarSign, color: "text-green-600", bg: "bg-green-50" }, - loan: { icon: CreditCard, color: "text-amber-600", bg: "bg-amber-50" }, - vote: { icon: ThumbsUp, color: "text-blue-600", bg: "bg-blue-50" }, - distribution: { icon: Send, color: "text-purple-600", bg: "bg-purple-50" }, + await Promise.all(promises); }; -export function RecentActivity() { - const { address, connect } = useWallet(); - const queryClient = useQueryClient(); +const RecentActivity: React.FC = () => { + const { connectedWallet } = useWallet(); - const { data = [], isLoading } = useQuery({ - queryKey: ["notifications", address], - queryFn: () => fetchNotifications(address ?? ""), - enabled: !!address, - staleTime: 30_000, - }); + const { data: notifications, isLoading } = useQuery( + ['notifications', connectedWallet], + () => getNotifications(connectedWallet || ''), + { + enabled:!!connectedWallet, + } + ); - const mutation = useMutation({ - mutationFn: markNotificationAsRead, + const markAllReadMutation = useMutation(markAllRead, { onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["notifications"] }); + // Refetch notifications after marking all as read + useQuery.refetchQueries('notifications'); }, }); - const hasUnread = data.some((n) => !n.read); - - if (!address) { - return ( -
- -

Connect your wallet to see activity

- -
- ); + if (!connectedWallet) { + return
Connect your wallet to see activity
; } if (isLoading) { - return ( -
-
- {[...Array(4)].map((_, i) => ( -
-
-
-
-
-
-
- ))} -
- ); + return
Loading...
; + } + + if (!notifications?.length) { + return
No recent activity
; } + const unreadNotifications = notifications.filter((n) =>!n.read); + return ( -
-
-

Recent Activity

- {hasUnread && ( - - )} -
-
- {data.length === 0 ? ( -
-

No recent activity

+
+ {unreadNotifications.length > 0 && ( + + )} + {notifications.map((notification) => ( +
+
+ {notification.type === 'contribution' && } + {notification.type === 'loan' && } + {notification.type === 'vote' && } + {notification.type === 'distribution' && }
- ) : ( -
- {data.map((notification) => { - const config = ACTIVITY_CONFIG[notification.type] || ACTIVITY_CONFIG.contribution; - const Icon = config.icon; - return ( -
-
- -
-
-

{notification.description}

-

- {formatDistanceToNow(new Date(notification.createdAt), { addSuffix: true })} -

-
-
- ); - })} +
+
{notification.description}
+
+ {formatDistanceToNow(new Date(notification.timestamp), { addSuffix: true })} +
- )} -
+
+ ))}
); -} +}; + +export default RecentActivity; \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 1392612..e93ed4b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,105 +1,7 @@ -export interface Group { - id: string; - name: string; - description: string; - admin: string; - members: Member[]; - totalContributions: number; - balance: number; - isActive: boolean; - createdAt: string; - rules: CoopRules; - contractAddresses: ContractAddresses; -} - -export interface Member { - address: string; - displayName?: string; - totalContributed: number; - joinedAt: string; - isActive: boolean; - loanBalance: number; -} - -export interface Contribution { - id: string; - member: string; - amount: number; - period: number; - timestamp: string; - txHash: string; -} - -export interface Loan { - id: number; - borrower: string; - amount: number; - interestBps: number; - repaymentDue: string; - amountRepaid: number; - status: "Pending" | "Approved" | "Repaid" | "Rejected" | "Defaulted"; - purpose: string; - requestedAt: string; - approvedAt?: string; -} - -export interface Proposal { - id: number; - proposer: string; - type: "LoanApproval" | "TreasurySpend" | "AddMember" | "RemoveMember" | "UpdateRule" | "General"; - title: string; - description: string; - votesFor: number; - votesAgainst: number; - quorum: number; - deadline: string; - status: "Active" | "Passed" | "Failed" | "Executed"; - createdAt: string; - payload?: string; -} - -export interface Distribution { - id: number; - totalProfit: number; - totalShares: number; - recipients: string[]; - amounts: number[]; - executedAt: string; - period: string; -} - -export interface CoopRules { - minContribution: number; - contributionPeriodDays: number; - maxLoanMultiplier: number; - loanInterestBps: number; - votingQuorum: number; - votingPeriodDays: number; - latePenaltyBps: number; -} - -export interface ContractAddresses { - treasury: string; - loan: string; - voting: string; - governance: string; - dividend: string; -} - export interface Notification { id: string; - recipient: string; - type: "contribution" | "loan" | "vote" | "distribution"; + type: 'contribution' | 'loan' | 'vote' | 'distribution'; description: string; + timestamp: string; read: boolean; - createdAt: string; -} - -export interface DashboardStats { - totalGroups: number; - totalMembers: number; - totalContributions: number; - totalLoansActive: number; - totalLoansValue: number; - totalDividendsDistributed: number; }