Update buy and packages pages to load real data from API

- wallet/buy.tsx: createResource fetches /api/packages?role= instead of hardcoded bundles
- packages.tsx: replaced stub with real package list from API; shows type badge, price, tracecoin amount

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar 2026-04-02 18:11:54 +02:00
parent 3209d13011
commit bcb940f3f1
2 changed files with 201 additions and 67 deletions

View file

@ -1,15 +1,110 @@
import { createResource, Show, For } from 'solid-js';
import { A } from '@solidjs/router';
import { authState } from '~/lib/auth';
const API = '/api/gateway';
type Package = {
id: string;
name: string;
role_key: string;
package_type: string;
tracecoins_amount: number;
price_inr: number;
description?: string;
};
const PACKAGE_TYPE_LABELS: Record<string, string> = {
TRACECOIN_BUNDLE: 'Tracecoin Bundle',
JOB_POSTING: 'Job Posting',
CONTACT_VIEWS: 'Contact Views',
};
export default function DashboardPackagesPage() {
const rc = () => authState().runtime_config;
const role = () => rc()?.role ?? '';
const [packages] = createResource(role, async (roleKey) => {
if (!roleKey) return [];
try {
const params = new URLSearchParams({ role: roleKey });
const res = await fetch(`${API}/api/packages?${params}`);
if (!res.ok) return [];
const data = await res.json();
return (Array.isArray(data) ? data : (data.packages ?? [])) as Package[];
} catch {
return [];
}
});
return (
<section class="dashboard-card">
<h1>Packages</h1>
<p class="dashboard-muted">
Package purchase flow is being finalized in the Rust payments module.
</p>
<p>
You can review your current wallet in <A href="/dashboard/wallet">Wallet</A>.
</p>
</section>
<div style={{ 'max-width': '900px' }}>
<div style={{ 'margin-bottom': '28px' }}>
<h1 style={{ margin: 0, 'font-size': '22px', 'font-weight': '800' }}>Packages</h1>
<p style={{ margin: '6px 0 0', color: '#64748b', 'font-size': '14px' }}>
Available packages for your account type.
</p>
</div>
<Show when={packages.loading}>
<p style={{ color: '#94a3b8' }}>Loading packages</p>
</Show>
<Show when={!packages.loading && (packages()?.length ?? 0) === 0}>
<section class="dashboard-card">
<p class="dashboard-muted">No packages available for your role at this time.</p>
<p>
You can manage your Tracecoins in <A href="/dashboard/wallet">Wallet</A>.
</p>
</section>
</Show>
<Show when={!packages.loading && (packages()?.length ?? 0) > 0}>
<div style={{ display: 'grid', 'grid-template-columns': 'repeat(auto-fill, minmax(260px, 1fr))', gap: '16px' }}>
<For each={packages()}>
{(pkg) => (
<div class="table-card" style={{ padding: '24px', display: 'flex', 'flex-direction': 'column', gap: '12px' }}>
<div>
<span style={{
'font-size': '11px',
'font-weight': '700',
'text-transform': 'uppercase',
color: '#fd6116',
'letter-spacing': '0.06em',
}}>
{PACKAGE_TYPE_LABELS[pkg.package_type] ?? pkg.package_type}
</span>
<h3 style={{ margin: '4px 0 0', 'font-size': '16px', 'font-weight': '800' }}>{pkg.name}</h3>
</div>
<Show when={pkg.description}>
<p style={{ margin: 0, color: '#64748b', 'font-size': '13px' }}>{pkg.description}</p>
</Show>
<div style={{ display: 'flex', 'align-items': 'baseline', gap: '8px' }}>
<span style={{ 'font-size': '28px', 'font-weight': '800' }}>
{(pkg.price_inr / 100).toLocaleString('en-IN')}
</span>
</div>
<Show when={pkg.tracecoins_amount > 0}>
<p style={{ margin: 0, 'font-size': '13px', color: '#64748b' }}>
🪙 {pkg.tracecoins_amount} Tracecoins included
</p>
</Show>
<button
class="btn btn-primary"
style={{ 'margin-top': 'auto' }}
onClick={() => alert('Payment gateway integration coming soon.')}
>
Buy Now
</button>
</div>
)}
</For>
</div>
</Show>
</div>
);
}

View file

