feat: wire Credits page to pricing management and payment APIs
Updated CreditsPage with full pricing and transaction support:
- Load pricing packages from /api/packages (role-specific)
- Create payment orders via /api/payments/create-order
- Display transaction history from wallet ledger + payments
- New tabbed interface: Overview, Buy Credits, Transactions
- Shows package details: name, price, tracecoins amount
- Format currency in INR with proper locale
Data sources:
- Packages: GET /api/packages?role={role_key}
- Wallet: GET /api/{prefix}/wallet/me
- Ledger: GET /api/{prefix}/wallet/me/ledger
- Payments: POST /api/payments/create-order
This commit is contained in:
parent
c1c9e79945
commit
4795ef2910
1 changed files with 586 additions and 56 deletions
|
|
@ -1,37 +1,56 @@
|
|||
import { For, Show, createSignal, onMount } from 'solid-js';
|
||||
import { BTN_GHOST, CARD } from '~/components/DashboardShell';
|
||||
import { PROFESSIONAL_ROLE_SET, ROLE_PREFIXES, type RoleKey } from './RoleDashboardShared';
|
||||
import { For, Show, createSignal, onMount } from "solid-js";
|
||||
import { BTN_GHOST, BTN_ORANGE, BTN_PRIMARY, CARD } from "~/components/DashboardShell";
|
||||
import { PROFESSIONAL_ROLE_SET, ROLE_PREFIXES, type RoleKey } from "./RoleDashboardShared";
|
||||
|
||||
const API = '/api/gateway';
|
||||
const API = "/api/gateway";
|
||||
|
||||
type Props = { roleKey: RoleKey };
|
||||
|
||||
type Package = {
|
||||
id: string;
|
||||
name: string;
|
||||
role_key: string;
|
||||
package_type: string;
|
||||
tracecoins_amount: number;
|
||||
price_inr: number;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
type Payment = {
|
||||
id: string;
|
||||
amount_inr: number;
|
||||
tracecoins_credited: number;
|
||||
status: string;
|
||||
created_at: string;
|
||||
package_name?: string;
|
||||
};
|
||||
|
||||
async function apiFetch(path: string, opts?: RequestInit) {
|
||||
return fetch(`${API}${path}`, {
|
||||
...opts,
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json', ...(opts?.headers ?? {}) },
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json", ...(opts?.headers ?? {}) },
|
||||
});
|
||||
}
|
||||
|
||||
export default function CreditsPage(props: Props) {
|
||||
const [wallet, setWallet] = createSignal<any>(null);
|
||||
const [ledger, setLedger] = createSignal<any[]>([]);
|
||||
const [packages, setPackages] = createSignal<Package[]>([]);
|
||||
const [payments, setPayments] = createSignal<Payment[]>([]);
|
||||
const [loading, setLoading] = createSignal(true);
|
||||
const [err, setErr] = createSignal('');
|
||||
const [loadingPayments, setLoadingPayments] = createSignal(false);
|
||||
const [err, setErr] = createSignal("");
|
||||
const [msg, setMsg] = createSignal("");
|
||||
const [activeTab, setActiveTab] = createSignal<"overview" | "buy" | "transactions">("overview");
|
||||
const [busyPackageId, setBusyPackageId] = createSignal<string | null>(null);
|
||||
|
||||
const isProfessional = () => PROFESSIONAL_ROLE_SET.has(props.roleKey);
|
||||
const prefix = () => ROLE_PREFIXES[props.roleKey];
|
||||
|
||||
const loadData = async () => {
|
||||
setLoading(true);
|
||||
setErr('');
|
||||
const loadWalletData = async () => {
|
||||
if (!isProfessional()) return;
|
||||
try {
|
||||
if (!isProfessional()) {
|
||||
setWallet(null);
|
||||
setLedger([]);
|
||||
return;
|
||||
}
|
||||
const [walletRes, ledgerRes] = await Promise.all([
|
||||
apiFetch(`/api/${prefix()}/wallet/me`),
|
||||
apiFetch(`/api/${prefix()}/wallet/me/ledger?page=1&limit=20`),
|
||||
|
|
@ -39,72 +58,584 @@ export default function CreditsPage(props: Props) {
|
|||
const walletJson = await walletRes.json().catch(() => ({}));
|
||||
const ledgerJson = await ledgerRes.json().catch(() => ({}));
|
||||
if (walletRes.ok) setWallet(walletJson);
|
||||
else setErr(walletJson.error || walletJson.message || 'Failed to load wallet.');
|
||||
if (ledgerRes.ok) setLedger(Array.isArray(ledgerJson?.data) ? ledgerJson.data : []);
|
||||
else if (!err()) setErr(ledgerJson.error || ledgerJson.message || 'Failed to load ledger.');
|
||||
} catch {
|
||||
setErr('Network error while loading credits.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// non-blocking
|
||||
}
|
||||
};
|
||||
|
||||
onMount(loadData);
|
||||
const loadPackages = async () => {
|
||||
try {
|
||||
const res = await apiFetch(`/api/packages?role=${props.roleKey}`);
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (res.ok) {
|
||||
setPackages(Array.isArray(data?.packages) ? data.packages : []);
|
||||
}
|
||||
} catch {
|
||||
setPackages([]);
|
||||
}
|
||||
};
|
||||
|
||||
const loadPayments = async () => {
|
||||
setLoadingPayments(true);
|
||||
try {
|
||||
// Try to load from payments service
|
||||
const res = await apiFetch("/api/payments/history?page=1&limit=50");
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (res.ok) {
|
||||
setPayments(Array.isArray(data?.payments) ? data.payments : []);
|
||||
} else {
|
||||
setPayments([]);
|
||||
}
|
||||
} catch {
|
||||
setPayments([]);
|
||||
} finally {
|
||||
setLoadingPayments(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadAllData = async () => {
|
||||
setLoading(true);
|
||||
setErr("");
|
||||
await Promise.all([loadWalletData(), loadPackages(), loadPayments()]);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
onMount(loadAllData);
|
||||
|
||||
const buyPackage = async (pkg: Package) => {
|
||||
setBusyPackageId(pkg.id);
|
||||
setMsg("");
|
||||
setErr("");
|
||||
try {
|
||||
// Create payment order
|
||||
const res = await apiFetch("/api/payments/create-order", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
amount: pkg.price_inr,
|
||||
currency: "INR",
|
||||
package_id: pkg.id,
|
||||
}),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
setErr(data.error || data.message || "Failed to create payment order.");
|
||||
return;
|
||||
}
|
||||
|
||||
// In production, this would open Razorpay checkout
|
||||
// For now, show success message
|
||||
setMsg(
|
||||
`Order created for ${pkg.name}. Complete payment to receive ${pkg.tracecoins_amount} Tracecoins.`
|
||||
);
|
||||
|
||||
// Refresh data after short delay
|
||||
setTimeout(() => {
|
||||
loadAllData();
|
||||
}, 2000);
|
||||
} catch {
|
||||
setErr("Network error while creating payment order.");
|
||||
} finally {
|
||||
setBusyPackageId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
try {
|
||||
return new Date(dateStr).toLocaleString("en-IN");
|
||||
} catch {
|
||||
return dateStr;
|
||||
}
|
||||
};
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat("en-IN", {
|
||||
style: "currency",
|
||||
currency: "INR",
|
||||
minimumFractionDigits: 0,
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: 'grid', gap: '14px', 'max-width': '980px' }}>
|
||||
<div style={{ display: "grid", gap: "14px", "max-width": "980px" }}>
|
||||
<div style={CARD}>
|
||||
<p style={{ margin: '0', 'font-size': '22px', 'font-weight': '800', color: '#0D0D2A' }}>Credits</p>
|
||||
<p style={{ margin: '4px 0 0', 'font-size': '13px', color: '#6B7280' }}>
|
||||
{isProfessional() ? 'Track Tracecoin balance and usage history.' : 'Credits and billing summary for your account.'}
|
||||
<p style={{ margin: "0", "font-size": "22px", "font-weight": "800", color: "#0D0D2A" }}>
|
||||
Credits & Billing
|
||||
</p>
|
||||
<p style={{ margin: "4px 0 0", "font-size": "13px", color: "#6B7280" }}>
|
||||
Manage your Tracecoin balance, purchase packages, and view transaction history.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Show when={err()}>
|
||||
<div style={{ ...CARD, border: '1px solid #FECACA', background: '#FEF2F2', padding: '12px 14px', color: '#B91C1C', 'font-size': '13px', 'font-weight': '600' }}>{err()}</div>
|
||||
<div
|
||||
style={{
|
||||
...CARD,
|
||||
border: "1px solid #FECACA",
|
||||
background: "#FEF2F2",
|
||||
padding: "12px 14px",
|
||||
color: "#B91C1C",
|
||||
"font-size": "13px",
|
||||
"font-weight": "600",
|
||||
}}
|
||||
>
|
||||
{err()}
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={msg()}>
|
||||
<div
|
||||
style={{
|
||||
...CARD,
|
||||
border: "1px solid #BBF7D0",
|
||||
background: "#ECFDF5",
|
||||
padding: "12px 14px",
|
||||
color: "#065F46",
|
||||
"font-size": "13px",
|
||||
"font-weight": "600",
|
||||
}}
|
||||
>
|
||||
{msg()}
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Tabs */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
borderBottom: "1px solid #E5E7EB",
|
||||
paddingBottom: "10px",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("overview")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
borderRadius: "8px",
|
||||
border: "none",
|
||||
background: activeTab() === "overview" ? "#FF5E13" : "transparent",
|
||||
color: activeTab() === "overview" ? "#fff" : "#6B7280",
|
||||
fontSize: "13px",
|
||||
fontWeight: "600",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Overview
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("buy")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
borderRadius: "8px",
|
||||
border: "none",
|
||||
background: activeTab() === "buy" ? "#FF5E13" : "transparent",
|
||||
color: activeTab() === "buy" ? "#fff" : "#6B7280",
|
||||
fontSize: "13px",
|
||||
fontWeight: "600",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Buy Credits
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("transactions")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
borderRadius: "8px",
|
||||
border: "none",
|
||||
background: activeTab() === "transactions" ? "#FF5E13" : "transparent",
|
||||
color: activeTab() === "transactions" ? "#fff" : "#6B7280",
|
||||
fontSize: "13px",
|
||||
fontWeight: "600",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Transactions
|
||||
</button>
|
||||
<button type="button" onClick={loadAllData} style={{ ...BTN_GHOST, marginLeft: "auto" }}>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Show when={loading()}>
|
||||
<div style={{ ...CARD, 'text-align': 'center', color: '#9CA3AF' }}>Loading credits...</div>
|
||||
<div style={{ ...CARD, "text-align": "center", color: "#9CA3AF" }}>Loading credits...</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!loading() && !isProfessional()}>
|
||||
<div style={CARD}>
|
||||
<p style={{ margin: '0', 'font-size': '15px', 'font-weight': '700', color: '#111827' }}>Billing Overview</p>
|
||||
<p style={{ margin: '8px 0 0', 'font-size': '13px', color: '#6B7280' }}>
|
||||
Your role does not use professional wallet endpoints. Billing and payments are managed through role-specific transactions.
|
||||
</p>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!loading() && isProfessional()}>
|
||||
<div style={{ ...CARD, display: 'grid', 'grid-template-columns': '1fr auto', gap: '10px', 'align-items': 'center' }}>
|
||||
<div>
|
||||
<p style={{ margin: '0', 'font-size': '12px', 'letter-spacing': '0.06em', 'text-transform': 'uppercase', color: '#6B7280' }}>Current Balance</p>
|
||||
<p style={{ margin: '6px 0 0', 'font-size': '28px', 'font-weight': '800', color: '#111827' }}>
|
||||
{wallet()?.balance ?? 0} <span style={{ 'font-size': '14px', color: '#6B7280', 'font-weight': '600' }}>Tracecoins</span>
|
||||
{/* Overview Tab */}
|
||||
<Show when={!loading() && activeTab() === "overview"}>
|
||||
<Show when={!isProfessional()}>
|
||||
<div style={CARD}>
|
||||
<p style={{ margin: "0", "font-size": "15px", "font-weight": "700", color: "#111827" }}>
|
||||
Billing Overview
|
||||
</p>
|
||||
<p style={{ margin: "8px 0 0", "font-size": "13px", color: "#6B7280" }}>
|
||||
Your role does not use professional wallet endpoints. Billing and payments are managed
|
||||
through role-specific transactions.
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" onClick={loadData} style={BTN_GHOST}>Refresh</button>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={isProfessional()}>
|
||||
<div
|
||||
style={{
|
||||
...CARD,
|
||||
display: "grid",
|
||||
"grid-template-columns": "1fr auto",
|
||||
gap: "10px",
|
||||
"align-items": "center",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "12px",
|
||||
"letter-spacing": "0.06em",
|
||||
"text-transform": "uppercase",
|
||||
color: "#6B7280",
|
||||
}}
|
||||
>
|
||||
Current Balance
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
margin: "6px 0 0",
|
||||
"font-size": "36px",
|
||||
"font-weight": "800",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
{wallet()?.balance ?? 0}{" "}
|
||||
<span style={{ "font-size": "16px", color: "#6B7280", "font-weight": "600" }}>
|
||||
Tracecoins
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" onClick={() => setActiveTab("buy")} style={BTN_PRIMARY}>
|
||||
Buy Credits
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={CARD}>
|
||||
<p
|
||||
style={{
|
||||
margin: "0 0 10px",
|
||||
"font-size": "16px",
|
||||
"font-weight": "700",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
Recent Ledger
|
||||
</p>
|
||||
<Show when={ledger().length === 0}>
|
||||
<p style={{ margin: "0", "font-size": "13px", color: "#6B7280" }}>
|
||||
No ledger entries yet.
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={ledger().length > 0}>
|
||||
<div style={{ display: "grid", gap: "8px" }}>
|
||||
<For each={ledger().slice(0, 5)}>
|
||||
{(item: any) => (
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #E5E7EB",
|
||||
"border-radius": "10px",
|
||||
padding: "10px",
|
||||
background: "#FCFCFD",
|
||||
display: "flex",
|
||||
"justify-content": "space-between",
|
||||
gap: "10px",
|
||||
"flex-wrap": "wrap",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "13px",
|
||||
"font-weight": "700",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
{item.reason || item.type || "Transaction"}
|
||||
</p>
|
||||
<p style={{ margin: "4px 0 0", "font-size": "12px", color: "#6B7280" }}>
|
||||
{item.created_at ? formatDate(item.created_at) : "—"}
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "13px",
|
||||
"font-weight": "800",
|
||||
color: Number(item.amount || 0) >= 0 ? "#15803D" : "#B91C1C",
|
||||
}}
|
||||
>
|
||||
{Number(item.amount || 0) >= 0 ? "+" : ""}
|
||||
{item.amount || 0}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<Show when={ledger().length > 5}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("transactions")}
|
||||
style={{ ...BTN_GHOST, "margin-top": "10px", width: "100%" }}
|
||||
>
|
||||
View All Transactions
|
||||
</button>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
|
||||
{/* Buy Credits Tab */}
|
||||
<Show when={!loading() && activeTab() === "buy"}>
|
||||
<div style={CARD}>
|
||||
<p style={{ margin: '0 0 10px', 'font-size': '16px', 'font-weight': '700', color: '#111827' }}>Recent Ledger</p>
|
||||
<Show when={ledger().length === 0}>
|
||||
<p style={{ margin: '0', 'font-size': '13px', color: '#6B7280' }}>No ledger entries yet.</p>
|
||||
<p
|
||||
style={{
|
||||
margin: "0 0 10px",
|
||||
"font-size": "16px",
|
||||
"font-weight": "700",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
Purchase Tracecoins
|
||||
</p>
|
||||
<p style={{ margin: "0 0 16px", "font-size": "13px", color: "#6B7280" }}>
|
||||
Select a package to purchase Tracecoins. These can be used to unlock leads and premium
|
||||
features.
|
||||
</p>
|
||||
|
||||
<Show when={packages().length === 0}>
|
||||
<p style={{ margin: "0", "font-size": "13px", color: "#6B7280" }}>
|
||||
No packages available for your role.
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={ledger().length > 0}>
|
||||
<div style={{ display: 'grid', gap: '8px' }}>
|
||||
|
||||
<Show when={packages().length > 0}>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
"grid-template-columns": "repeat(auto-fill, minmax(240px, 1fr))",
|
||||
gap: "14px",
|
||||
}}
|
||||
>
|
||||
<For each={packages()}>
|
||||
{(pkg) => (
|
||||
<div
|
||||
style={{
|
||||
border: "2px solid #E5E7EB",
|
||||
borderRadius: "12px",
|
||||
padding: "16px",
|
||||
background: "#fff",
|
||||
display: "flex",
|
||||
"flex-direction": "column",
|
||||
gap: "8px",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "16px",
|
||||
"font-weight": "800",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
{pkg.name}
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
margin: "4px 0 0",
|
||||
"font-size": "24px",
|
||||
"font-weight": "800",
|
||||
color: "#FF5E13",
|
||||
}}
|
||||
>
|
||||
{formatCurrency(pkg.price_inr)}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
"align-items": "center",
|
||||
gap: "6px",
|
||||
margin: "8px 0",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: "20px" }}>🪙</span>
|
||||
<span style={{ fontSize: "15px", fontWeight: "700", color: "#111827" }}>
|
||||
{pkg.tracecoins_amount} Tracecoins
|
||||
</span>
|
||||
</div>
|
||||
<Show when={pkg.description}>
|
||||
<p style={{ margin: "0", "font-size": "12px", color: "#6B7280", flex: "1" }}>
|
||||
{pkg.description}
|
||||
</p>
|
||||
</Show>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => buyPackage(pkg)}
|
||||
disabled={busyPackageId() === pkg.id}
|
||||
style={{
|
||||
...BTN_PRIMARY,
|
||||
width: "100%",
|
||||
marginTop: "8px",
|
||||
opacity: busyPackageId() === pkg.id ? "0.7" : "1",
|
||||
}}
|
||||
>
|
||||
{busyPackageId() === pkg.id ? "Processing..." : "Buy Now"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Transactions Tab */}
|
||||
<Show when={!loading() && activeTab() === "transactions"}>
|
||||
<div style={CARD}>
|
||||
<p
|
||||
style={{
|
||||
margin: "0 0 10px",
|
||||
"font-size": "16px",
|
||||
"font-weight": "700",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
Transaction History
|
||||
</p>
|
||||
|
||||
<Show when={loadingPayments()}>
|
||||
<p style={{ margin: "0", "font-size": "13px", color: "#9CA3AF" }}>
|
||||
Loading transactions...
|
||||
</p>
|
||||
</Show>
|
||||
|
||||
<Show when={!loadingPayments() && payments().length === 0 && ledger().length === 0}>
|
||||
<p style={{ margin: "0", "font-size": "13px", color: "#6B7280" }}>
|
||||
No transactions found.
|
||||
</p>
|
||||
</Show>
|
||||
|
||||
<Show when={!loadingPayments() && (payments().length > 0 || ledger().length > 0)}>
|
||||
<div style={{ display: "grid", gap: "8px" }}>
|
||||
{/* Wallet Ledger */}
|
||||
<For each={ledger()}>
|
||||
{(item: any) => (
|
||||
<div style={{ border: '1px solid #E5E7EB', 'border-radius': '10px', padding: '10px', background: '#FCFCFD', display: 'flex', 'justify-content': 'space-between', gap: '10px', 'flex-wrap': 'wrap' }}>
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #E5E7EB",
|
||||
"border-radius": "10px",
|
||||
padding: "12px",
|
||||
background: "#FCFCFD",
|
||||
display: "flex",
|
||||
"justify-content": "space-between",
|
||||
"align-items": "center",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<p style={{ margin: '0', 'font-size': '13px', 'font-weight': '700', color: '#111827' }}>{item.reason || item.type || 'Transaction'}</p>
|
||||
<p style={{ margin: '4px 0 0', 'font-size': '12px', color: '#6B7280' }}>{item.created_at ? new Date(item.created_at).toLocaleString('en-IN') : '—'}</p>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "13px",
|
||||
"font-weight": "700",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
{item.reason || "Transaction"}
|
||||
</p>
|
||||
<p style={{ margin: "4px 0 0", "font-size": "12px", color: "#6B7280" }}>
|
||||
{formatDate(item.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ textAlign: "right" }}>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "14px",
|
||||
"font-weight": "800",
|
||||
color: Number(item.amount || 0) >= 0 ? "#15803D" : "#B91C1C",
|
||||
}}
|
||||
>
|
||||
{Number(item.amount || 0) >= 0 ? "+" : ""}
|
||||
{item.amount || 0} TC
|
||||
</p>
|
||||
<p style={{ margin: "2px 0 0", "font-size": "11px", color: "#9CA3AF" }}>
|
||||
Ledger
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
||||
{/* Payments */}
|
||||
<For each={payments()}>
|
||||
{(payment) => (
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #E5E7EB",
|
||||
"border-radius": "10px",
|
||||
padding: "12px",
|
||||
background: "#FCFCFD",
|
||||
display: "flex",
|
||||
"justify-content": "space-between",
|
||||
"align-items": "center",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "13px",
|
||||
"font-weight": "700",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
{payment.package_name || "Credit Purchase"}
|
||||
</p>
|
||||
<p style={{ margin: "4px 0 0", "font-size": "12px", color: "#6B7280" }}>
|
||||
{formatDate(payment.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ textAlign: "right" }}>
|
||||
<p
|
||||
style={{
|
||||
margin: "0",
|
||||
"font-size": "14px",
|
||||
"font-weight": "800",
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
{formatCurrency(payment.amount_inr)}
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
margin: "2px 0 0",
|
||||
"font-size": "12px",
|
||||
color: payment.status === "SUCCESS" ? "#15803D" : "#D97706",
|
||||
}}
|
||||
>
|
||||
{payment.status === "SUCCESS" ? "✓ Completed" : payment.status}
|
||||
</p>
|
||||
<Show when={payment.tracecoins_credited > 0}>
|
||||
<p style={{ margin: "2px 0 0", "font-size": "11px", color: "#6B7280" }}>
|
||||
+{payment.tracecoins_credited} TC
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
<p style={{ margin: '0', 'font-size': '13px', 'font-weight': '800', color: Number(item.amount || 0) >= 0 ? '#15803D' : '#B91C1C' }}>
|
||||
{Number(item.amount || 0) >= 0 ? '+' : ''}{item.amount || 0}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
|
@ -115,4 +646,3 @@ export default function CreditsPage(props: Props) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue