Skip to content

Commit 63bdc0d

Browse files
committed
Refine add funds modal and review fixes
1 parent 5c2628e commit 63bdc0d

5 files changed

Lines changed: 116 additions & 153 deletions

File tree

src/features/Billing/v1/components/layout/AddFundsModal.tsx

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { useEffect, useMemo, useState } from "react";
22
import {
3-
BadgeCheck,
43
Banknote,
54
CheckCircle2,
65
CreditCard,
@@ -11,7 +10,7 @@ import {
1110
Wallet,
1211
X,
1312
} from "lucide-react";
14-
import { MIN_ADD_RUPEES, RECHARGE_PACKS } from "../../constants/billing.constants";
13+
import { MIN_ADD_RUPEES } from "../../constants/billing.constants";
1514
import { useAddFunds } from "../../hooks/useWallet";
1615
import { buildAddFundsPreview, formatCredits, formatRupees, validateMinAddFunds } from "../../utils/credits";
1716
import type { AddFundsPayload, PaymentState } from "../../Billing.types";
@@ -130,36 +129,8 @@ export default function AddFundsModal({ isOpen, onClose, onSuccess, onError }: P
130129
</div>
131130

132131
<section className="mt-6">
133-
<SectionTitle title="Credit calculator" />
134-
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
135-
{RECHARGE_PACKS.slice(0, 4).map((pack) => {
136-
const isSelected = amount === pack.amountRupees;
137-
return (
138-
<button
139-
type="button"
140-
key={pack.id}
141-
onClick={() => setAmountStr(pack.amountRupees.toString())}
142-
className="min-h-24 rounded-xl border p-3 text-left transition-all hover:-translate-y-0.5"
143-
style={{
144-
backgroundColor: isSelected ? "var(--cd-primary-subtle)" : "var(--cd-bg)",
145-
borderColor: isSelected ? "var(--cd-primary)" : "var(--cd-border-subtle)",
146-
color: isSelected ? "var(--cd-primary-text)" : "var(--cd-text)",
147-
}}
148-
>
149-
<div className="flex items-center justify-between gap-2">
150-
<span className="text-xs font-bold">{pack.label}</span>
151-
{isSelected ? <BadgeCheck size={16} /> : null}
152-
</div>
153-
<span className="mt-3 block text-lg font-black">{formatRupees(pack.amountRupees)}</span>
154-
<span className="mt-1 block text-xs font-semibold">
155-
{formatCredits(pack.baseCredits + pack.bonusCredits)} cr
156-
</span>
157-
</button>
158-
);
159-
})}
160-
</div>
161-
162-
<label className="mt-4 block text-sm font-bold" style={{ color: "var(--cd-text)" }}>
132+
<SectionTitle title="Custom amount" />
133+
<label className="block text-sm font-bold" style={{ color: "var(--cd-text)" }}>
163134
Custom amount (Rs., min {MIN_ADD_RUPEES})
164135
</label>
165136
<div className="relative mt-2">
@@ -190,6 +161,21 @@ export default function AddFundsModal({ isOpen, onClose, onSuccess, onError }: P
190161
)}
191162
</section>
192163

164+
<section className="mt-6">
165+
<SectionTitle title="Credit calculator" />
166+
<div
167+
className="grid gap-3 rounded-xl border p-4 sm:grid-cols-3"
168+
style={{ backgroundColor: "var(--cd-bg)", borderColor: "var(--cd-border-subtle)" }}
169+
>
170+
<GeneratedCreditTile label="Base credits" value={formatCredits(preview.baseCredits)} />
171+
<GeneratedCreditTile
172+
label="Bonus credits"
173+
value={preview.bonusCredits > 0 ? `+${formatCredits(preview.bonusCredits)}` : "0"}
174+
/>
175+
<GeneratedCreditTile label="Total credits" value={formatCredits(preview.totalCredits)} accent />
176+
</div>
177+
</section>
178+
193179
<section className="mt-6">
194180
<SectionTitle title="Payment method" />
195181
<div className="grid grid-cols-2 gap-2 sm:grid-cols-5">
@@ -291,6 +277,33 @@ function SectionTitle({ title }: { title: string }) {
291277
);
292278
}
293279

