diff --git a/app/action/[userId]/page.tsx b/app/action/[userId]/page.tsx index 0e96607..9ad730a 100644 --- a/app/action/[userId]/page.tsx +++ b/app/action/[userId]/page.tsx @@ -159,6 +159,7 @@ function ContributeFlow({ contribution, onSuccess }: { contribution: MyContribut const [step, setStep] = useState('idle'); const [amount, setAmount] = useState(''); const [pin, setPin] = useState(''); + const [isAnonymous, setIsAnonymous] = useState(false); const fixedAmount = Number(contribution.amountPerMember); @@ -170,16 +171,16 @@ function ContributeFlow({ contribution, onSuccess }: { contribution: MyContribut } setStep('loading'); try { - await contributeToGroup(contribution.groupId, contribution.id, payAmount, pin); + await contributeToGroup(contribution.groupId, contribution.id, payAmount, pin, isAnonymous); onSuccess(payAmount); - setStep('idle'); setPin(''); setAmount(''); + setStep('idle'); setPin(''); setAmount(''); setIsAnonymous(false); } catch (err: any) { alert(err?.response?.data?.message || 'Contribution failed'); setStep(contribution.type === 'fixed' ? 'enter_pin' : 'enter_amount'); } }; - const cancel = () => { setStep('idle'); setPin(''); setAmount(''); }; + const cancel = () => { setStep('idle'); setPin(''); setAmount(''); setIsAnonymous(false); }; if (step === 'idle') return ( - +
+ +
+
+
PIN
+ setPin(e.target.value.replace(/\D/g, '').slice(0, 4))} + className="h-8 text-xs w-full sm:w-20 min-w-0 tracking-widest" autoFocus /> +
+
+ + +
); @@ -287,7 +294,7 @@ function ContributionCard({ contribution: initial, currentUserId }: { contributi const progress = goal > 0 ? Math.min((collected / goal) * 100, 100) : 0; const isActive = c.status === 'active'; const hasPaid = !!c.myPayment; - const canContribute = isActive && !hasPaid; + const canContribute = isActive; const isDeadlinePast = c.deadline && new Date(c.deadline) < new Date(); useEffect(() => { @@ -1845,7 +1852,7 @@ const ActionsByAccountPage = () => { {/* ── ROW 3: Public Campaigns ── */} {!isViewingAnotherUser && (
-
+
My Campaigns {visibleCampaigns.length} @@ -1854,7 +1861,7 @@ const ActionsByAccountPage = () => { {activeCampaigns.length} active )} -
+
{(['all', 'active', 'closed'] as const).map((f) => (
@@ -195,7 +195,7 @@ export function ContactDetailsPanel({ contact, isOpen, onClose }: ContactDetails className="h-5 w-5 p-0 rounded-full hover:bg-gray-100 dark:hover:bg-darkBg-hover" title="Manage tags" > - +
diff --git a/app/contacts/components/ContactsTable.tsx b/app/contacts/components/ContactsTable.tsx index de1ced5..b574cb0 100644 --- a/app/contacts/components/ContactsTable.tsx +++ b/app/contacts/components/ContactsTable.tsx @@ -85,7 +85,7 @@ export function ContactsTable({ contacts, isLoading, onSelect }: ContactsTablePr if (isLoading) { return ( -
+
Loading contacts...
); @@ -93,9 +93,9 @@ export function ContactsTable({ contacts, isLoading, onSelect }: ContactsTablePr if (contacts.length === 0) { return ( -
+
-
+
?
diff --git a/app/contacts/components/PendingRequestsTable.tsx b/app/contacts/components/PendingRequestsTable.tsx index f563e9c..fc69ad8 100644 --- a/app/contacts/components/PendingRequestsTable.tsx +++ b/app/contacts/components/PendingRequestsTable.tsx @@ -47,7 +47,7 @@ export function PendingRequestsTable({ requests, isLoading }: PendingRequestsTab if (isLoading) { return ( -
+
Loading sent requests...
); @@ -55,9 +55,9 @@ export function PendingRequestsTable({ requests, isLoading }: PendingRequestsTab if (requests.length === 0) { return ( -
+
-
+
Inbox
diff --git a/app/contacts/components/SentRequestsTable.tsx b/app/contacts/components/SentRequestsTable.tsx index 37f302c..5690104 100644 --- a/app/contacts/components/SentRequestsTable.tsx +++ b/app/contacts/components/SentRequestsTable.tsx @@ -14,7 +14,7 @@ export function SentRequestsTable({ requests, isLoading }: SentRequestsTableProp if (isLoading) { return ( -
+
Loading pending requests...
); @@ -22,9 +22,9 @@ export function SentRequestsTable({ requests, isLoading }: SentRequestsTableProp if (requests.length === 0) { return ( -
+
-
+
Outbox
diff --git a/app/contacts/page.tsx b/app/contacts/page.tsx index 76179f3..ce7e40d 100644 --- a/app/contacts/page.tsx +++ b/app/contacts/page.tsx @@ -262,7 +262,7 @@ function ContactsPageInner() {
-
+
{(activeTab === 'normal' || activeTab === 'companies' || activeTab === 'persons') && ( <> {/* Favorites Section */} diff --git a/app/groups/page.tsx b/app/groups/page.tsx index 386c8f5..a56d199 100644 --- a/app/groups/page.tsx +++ b/app/groups/page.tsx @@ -175,7 +175,7 @@ export default function GroupsPage() {
{/* Group list */} -
+
{filteredGroups.length === 0 ? (
diff --git a/app/home/requests/page.tsx b/app/home/requests/page.tsx index 6975a35..b5c8095 100644 --- a/app/home/requests/page.tsx +++ b/app/home/requests/page.tsx @@ -109,14 +109,14 @@ export default function RequestsPage() { return ( -
+
-
@@ -136,9 +136,9 @@ export default function RequestsPage() {

- -
-
+ +
+
{type === 'received' ? ( ) : ( @@ -146,21 +146,21 @@ export default function RequestsPage() { )} {request.amount.toLocaleString()} {request.currency}
- + {type === 'received' && isPending && (
- - + ); +} + function ContributionRow({ c, onClick }: { c: PendingContribution; onClick: () => void }) { - const progress = c.goalAmount > 0 ? Math.min((c.collectedAmount / c.goalAmount) * 100, 100) : 0; + const hasGoal = c.goalAmount > 0; + const progress = hasGoal ? Math.min((c.collectedAmount / c.goalAmount) * 100, 100) : 0; const isPaid = !!c.myPayment; const isActive = c.status === 'active'; @@ -226,9 +283,20 @@ function ContributionRow({ c, onClick }: { c: PendingContribution; onClick: () =

{c.title}

{c.groupName}

-
-
-
+ {hasGoal ? ( + <> +
+
+
+

+ {fmtRwf(c.collectedAmount, c.currency)} of {fmtRwf(c.goalAmount, c.currency)} +

+ + ) : ( +

+ {fmtRwf(c.collectedAmount, c.currency)} collected +

+ )}
{/* Right side */} @@ -318,6 +386,7 @@ export const RecentActions = ({ userId }: RecentActionsProps) => { const [voteItems, setVoteItems] = useState([]); const [voteDataMap, setVoteDataMap] = useState>({}); const [pendingContributions, setPending] = useState([]); + const [campaigns, setCampaigns] = useState([]); const [orgActions, setOrgActions] = useState([]); const [isOrganization, setIsOrganization] = useState(false); const [loading, setLoading] = useState(true); @@ -359,10 +428,11 @@ export const RecentActions = ({ userId }: RecentActionsProps) => { return; } - // Individual user: fetch QR objects + contributions in parallel - const [qrRes, contribRes] = await Promise.allSettled([ + // Individual user: fetch QR objects + contributions + public campaigns in parallel + const [qrRes, contribRes, campaignsRes] = await Promise.allSettled([ axios.get(`${baseUrl}/users/${userId}/qr-objects`, { headers }), getMyGroupContributions(), + getMyPublicContributions(), ]); if (qrRes.status === 'fulfilled') { @@ -470,6 +540,18 @@ export const RecentActions = ({ userId }: RecentActionsProps) => { setPending(toShow); } } + + // Public campaigns: active first, up to 2 + if (campaignsRes.status === 'fulfilled') { + const all: any[] = campaignsRes.value?.data?.data || campaignsRes.value?.data || []; + if (Array.isArray(all)) { + const active = all.filter((c: any) => c.status === 'active'); + const toShow = active.length > 0 + ? active.slice(0, 2) + : all.filter((c: any) => c.status !== 'active').slice(0, 2); + setCampaigns(toShow); + } + } } catch { // silently fail — card shows empty state } finally { @@ -486,7 +568,7 @@ export const RecentActions = ({ userId }: RecentActionsProps) => { const hasContent = isOrganization ? orgActions.length > 0 - : tickets.length > 0 || voteItems.length > 0 || pendingContributions.length > 0; + : tickets.length > 0 || voteItems.length > 0 || pendingContributions.length > 0 || campaigns.length > 0; return (
@@ -632,6 +714,21 @@ export const RecentActions = ({ userId }: RecentActionsProps) => {
)} + + {campaigns.length > 0 && ( +
+
+

+ Campaigns +

+
+
+ {campaigns.map((c) => ( + + ))} +
+
+ )} )} diff --git a/components/chat/GroupDialogs.tsx b/components/chat/GroupDialogs.tsx index 84d494e..d4964ad 100644 --- a/components/chat/GroupDialogs.tsx +++ b/components/chat/GroupDialogs.tsx @@ -54,19 +54,19 @@ export default function GroupDialogs({
-
- +
+
Leave Group
- - Are you sure you want to leave "{getGroupName(leaveDialog.groupId)}"? + + Are you sure you want to leave "{getGroupName(leaveDialog.groupId)}"?

You won't be able to see new messages unless someone adds you back to the group.
- + Cancel
-
- +
+
Delete Group
- + Are you sure you want to permanently delete "{getGroupName(deleteDialog.groupId)}"?

- This action cannot be undone. All group data, messages, and member information will be permanently removed. + This action cannot be undone. All group data, messages, and member information will be permanently removed.
- + Cancel - - + onManageAction("info", group)}> Group Info @@ -48,7 +48,7 @@ export default function GroupMenu({ group, isActiveMember, onManageAction }: Gro <> onManageAction("invite", group)} - className="flex items-center gap-3 py-2.5 cursor-pointer hover:bg-gray-50" + className="flex items-center gap-3 py-2.5 cursor-pointer" > Invite Members @@ -58,7 +58,7 @@ export default function GroupMenu({ group, isActiveMember, onManageAction }: Gro onManageAction("mute", group)} - className="flex items-center gap-3 py-2.5 cursor-pointer hover:bg-gray-50" + className="flex items-center gap-3 py-2.5 cursor-pointer" > Mute Notifications @@ -66,7 +66,7 @@ export default function GroupMenu({ group, isActiveMember, onManageAction }: Gro onManageAction("unmute", group)} - className="flex items-center gap-3 py-2.5 cursor-pointer hover:bg-gray-50" + className="flex items-center gap-3 py-2.5 cursor-pointer" > Unmute Notifications @@ -110,9 +110,9 @@ export default function GroupMenu({ group, isActiveMember, onManageAction }: Gro {!isActiveMember && ( onManageAction("view", group)} - className="flex items-center gap-3 py-2.5 cursor-pointer hover:bg-gray-50" + className="flex items-center gap-3 py-2.5 cursor-pointer" > - + View Group )} diff --git a/components/chat/GroupsHeader.tsx b/components/chat/GroupsHeader.tsx index 68714a5..aec53c7 100644 --- a/components/chat/GroupsHeader.tsx +++ b/components/chat/GroupsHeader.tsx @@ -45,7 +45,7 @@ export default function GroupsHeader({ groupsCount, searchTerm, onSearchChange } {/* Search and Filters */}
- + Filters diff --git a/components/chat/add-member-modal.tsx b/components/chat/add-member-modal.tsx index 6e171a9..a9ef23e 100644 --- a/components/chat/add-member-modal.tsx +++ b/components/chat/add-member-modal.tsx @@ -230,15 +230,15 @@ export default function AddMemberModal({ return ( !open && onClose()}> - - - -
- + + + +
+
Add Members
-
+
Invite people to "{groupName}"
@@ -246,12 +246,21 @@ export default function AddMemberModal({ - - + + From Contacts - + Share Link @@ -262,10 +271,10 @@ export default function AddMemberModal({ {/* Search Bar */}
- + setSearchTerm(e.target.value)} /> @@ -273,16 +282,16 @@ export default function AddMemberModal({ {/* Selection Summary */} {selectedContacts.length > 0 && ( -
+
- + {selectedContacts.length} contact{selectedContacts.length > 1 ? "s" : ""} selected @@ -292,13 +301,13 @@ export default function AddMemberModal({ {/* Select All */} {filteredContacts.length > 0 && ( -
+
-
@@ -308,11 +317,11 @@ export default function AddMemberModal({
{isLoadingContacts ? (
- +
) : filteredContacts.length === 0 ? ( -
- +
+

No contacts found

{searchTerm ? "Try a different search term" : "Add contacts to invite them"} @@ -326,7 +335,7 @@ export default function AddMemberModal({ return (

toggleContact(otherUser.id)} > - + {getInitials(otherUser.firstName, otherUser.lastName)}
-

+

{otherUser.firstName} {otherUser.lastName}

-

+

{otherUser.email}

@@ -355,11 +364,11 @@ export default function AddMemberModal({ {/* Action Buttons */} {selectedContacts.length > 0 && ( -
+
@@ -390,13 +399,13 @@ export default function AddMemberModal({ <> {/* QR Code Section */} {qrCode && ( -
-

- +
+

+ QR Code

-
+
Group QR Code
-

+

Scan this QR code to join the group

{/* Content Area */} -
+
{activeFilter === 'groups' ? ( // Groups-specific rendering groupsLoading ? ( diff --git a/components/chat/create-group-modal.tsx b/components/chat/create-group-modal.tsx index 1d17d80..10df125 100644 --- a/components/chat/create-group-modal.tsx +++ b/components/chat/create-group-modal.tsx @@ -338,10 +338,10 @@ export default function CreateGroupModalUpdated({ ) : (