@ -1,14 +1,34 @@
import { For } from 'solid-js';
import { createResource, Show, For } from 'solid-js';
import { authState } from '~/lib/auth';
const API = '/api/gateway';
type Package = {
id: string;
name: string;
role_key: string;
package_type: string;
tracecoins_amount: number;
price_inr: number;
description?: string;
};
export default function BuyTracecoins() {
const rc = () => authState().runtime_config;
const role = () => rc()?.role ?? '';
const BUNDLES = [
{ id: 'basic', name: 'Basic Pack', coins: 100, price: 500, popular: false, description: 'Perfect for beginners' },
{ id: 'pro', name: 'Professional Pack', coins: 300, price: 1200, popular: true, description: 'Best value for active pros' },
{ id: 'elite', name: 'Elite Pack', coins: 1000, price: 3500, popular: false, description: 'Power user savings' },
];
const [packages] = createResource(role, async (roleKey) => {
if (!roleKey) return [];
try {
const params = new URLSearchParams({ role: roleKey });
const res = await fetch(`${API}/api/packages?${params}`);
if (!res.ok) return [];
const data = await res.json();
return (Array.isArray(data) ? data : (data.packages ?? [])) as Package[];
} catch {
return [];
}
});
return (
<div style={{ 'max-width': '1000px' }}>
@ -19,59 +39,78 @@ export default function BuyTracecoins() {
</p>
</div>
<div style={{ display: 'grid', 'grid-template-columns': 'repeat(auto-fit, minmax(300px, 1fr))', gap: '20px' }}>
<For each={BUNDLES}>
{(bundle) => (
<div
class="table-card"
style={{
padding: '32px',
position: 'relative',
display: 'flex',
'flex-direction': 'column',
'align-items': 'center',
border: bundle.popular ? '2px solid #fd6116' : '1px solid rgba(16, 11, 47, 0.08)'
}}
>
{bundle.popular && (
<div style={{
position: 'absolute',
top: '-12px',
background: '#fd6116',
color: '#fff',
padding: '2px 12px',
'border-radius': '999px',
'font-size': '11px',
'font-weight': '800',
'text-transform': 'uppercase'
}}>
Most Popular
<Show when={packages.loading}>
<p style={{ color: '#94a3b8' }}>Loading packages</p>
</Show>
<Show when={!packages.loading && (packages()?.length ?? 0) === 0}>
<div class="table-card" style={{ padding: '40px', 'text-align': 'center', color: '#94a3b8' }}>
No packages available for your role at this time.
</div>
</Show>
<Show when={!packages.loading && (packages()?.length ?? 0) > 0}>
<div style={{ display: 'grid', 'grid-template-columns': 'repeat(auto-fit, minmax(280px, 1fr))', gap: '20px' }}>
<For each={packages()}>
{(pkg, i) => {
const isPopular = () => i() === 1;
return (
<div
class="table-card"
style={{
padding: '32px',
position: 'relative',
display: 'flex',
'flex-direction': 'column',
'align-items': 'center',
border: isPopular() ? '2px solid #fd6116' : '1px solid rgba(16, 11, 47, 0.08)',
}}
>
<Show when={isPopular()}>
<div style={{
position: 'absolute',
top: '-12px',
background: '#fd6116',
color: '#fff',
padding: '2px 12px',
'border-radius': '999px',
'font-size': '11px',
'font-weight': '800',
'text-transform': 'uppercase',
}}>
Most Popular
</div>
</Show>
<h3 style={{ margin: '0 0 8px', 'font-size': '18px', 'font-weight': '800' }}>{pkg.name}</h3>
<Show when={pkg.description}>
<p style={{ margin: '0 0 24px', color: '#64748b', 'font-size': '13px', 'text-align': 'center' }}>
{pkg.description}
</p>
</Show>
<div style={{ 'font-size': '48px', 'font-weight': '800', color: '#111827', 'margin-bottom': '4px' }}>
🪙 {pkg.tracecoins_amount}
</div>
<div style={{ 'font-size': '14px', color: '#64748b', 'margin-bottom': '24px' }}>Tracecoins</div>
<div style={{ 'font-size': '24px', 'font-weight': '700', 'margin-bottom': '24px' }}>
{(pkg.price_inr / 100).toLocaleString('en-IN')}
</div>
<button
class="btn btn-primary"
style={{ width: '100%', 'padding-top': '14px', 'padding-bottom': '14px' }}
onClick={() => alert('Payment gateway integration coming soon.')}
>
Purchase Now
</button>
</div>
)}
<h3 style={{ margin: '0 0 8px', 'font-size': '18px', 'font-weight': '800' }}>{bundle.name}</h3>
<p style={{ margin: '0 0 24px', color: '#64748b', 'font-size': '13px', 'text-align': 'center' }}>{bundle.description}</p>
<div style={{ 'font-size': '48px', 'font-weight': '800', color: '#111827', 'margin-bottom': '4px' }}>
🪙 {bundle.coins}
</div>
<div style={{ 'font-size': '14px', color: '#64748b', 'margin-bottom': '24px' }}>Tracecoins</div>
<div style={{ 'font-size': '24px', 'font-weight': '700', 'margin-bottom': '24px' }}>
{(bundle.price).toLocaleString('en-IN')}
</div>
<button
class="btn btn-primary"
style={{ width: '100%', 'padding-top': '14px', 'padding-bottom': '14px' }}
onClick={() => alert('Payment gateway integration coming soon.')}
>
Purchase Now
</button>
</div>
)}
</For>
</div>
);
}}
</For>
</div>
</Show>
<div class="table-card" style={{ 'margin-top': '40px', padding: '24px', background: '#f8fafc' }}>
<h4 style={{ margin: '0 0 12px', 'font-size': '15px', 'font-weight': '700' }}>Safe & Secure Payments</h4>