280+
function GeneratedCreditTile({
281+
label,
282+
value,
283+
accent = false,
284+
}: {
285+
label: string;
286+
value: string;
287+
accent?: boolean;
288+
}) {
289+
return (
290+
<div
291+
className="min-h-24 rounded-xl border p-4"
292+
style={{
293+
backgroundColor: accent ? "var(--cd-primary-subtle)" : "var(--cd-surface)",
294+
borderColor: accent ? "var(--cd-primary)" : "var(--cd-border-subtle)",
295+
}}
296+
>
297+
<span className="text-xs font-bold uppercase tracking-wide" style={{ color: "var(--cd-text-muted)" }}>
298+
{label}
299+
</span>
300+
<span className="mt-3 block text-2xl font-black" style={{ color: accent ? "var(--cd-primary)" : "var(--cd-text)" }}>
301+
{value}
302+
</span>
303+
</div>
304+
);
305+
}
306+
294307
function SummaryRow({
295308
label,
296309
value,

src/features/Billing/v1/pages/PaymentsPage.tsx

Lines changed: 0 additions & 78 deletions
This file was deleted.

src/features/Contact_And_Support/v1/Components/InternalSupport_Table.tsx

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,38 @@ const CopyEmailButton = ({ email }: { email: string }) => {
9999
);
100100
};
101101

102+
const TeamMemberAvatar = ({ member }: { member: TeamMember }) => {
103+
const [imageFailed, setImageFailed] = useState(false);
104+
105+
return (
106+
<div className="relative shrink-0">
107+
{imageFailed ? (
108+
<div
109+
className="w-9 h-9 rounded-full flex items-center justify-center text-sm font-bold text-white select-none"
110+
style={{ backgroundColor: "var(--cd-primary)" }}
111+
>
112+
{getInitials(member.name)}
113+
</div>
114+
) : (
115+
<img
116+
src={member.image}
117+
alt={member.name}
118+
className="w-9 h-9 rounded-full object-cover border"
119+
style={{ borderColor: "var(--cd-border)" }}
120+
onError={() => setImageFailed(true)}
121+
/>
122+
)}
123+
<span
124+
className="absolute bottom-0 right-0 w-2.5 h-2.5 rounded-full border-2"
125+
style={{
126+
backgroundColor: STATUS_COLOR[member.status],
127+
borderColor: "var(--cd-surface)",
128+
}}
129+
/>
130+
</div>
131+
);
132+
};
133+
102134
const InternalSupport_Table = () => {
103135
return (
104136
<div className="w-full h-fit overflow-x-auto">
@@ -116,32 +148,7 @@ const InternalSupport_Table = () => {
116148
<tr key={member.id}>
117149
<td>
118150
<div className="flex items-center gap-3">
119-
<div className="relative shrink-0">
120-
<img
121-
src={member.image}
122-
alt={member.name}
123-
className="w-9 h-9 rounded-full object-cover border"
124-
style={{ borderColor: "var(--cd-border)" }}
125-
onError={(e) => {
126-
e.currentTarget.style.display = 'none';
127-
const sibling = e.currentTarget.nextElementSibling as HTMLElement;
128-
if (sibling) sibling.style.display = 'flex';
129-
}}
130-
/>
131-
<div
132-
className="w-9 h-9 rounded-full flex items-center justify-center text-sm font-bold text-white select-none"
133-
style={{ backgroundColor: "var(--cd-primary)", display: 'none' }}
134-
>
135-
{getInitials(member.name)}
136-
</div>
137-
<span
138-
className="absolute bottom-0 right-0 w-2.5 h-2.5 rounded-full border-2"
139-
style={{
140-
backgroundColor: STATUS_COLOR[member.status],
141-
borderColor: "var(--cd-surface)",
142-
}}
143-
/>
144-
</div>
151+
<TeamMemberAvatar member={member} />
145152
<span className="font-medium text-sm" style={{ color: "var(--cd-text)" }}>
146153
{member.name}
147154
</span>

src/features/SideBar/v1/Section/SideBar.tsx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { RiContactsBookFill } from "react-icons/ri";
2+
import { useEffect, useState } from "react";
23
import {
34
MdAssignment,
45
MdDashboard,
@@ -21,13 +22,25 @@ import useOrganizationStore from "@/features/Auth/v1/Store/Organization.Store";
2122
const SideBar = () => {
2223
const user = useAuthStore((state) => state.user);
2324
const organization = useOrganizationStore((state) => state.organization);
25+
const [profileImageFailed, setProfileImageFailed] = useState(false);
2426

2527
console.log("User in SideBar:", user);
2628
console.log("Organization in SideBar:", organization);
2729
const { theme } = useTheme();
2830

2931
const communityName = organization?.CommunityName || "CommDesk";
3032
const userRole = user?.role || "Admin";
33+
const profileImageUrl = organization?.LogoUrl || "/defaultProfile.png";
34+
const initials = communityName
35+
.split(" ")
36+
.map((part) => part[0])
37+
.join("")
38+
.toUpperCase()
39+
.slice(0, 2);
40+
41+
useEffect(() => {
42+
setProfileImageFailed(false);
43+
}, [profileImageUrl]);
3144

3245
return (
3346
<div
@@ -87,14 +100,21 @@ const SideBar = () => {
87100
className="mt-3 w-full rounded-xl p-3 flex items-center gap-3 cursor-pointer transition-colors duration-150"
88101
style={{ backgroundColor: theme.bg.surfaceSecondary }}
89102
>
90-
<img
91-
src={organization?.LogoUrl || "/defaultProfile.png"}
92-
alt="Profile"
93-
className="w-9 h-9 rounded-full object-cover shrink-0"
94-
onError={(e) => {
95-
e.currentTarget.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(communityName)}&background=4f46e5&color=fff`;
96-
}}
97-
/>
103+
{profileImageFailed ? (
104+
<div
105+
className="w-9 h-9 rounded-full shrink-0 flex items-center justify-center text-xs font-bold text-white"
106+
style={{ backgroundColor: theme.primary.default }}
107+
>
108+
{initials || "CD"}
109+
</div>
110+
) : (
111+
<img
112+
src={profileImageUrl}
113+
alt="Profile"
114+
className="w-9 h-9 rounded-full object-cover shrink-0"
115+
onError={() => setProfileImageFailed(true)}
116+
/>
117+
)}
98118
<div className="min-w-0 flex-1 flex flex-col justify-center gap-0.5">
99119
<p className="text-sm font-semibold truncate" style={{ color: theme.text.primary }}>
100120
{communityName}

tests/e2e/billing.spec.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,25 @@ test.describe("Billing & Credits Wallet E2E Scenarios", () => {
4141
await expect(page.getByRole("heading", { name: "Community Wallet" })).toBeVisible();
4242

4343
await page.getByRole("button", { name: "Add Funds" }).first().click();
44-
await expect(page).toHaveURL(/\/org\/billing\/add-funds/);
44+
const addFundsDialog = page.getByRole("dialog", { name: "Add Funds" });
45+
await expect(addFundsDialog).toBeVisible();
4546

46-
await page.fill('input[type="number"]', "500");
47+
await addFundsDialog.locator('input[type="number"]').fill("500");
4748

48-
await expect(page.getByTestId("credits-added-preview")).toHaveText("5,500"); // 5000 base + 500 bonus
49-
await expect(page.locator("text=Platform fee")).toBeVisible();
50-
await expect(page.locator("text=GST (18%)")).toBeVisible();
49+
await expect(addFundsDialog.locator("text=Total credits")).toBeVisible();
50+
await expect(addFundsDialog.locator("text=5,500")).toBeVisible();
51+
await expect(addFundsDialog.locator("text=Platform fee")).toBeVisible();
52+
await expect(addFundsDialog.locator("text=GST (18%)")).toBeVisible();
5153

52-
await page.getByRole("button", { name: "UPI" }).click();
53-
await page.getByRole("button", { name: /Pay/ }).click();
54+
await addFundsDialog.getByRole("button", { name: "UPI" }).click();
55+
await addFundsDialog.getByRole("button", { name: /Pay/ }).click();
5456

5557
await expect(page.locator("text=Processing...")).toBeVisible();
5658
await expect(page.getByRole("heading", { name: "Payment successful" })).toBeVisible({
5759
timeout: 10000,
5860
});
5961

60-
await page.getByRole("button", { name: "Back to wallet" }).click();
62+
await page.getByRole("button", { name: "Done" }).click();
6163
await expect(page).toHaveURL(/\/org\/dashboard\/community\/wallet/);
6264
});
6365

@@ -115,7 +117,6 @@ test.describe("Billing & Credits Wallet E2E Scenarios", () => {
115117
await expect(page.locator("text=AI_SUMMARY").first()).toBeVisible();
116118

117119
await page.getByRole("button", { name: "Overview" }).click();
118-
await expect(page.getByTestId("wallet-stat-available-value")).toHaveText("6,849");
119120
const newBalanceText = await page.getByTestId("wallet-stat-available-value").textContent();
120121
const newBalance = parseCredits(newBalanceText);
121122

0 commit comments

Comments
 (0)