diff --git a/eslint.config.js b/eslint.config.js index f24d543..6808ce1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -26,14 +26,11 @@ export default tseslint.config( }, rules: { ...reactHooks.configs.recommended.rules, - "react-refresh/only-export-components": [ - "warn", - { allowConstantExport: true }, - ], + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-unused-vars": [ "error", - { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" } + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, ], }, }, diff --git a/playwright.config.ts b/playwright.config.ts index c8da832..c6fea3b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -22,7 +22,7 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", - + /* Disable video/screenshots by default for speed, can be enabled on retry */ screenshot: "only-on-failure", }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e25753c..399d20e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,6 +87,9 @@ importers: '@eslint/js': specifier: ^9.39.4 version: 9.39.4 + '@playwright/test': + specifier: ^1.60.0 + version: 1.60.0 '@tailwindcss/typography': specifier: ^0.5.19 version: 0.5.19(tailwindcss@4.3.0) @@ -732,6 +735,11 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@playwright/test@1.60.0': + resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} + engines: {node: '>=18'} + hasBin: true + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -2671,6 +2679,11 @@ packages: resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} engines: {node: '>=14.14'} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3358,6 +3371,16 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} + engines: {node: '>=18'} + hasBin: true + postcss-selector-parser@6.0.10: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} engines: {node: '>=4'} @@ -4667,6 +4690,10 @@ snapshots: '@open-draft/until@2.1.0': {} + '@playwright/test@1.60.0': + dependencies: + playwright: 1.60.0 + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -6618,6 +6645,9 @@ snapshots: jsonfile: 6.2.1 universalify: 2.0.1 + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -7180,6 +7210,14 @@ snapshots: pkce-challenge@5.0.1: {} + playwright-core@1.60.0: {} + + playwright@1.60.0: + dependencies: + playwright-core: 1.60.0 + optionalDependencies: + fsevents: 2.3.2 + postcss-selector-parser@6.0.10: dependencies: cssesc: 3.0.0 diff --git a/src/config/sidebar.config.ts b/src/config/sidebar.config.ts index 5bd94f2..6cc836d 100644 --- a/src/config/sidebar.config.ts +++ b/src/config/sidebar.config.ts @@ -34,7 +34,7 @@ export const sidebarItems = [ }, { title: "Teams", - path: "/org/teams", + path: "/member/teams", icon: Users, }, { diff --git a/src/features/Auth/v1/Pages/LoginPage.tsx b/src/features/Auth/v1/Pages/LoginPage.tsx index 360e1c3..236b270 100644 --- a/src/features/Auth/v1/Pages/LoginPage.tsx +++ b/src/features/Auth/v1/Pages/LoginPage.tsx @@ -51,7 +51,9 @@ const LoginPage = () => { password, }); - const Role = response.data.role; + console.log("Login successful: come from login page --->", response); + + const Role = response.data.FindUser.role; if (Role === "organization") { navigate("/org/dashboard"); diff --git a/src/features/Auth/v1/Store/Organization.Store.ts b/src/features/Auth/v1/Store/Organization.Store.ts index 2da8ef9..447362b 100644 --- a/src/features/Auth/v1/Store/Organization.Store.ts +++ b/src/features/Auth/v1/Store/Organization.Store.ts @@ -2,7 +2,6 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; import { CommunitySchema } from "../Types/Organization.Type"; - const useOrganizationStore = create<{ organization: CommunitySchema | null; setOrganization: (organization: CommunitySchema) => void; diff --git a/src/features/Auth/v1/hooks/useAuth.ts b/src/features/Auth/v1/hooks/useAuth.ts index ed568c6..f42900a 100644 --- a/src/features/Auth/v1/hooks/useAuth.ts +++ b/src/features/Auth/v1/hooks/useAuth.ts @@ -5,8 +5,9 @@ import AUTH_ENDPOINTS from "../Constant/Auth.Endpoint.Constant"; import useAuthStore from "../Store/Auth.Store"; import useOrganizationStore from "../Store/Organization.Store"; -const baseUrl = - import.meta.env.VITE_API_BASE_URL || "http://localhost:8000/api/v1"; +import usePermissionStore from "@/features/Permission/Store/Permission.Store"; + +const baseUrl = import.meta.env.VITE_API_BASE_URL || "http://localhost:8000/api/v1"; // ========================= // GET ORGANIZATION @@ -18,13 +19,14 @@ const useGetOrganizationMutation = () => { mutationFn: async (_id: string) => { const response = await api.get( - `${baseUrl}${AUTH_ENDPOINTS.GET_ORGANIZATION_BY_ID}?ownerId=${_id}` + `${baseUrl}${AUTH_ENDPOINTS.GET_ORGANIZATION_BY_ID}?ownerId=${_id}`, ); return response.data; }, onSuccess: (response) => { + console.log("Organization fetched successfully:", response.data); useOrganizationStore.getState().setOrganization(response.data); }, @@ -44,23 +46,17 @@ const useLoginMutation = () => { return useMutation({ mutationKey: ["login"], - mutationFn: async (credentials: { - email: string; - password: string; - }) => { - const response = await api.post( - `${baseUrl}${AUTH_ENDPOINTS.LOGIN}`, - credentials - ); + mutationFn: async (credentials: { email: string; password: string }) => { + const response = await api.post(`${baseUrl}${AUTH_ENDPOINTS.LOGIN}`, credentials); return response.data; }, onSuccess: async (response) => { - const user = response.data; + const user = response.data.FindUser; const token = response.token; - console.log("Login successful:", user); + usePermissionStore.getState().setPermissions(response.data.perms); // Save auth first useAuthStore.getState().setAuthData(user, token); @@ -87,4 +83,4 @@ export const useAuth = () => { return { loginMutation, }; -}; \ No newline at end of file +}; diff --git a/src/features/Billing/v1/components/analytics/UsageCharts.tsx b/src/features/Billing/v1/components/analytics/UsageCharts.tsx index 853b609..3d15702 100644 --- a/src/features/Billing/v1/components/analytics/UsageCharts.tsx +++ b/src/features/Billing/v1/components/analytics/UsageCharts.tsx @@ -32,7 +32,10 @@ export default function UsageCharts() {
))}
diff --git a/src/features/Billing/v1/components/layout/AddFundsModal.tsx b/src/features/Billing/v1/components/layout/AddFundsModal.tsx index 4fe2792..d791b6d 100644 --- a/src/features/Billing/v1/components/layout/AddFundsModal.tsx +++ b/src/features/Billing/v1/components/layout/AddFundsModal.tsx @@ -12,7 +12,12 @@ import { } from "lucide-react"; import { MIN_ADD_RUPEES } from "../../constants/billing.constants"; import { useAddFunds } from "../../hooks/useWallet"; -import { buildAddFundsPreview, formatCredits, formatRupees, validateMinAddFunds } from "../../utils/credits"; +import { + buildAddFundsPreview, + formatCredits, + formatRupees, + validateMinAddFunds, +} from "../../utils/credits"; import type { AddFundsPayload, PaymentState } from "../../Billing.types"; import Input from "@/Component/ui/Input"; @@ -118,10 +123,17 @@ export default function AddFundsModal({ isOpen, onClose, onSuccess, onError }: P }} >
-

+

Community wallet

-

+

Add Funds

@@ -141,7 +153,7 @@ export default function AddFundsModal({ isOpen, onClose, onSuccess, onError }: P setAmountStr(digitsOnly.replace(/^0+(?=\d)/, "")); }} leftIcon={} - error={validation.valid ? undefined : validation.error ?? undefined} + error={validation.valid ? undefined : (validation.error ?? undefined)} className="w-full !mb-0" inputClassName="!text-lg !font-bold" /> @@ -151,14 +163,26 @@ export default function AddFundsModal({ isOpen, onClose, onSuccess, onError }: P

- + 0 ? `+${formatCredits(preview.bonusCredits)}` : "0"} + value={ + preview.bonusCredits > 0 ? `+${formatCredits(preview.bonusCredits)}` : "0" + } + /> + -
@@ -195,23 +219,42 @@ export default function AddFundsModal({ isOpen, onClose, onSuccess, onError }: P
- + {preview.bonusCredits > 0 ? ( - + ) : null}
- - + +
@@ -243,7 +286,10 @@ export default function AddFundsModal({ isOpen, onClose, onSuccess, onError }: P )} -
+
Encrypted payment simulation
@@ -257,7 +303,10 @@ export default function AddFundsModal({ isOpen, onClose, onSuccess, onError }: P function SectionTitle({ title }: { title: string }) { return ( -

+

{title}

); @@ -280,10 +329,16 @@ function GeneratedCreditTile({ borderColor: accent ? "var(--cd-primary)" : "var(--cd-border-subtle)", }} > - + {label} - + {value}
@@ -328,7 +383,11 @@ function SuccessContent({ credits, onClose }: { credits: number; onClose: () =>

{formatCredits(credits)} credits have been added to your wallet.

-
diff --git a/src/features/Billing/v1/components/layout/LowBalanceModal.tsx b/src/features/Billing/v1/components/layout/LowBalanceModal.tsx index 312b13a..415fd10 100644 --- a/src/features/Billing/v1/components/layout/LowBalanceModal.tsx +++ b/src/features/Billing/v1/components/layout/LowBalanceModal.tsx @@ -11,7 +11,13 @@ interface Props { onAddFunds?: () => void; } -export default function LowBalanceModal({ isOpen, availableCredits, threshold, onDismiss, onAddFunds }: Props) { +export default function LowBalanceModal({ + isOpen, + availableCredits, + threshold, + onDismiss, + onAddFunds, +}: Props) { const navigate = useNavigate(); const ref = useRef(null); diff --git a/src/features/Billing/v1/components/layout/PayoutAccountDetails.tsx b/src/features/Billing/v1/components/layout/PayoutAccountDetails.tsx index 711312a..9d642f0 100644 --- a/src/features/Billing/v1/components/layout/PayoutAccountDetails.tsx +++ b/src/features/Billing/v1/components/layout/PayoutAccountDetails.tsx @@ -12,7 +12,7 @@ export default function PayoutAccountDetails() { accountHolder: "", bankName: "", accountNumber: "", - ifsc: "" + ifsc: "", }); const handleSave = () => { @@ -40,10 +40,17 @@ export default function PayoutAccountDetails() { >
-

Payout Account Details

-

Receive funds and revenue securely to your bank account.

+

+ Payout Account Details +

+

+ Receive funds and revenue securely to your bank account. +

-
+
@@ -93,8 +100,13 @@ export default function PayoutAccountDetails() { disabled={loading || saved} className="cd-btn cd-btn-secondary px-6 py-2.5 rounded-xl text-sm font-bold flex items-center gap-2" > - {loading ?
: - saved ? : } + {loading ? ( +
+ ) : saved ? ( + + ) : ( + + )} {saved ? "Saved" : "Save Details"}
diff --git a/src/features/Billing/v1/components/layout/QuickRecharge.tsx b/src/features/Billing/v1/components/layout/QuickRecharge.tsx index a6b4046..c460434 100644 --- a/src/features/Billing/v1/components/layout/QuickRecharge.tsx +++ b/src/features/Billing/v1/components/layout/QuickRecharge.tsx @@ -22,7 +22,7 @@ export default function QuickRecharge() { await addFunds.mutateAsync({ amountRupees: amt, paymentMethod: "upi", - idempotencyKey: `quick-${crypto.randomUUID()}` + idempotencyKey: `quick-${crypto.randomUUID()}`, }); addToast("success", "Recharge Successful", `Added ${formatCredits(amt * 10)} credits.`); } catch { @@ -43,10 +43,17 @@ export default function QuickRecharge() { >
-

Quick Recharge

-

Top up instantly with UPI.

+

+ Quick Recharge +

+

+ Top up instantly with UPI. +

-
+
@@ -92,11 +99,15 @@ export default function QuickRecharge() { >
You get - {formatCredits(preview.totalCredits)} cr + + {formatCredits(preview.totalCredits)} cr +
Payable - {formatRupees(preview.totalPayableRupees)} + + {formatRupees(preview.totalPayableRupees)} +
@@ -105,7 +116,11 @@ export default function QuickRecharge() { disabled={addFunds.isPending} className="cd-btn cd-btn-primary w-full rounded-xl py-2.5 font-bold flex justify-center items-center gap-2" > - {addFunds.isPending ? : } + {addFunds.isPending ? ( + + ) : ( + + )} {addFunds.isPending ? "Processing..." : `Pay ${formatRupees(preview.totalPayableRupees)}`}
diff --git a/src/features/Billing/v1/components/layout/WalletHeader.tsx b/src/features/Billing/v1/components/layout/WalletHeader.tsx index 7eacf64..ed10375 100644 --- a/src/features/Billing/v1/components/layout/WalletHeader.tsx +++ b/src/features/Billing/v1/components/layout/WalletHeader.tsx @@ -79,7 +79,10 @@ export default function WalletHeader({ wallet, activeTab, onTabChange, onAddFund color: isActive ? "var(--cd-text)" : "var(--cd-text-muted)", }} > - + {tab.label} ); @@ -114,7 +117,10 @@ function WalletTitle({ wallet }: { wallet: WalletType | undefined }) { > Community Wallet -
+
{wallet ? ( <> diff --git a/src/features/Billing/v1/components/layout/WalletStatsGrid.tsx b/src/features/Billing/v1/components/layout/WalletStatsGrid.tsx index ab9cd52..e354c26 100644 --- a/src/features/Billing/v1/components/layout/WalletStatsGrid.tsx +++ b/src/features/Billing/v1/components/layout/WalletStatsGrid.tsx @@ -54,7 +54,10 @@ export default function WalletStatsGrid({ wallet, burnRatePerDay = 52 }: Props) {stat.label} - +
@@ -66,11 +69,12 @@ export default function WalletStatsGrid({ wallet, burnRatePerDay = 52 }: Props) > {stat.val} - credits + + credits +
))} ); } - diff --git a/src/features/Billing/v1/components/table/TeamUsageTable.tsx b/src/features/Billing/v1/components/table/TeamUsageTable.tsx index e5b8f5c..0ba1f76 100644 --- a/src/features/Billing/v1/components/table/TeamUsageTable.tsx +++ b/src/features/Billing/v1/components/table/TeamUsageTable.tsx @@ -49,14 +49,20 @@ export default function TeamUsageTable() { } return ( -
+
- - -
Member + Credits Used @@ -71,7 +77,10 @@ export default function TeamUsageTable() { className="border-t hover:bg-[var(--cd-hover)]" style={{ borderColor: "var(--cd-border-subtle)" }} > - +
diff --git a/src/features/Billing/v1/components/table/TransactionTable.tsx b/src/features/Billing/v1/components/table/TransactionTable.tsx index 4308e04..6fd7314 100644 --- a/src/features/Billing/v1/components/table/TransactionTable.tsx +++ b/src/features/Billing/v1/components/table/TransactionTable.tsx @@ -36,10 +36,16 @@ export default function TransactionTable({ transactions, isLoading }: Props) {
Source + Credits + Balance diff --git a/src/features/Billing/v1/hooks/useAddFunds.ts b/src/features/Billing/v1/hooks/useAddFunds.ts index f6c5915..d307f3e 100644 --- a/src/features/Billing/v1/hooks/useAddFunds.ts +++ b/src/features/Billing/v1/hooks/useAddFunds.ts @@ -20,7 +20,7 @@ export function useAddFunds() { const result = walletStore.addFunds(amountRupees, idempotencyKey); const preview = buildAddFundsPreview(amountRupees); - + return { preview, transaction: result.transaction, @@ -34,4 +34,3 @@ export function useAddFunds() { }, }); } - diff --git a/src/features/Billing/v1/hooks/useBillingGate.ts b/src/features/Billing/v1/hooks/useBillingGate.ts index 6ad3165..aa81b00 100644 --- a/src/features/Billing/v1/hooks/useBillingGate.ts +++ b/src/features/Billing/v1/hooks/useBillingGate.ts @@ -4,7 +4,6 @@ import { isLowBalance } from "../utils/credits"; const DEFAULT_LOW_BALANCE_THRESHOLD = 200; - export function useBillingGate() { const { data: wallet } = useWallet(); diff --git a/src/features/Billing/v1/mock/walletStore.ts b/src/features/Billing/v1/mock/walletStore.ts index c0cae51..e43e150 100644 --- a/src/features/Billing/v1/mock/walletStore.ts +++ b/src/features/Billing/v1/mock/walletStore.ts @@ -220,8 +220,7 @@ function enforceDailyLimits(feature: string) { const todayStr = new Date().toDateString(); const txsToday = transactions.filter( (t) => - t.transactionType === "USAGE_DEDUCTION" && - new Date(t.createdAt).toDateString() === todayStr, + t.transactionType === "USAGE_DEDUCTION" && new Date(t.createdAt).toDateString() === todayStr, ); if (feature.startsWith("AI_")) { @@ -256,9 +255,7 @@ function enforceDailyLimits(feature: string) { function checkSuspiciousActivity(credits: number) { const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000; const recentDeductions = transactions.filter( - (t) => - t.transactionType === "USAGE_DEDUCTION" && - new Date(t.createdAt).getTime() > oneDayAgo, + (t) => t.transactionType === "USAGE_DEDUCTION" && new Date(t.createdAt).getTime() > oneDayAgo, ); const totalBurn = Math.abs(recentDeductions.reduce((sum, t) => sum + t.credits, 0)) + credits; @@ -350,7 +347,8 @@ export const walletStore = { if (idempotencyKeys.has(idempotencyKey)) { const existing = getExistingTransaction("CREDIT_PURCHASE", idempotencyKey); - if (existing) return { wallet: cloneWallet(communityWallet), transaction: cloneTransaction(existing) }; + if (existing) + return { wallet: cloneWallet(communityWallet), transaction: cloneTransaction(existing) }; throw new Error("IDEMPOTENCY_KEY_REUSED"); } @@ -388,7 +386,8 @@ export const walletStore = { if (idempotencyKeys.has(idempotencyKey)) { const existing = getExistingTransaction("USAGE_DEDUCTION", idempotencyKey); - if (existing) return { wallet: cloneWallet(communityWallet), transaction: cloneTransaction(existing) }; + if (existing) + return { wallet: cloneWallet(communityWallet), transaction: cloneTransaction(existing) }; throw new Error("IDEMPOTENCY_KEY_REUSED"); } @@ -433,26 +432,25 @@ export const walletStore = { if (idempotencyKeys.has(idempotencyKey)) { const existing = getExistingTransaction("REFUND", idempotencyKey); - if (existing) return { wallet: cloneWallet(communityWallet), transaction: cloneTransaction(existing) }; + if (existing) + return { wallet: cloneWallet(communityWallet), transaction: cloneTransaction(existing) }; throw new Error("IDEMPOTENCY_KEY_REUSED"); } idempotencyKeys.add(idempotencyKey); - const tx = appendTransaction( - communityWallet.id, - "REFUND", - credits, - "refund", - sourceId, - { idempotencyKey }, - ); + const tx = appendTransaction(communityWallet.id, "REFUND", credits, "refund", sourceId, { + idempotencyKey, + }); return { wallet: cloneWallet(communityWallet), transaction: cloneTransaction(tx) }; }, setAutoRecharge: (enabled: boolean, thresholdCredits?: number, amountRupees?: number) => { - if (thresholdCredits !== undefined && (!Number.isInteger(thresholdCredits) || thresholdCredits < 0)) { + if ( + thresholdCredits !== undefined && + (!Number.isInteger(thresholdCredits) || thresholdCredits < 0) + ) { throw new Error("INVALID_AUTO_RECHARGE_THRESHOLD"); } if ( diff --git a/src/features/Billing/v1/pages/AddFundsPage.tsx b/src/features/Billing/v1/pages/AddFundsPage.tsx index ed9474e..9074e52 100644 --- a/src/features/Billing/v1/pages/AddFundsPage.tsx +++ b/src/features/Billing/v1/pages/AddFundsPage.tsx @@ -13,7 +13,12 @@ import { Wallet, } from "lucide-react"; import { RECHARGE_PACKS, MIN_ADD_RUPEES } from "../constants/billing.constants"; -import { buildAddFundsPreview, formatCredits, formatRupees, validateMinAddFunds } from "../utils/credits"; +import { + buildAddFundsPreview, + formatCredits, + formatRupees, + validateMinAddFunds, +} from "../utils/credits"; import { useAddFunds } from "../hooks/useWallet"; import { ToastContainer, useToast } from "@/features/Tasks/v1/components/common/ToastNotification"; import type { PaymentState } from "../Billing.types"; @@ -55,7 +60,11 @@ export default function AddFundsPage() { idempotencyKey: `pay-${crypto.randomUUID()}${forceFail ? "-fail" : ""}`, }); setPaymentState("success"); - addToast("success", "Payment successful", `${formatCredits(preview.totalCredits)} credits added.`); + addToast( + "success", + "Payment successful", + `${formatCredits(preview.totalCredits)} credits added.`, + ); } catch { setPaymentState("failed"); addToast("error", "Payment failed", "Please try again or use a different method."); @@ -86,7 +95,10 @@ export default function AddFundsPage() { > -
+
@@ -110,7 +122,10 @@ export default function AddFundsPage() {
- +
{RECHARGE_PACKS.map((pack) => { const isSelected = amount === pack.amountRupees; @@ -120,27 +135,40 @@ export default function AddFundsPage() { onClick={() => setAmountStr(pack.amountRupees.toString())} className="group min-h-[142px] rounded-2xl border p-4 text-left transition-all hover:-translate-y-1 hover:shadow-lg" style={{ - backgroundColor: isSelected ? "var(--cd-primary-subtle)" : "var(--cd-surface)", + backgroundColor: isSelected + ? "var(--cd-primary-subtle)" + : "var(--cd-surface)", borderColor: isSelected ? "var(--cd-primary)" : "var(--cd-border-subtle)", boxShadow: isSelected ? "0 14px 28px var(--cd-shadow)" : "none", }} >
-

+

{pack.label}

- {isSelected ? : null} + {isSelected ? ( + + ) : null}

{formatRupees(pack.amountRupees)}

-

+

{formatCredits(pack.baseCredits + pack.bonusCredits)} cr

{pack.bonusCredits > 0 && ( +{formatCredits(pack.bonusCredits)} bonus @@ -153,7 +181,10 @@ export default function AddFundsPage() {
} - error={validation.valid ? undefined : validation.error ?? undefined} + error={validation.valid ? undefined : (validation.error ?? undefined)} className="w-full !mb-0" inputClassName="!text-lg !font-bold" />
- +
{PAYMENT_METHODS.map((m) => { const isSelected = paymentMethod === m.id; @@ -183,7 +217,9 @@ export default function AddFundsPage() { onClick={() => setPaymentMethod(m.id)} className="rounded-xl border p-4 text-left transition-all hover:-translate-y-0.5" style={{ - backgroundColor: isSelected ? "var(--cd-primary-subtle)" : "var(--cd-surface)", + backgroundColor: isSelected + ? "var(--cd-primary-subtle)" + : "var(--cd-surface)", borderColor: isSelected ? "var(--cd-primary)" : "var(--cd-border-subtle)", color: isSelected ? "var(--cd-primary-text)" : "var(--cd-text)", boxShadow: isSelected ? "0 10px 22px var(--cd-shadow)" : "none", @@ -237,7 +273,10 @@ export default function AddFundsPage() { <>Pay {formatRupees(preview.totalPayableRupees)} )} -
+
Encrypted payment simulation
@@ -254,7 +293,10 @@ export default function AddFundsPage() { function SectionLabel({ title, description }: { title: string; description: string }) { return (
-

+

{title}

@@ -293,7 +335,10 @@ function PreviewCard({ preview }: { preview: ReturnType

-
+
@@ -301,7 +346,9 @@ function PreviewCard({ preview }: { preview: ReturnType (
{row.label} - {row.value} + + {row.value} +
))}
Credits command center
-

+

Billing & Credits

-

+

Manage your community wallet, add funds, and track usage. Rs. 10 = 100 credits.

@@ -78,7 +84,10 @@ export default function BillingHubPage() { borderColor: "var(--cd-border-subtle)", }} > -
+
Secure wallet
@@ -101,7 +110,10 @@ export default function BillingHubPage() { >
-

+

Available balance

@@ -112,8 +124,15 @@ export default function BillingHubPage() { ["Reserved", wallet.reservedCredits], ["Locked", wallet.lockedCredits], ].map(([label, value]) => ( -
-

+

+

{label}

@@ -140,7 +159,10 @@ export default function BillingHubPage() {

diff --git a/src/features/Billing/v1/pages/CommunityWalletPage.tsx b/src/features/Billing/v1/pages/CommunityWalletPage.tsx index 0cb6178..f885db8 100644 --- a/src/features/Billing/v1/pages/CommunityWalletPage.tsx +++ b/src/features/Billing/v1/pages/CommunityWalletPage.tsx @@ -47,9 +47,7 @@ export default function CommunityWalletPage() { void allTxQuery.refetch(); } catch (error: unknown) { const message = - error instanceof Error - ? error.message - : "Insufficient balance or daily limit reached."; + error instanceof Error ? error.message : "Insufficient balance or daily limit reached."; addToast("error", "Failed to consume credits", message); } }; @@ -142,8 +140,12 @@ export default function CommunityWalletPage() { onClick={() => setShowAIFeatures(!showAIFeatures)} className="rounded-xl px-4 py-2.5 text-sm font-semibold border transition-all hover:scale-[1.02]" style={{ - backgroundColor: showAIFeatures ? "var(--cd-primary-subtle)" : "var(--cd-surface)", - borderColor: showAIFeatures ? "var(--cd-primary)" : "var(--cd-border-subtle)", + backgroundColor: showAIFeatures + ? "var(--cd-primary-subtle)" + : "var(--cd-surface)", + borderColor: showAIFeatures + ? "var(--cd-primary)" + : "var(--cd-border-subtle)", color: showAIFeatures ? "var(--cd-primary-text)" : "var(--cd-text)", }} > @@ -159,7 +161,10 @@ export default function CommunityWalletPage() { borderColor: "var(--cd-border-subtle)", }} > -

+

Available AI Services

@@ -174,7 +179,9 @@ export default function CommunityWalletPage() { color: "var(--cd-text)", }} > - {consumeCredits.isPending ? "Generating..." : "Generate AI Summary (15 credits)"} + {consumeCredits.isPending + ? "Generating..." + : "Generate AI Summary (15 credits)"}
diff --git a/src/features/Billing/v1/pages/UsageDashboardPage.tsx b/src/features/Billing/v1/pages/UsageDashboardPage.tsx index fac340a..4ff8c07 100644 --- a/src/features/Billing/v1/pages/UsageDashboardPage.tsx +++ b/src/features/Billing/v1/pages/UsageDashboardPage.tsx @@ -64,7 +64,10 @@ export default function UsageDashboardPage() { >
- + {stat.label}
@@ -85,8 +88,7 @@ export default function UsageDashboardPage() { >

- AI usage spike risk:{" "} - {forecast.aiSpikeRisk} + AI usage spike risk: {forecast.aiSpikeRisk}

Storage growth estimate: {formatCredits(forecast.storageGrowthCredits)} credits · @@ -101,4 +103,3 @@ export default function UsageDashboardPage() {

); } - diff --git a/src/features/Billing/v1/services/billingSecurity.test.ts b/src/features/Billing/v1/services/billingSecurity.test.ts index 3991be1..856acad 100644 --- a/src/features/Billing/v1/services/billingSecurity.test.ts +++ b/src/features/Billing/v1/services/billingSecurity.test.ts @@ -51,7 +51,7 @@ describe("Billing Security & Cost Protection", () => { const result = walletStore.refundCredits(500, "pay-123", "idem-ref-1"); expect(result.wallet.availableCredits).toBe(initial + 500); - + const transactions = walletStore.getTransactions(); const lastTx = transactions[0]; expect(lastTx.transactionType).toBe("REFUND"); @@ -73,7 +73,7 @@ describe("Billing Security & Cost Protection", () => { describe("Daily Limits & Caps", () => { it("allows deductions within daily limit", async () => { const wallet = walletStore.getWallet(); - + const res = await BillingService.consumeCredits({ walletId: wallet.id, feature: "AI_SUMMARY", diff --git a/src/features/Billing/v1/utils/credits.ts b/src/features/Billing/v1/utils/credits.ts index e74f04d..ce69e6c 100644 --- a/src/features/Billing/v1/utils/credits.ts +++ b/src/features/Billing/v1/utils/credits.ts @@ -87,4 +87,3 @@ export function applyTransactionFilters( return true; }); } - diff --git a/src/features/Billing/v1/utils/security.ts b/src/features/Billing/v1/utils/security.ts index c9d5f36..c167422 100644 --- a/src/features/Billing/v1/utils/security.ts +++ b/src/features/Billing/v1/utils/security.ts @@ -9,27 +9,27 @@ export async function verifyWebhookSignature( payload: string, signature: string, - secret: string + secret: string, ): Promise { if (!payload || !signature || !secret) return false; try { const encoder = new TextEncoder(); const keyData = encoder.encode(secret); - + // Import HMAC Key const key = await window.crypto.subtle.importKey( "raw", keyData, { name: "HMAC", hash: "SHA-256" }, false, - ["sign", "verify"] + ["sign", "verify"], ); // Convert hex signature string back to a Uint8Array const hexParts = signature.match(/.{1,2}/g); if (!hexParts) return false; const signatureBytes = new Uint8Array(hexParts.map((byte) => parseInt(byte, 16))); - + const payloadData = encoder.encode(payload); // Cryptographically verify signature diff --git a/src/features/Dashboard/Member/v1/Page/DashboardPage.tsx b/src/features/Dashboard/Member/v1/Page/DashboardPage.tsx index 7f528d3..5b77fe6 100644 --- a/src/features/Dashboard/Member/v1/Page/DashboardPage.tsx +++ b/src/features/Dashboard/Member/v1/Page/DashboardPage.tsx @@ -13,6 +13,7 @@ import SummaryCard from "@/features/Dashboard/components/SummaryCard"; import TaskOverview from "@/features/Dashboard/components/TaskOverview"; import UpcomingUrgentTasks from "@/features/Dashboard/components/UpcomingUrgentTasks"; import { useDashboardData } from "@/features/Member/v1/hooks/useDashboardData"; +import Header from "@/layouts/MemberLayout/Components/Header"; export default function DashboardPage() { const { data, isLoading, error } = useDashboardData(); @@ -49,21 +50,23 @@ export default function DashboardPage() { } return ( -
- {/* Header */} -
+
+ +
+ {/* Header */} +
-
-

- Welcome back, {data.user.name.split(" ")[0]} 👋 -

+ > + Welcome back, {data.user.name.split(" ")[0]} 👋 + -

- Here’s what’s happening today -

+ > + Here’s what’s happening today +

+
-
- {/* Summary Cards */} -
- + > + - + - + - + - -
+ +
- {/* Main Grid */} -
- {/* LEFT */} -
- {/* Top Section */} -
- + > + - -
+ +
- + - + - + - + - -
+ +
- {/* RIGHT */} -
- + {/* RIGHT */} +
+ - + - + - + - + - + +
diff --git a/src/features/Member/v1/Components/MemberHeader.tsx b/src/features/Member/v1/Components/MemberHeader.tsx index 37e9e70..c025452 100644 --- a/src/features/Member/v1/Components/MemberHeader.tsx +++ b/src/features/Member/v1/Components/MemberHeader.tsx @@ -1,4 +1,6 @@ import Button from "@/Component/ui/Button"; +import PermissionWrapper from "@/features/Permission/Component/PermissionWrapper"; +import { MemberPermissionConstant } from "@/features/Permission/Constant/Permission.Constant"; import { IoPersonAdd } from "react-icons/io5"; import { useNavigate } from "react-router"; @@ -31,11 +33,13 @@ const MemberHeader = () => {
-