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 { 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() { 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 ( return (
<section class="dashboard-card"> <div style={{ 'max-width': '900px' }}>
<h1>Packages</h1> <div style={{ 'margin-bottom': '28px' }}>
<p class="dashboard-muted"> <h1 style={{ margin: 0, 'font-size': '22px', 'font-weight': '800' }}>Packages</h1>
Package purchase flow is being finalized in the Rust payments module. <p style={{ margin: '6px 0 0', color: '#64748b', 'font-size': '14px' }}>
</p> Available packages for your account type.
<p> </p>
You can review your current wallet in <A href="/dashboard/wallet">Wallet</A>. </div>
</p>
</section> <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'; 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() { export default function BuyTracecoins() {
const rc = () => authState().runtime_config; const rc = () => authState().runtime_config;
const role = () => rc()?.role ?? '';
const BUNDLES = [ const [packages] = createResource(role, async (roleKey) => {
{ id: 'basic', name: 'Basic Pack', coins: 100, price: 500, popular: false, description: 'Perfect for beginners' }, if (!roleKey) return [];
{ id: 'pro', name: 'Professional Pack', coins: 300, price: 1200, popular: true, description: 'Best value for active pros' }, try {
{ id: 'elite', name: 'Elite Pack', coins: 1000, price: 3500, popular: false, description: 'Power user savings' }, 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 ( return (
<div style={{ 'max-width': '1000px' }}> <div style={{ 'max-width': '1000px' }}>
@ -19,59 +39,78 @@ export default function BuyTracecoins() {
</p> </p>
</div> </div>
<div style={{ display: 'grid', 'grid-template-columns': 'repeat(auto-fit, minmax(300px, 1fr))', gap: '20px' }}> <Show when={packages.loading}>
<For each={BUNDLES}> <p style={{ color: '#94a3b8' }}>Loading packages</p>
{(bundle) => ( </Show>
<div
class="table-card" <Show when={!packages.loading && (packages()?.length ?? 0) === 0}>
style={{ <div class="table-card" style={{ padding: '40px', 'text-align': 'center', color: '#94a3b8' }}>
padding: '32px', No packages available for your role at this time.
position: 'relative', </div>
display: 'flex', </Show>
'flex-direction': 'column',
'align-items': 'center', <Show when={!packages.loading && (packages()?.length ?? 0) > 0}>
border: bundle.popular ? '2px solid #fd6116' : '1px solid rgba(16, 11, 47, 0.08)' <div style={{ display: 'grid', 'grid-template-columns': 'repeat(auto-fit, minmax(280px, 1fr))', gap: '20px' }}>
}} <For each={packages()}>
> {(pkg, i) => {
{bundle.popular && ( const isPopular = () => i() === 1;
<div style={{ return (
position: 'absolute', <div
top: '-12px', class="table-card"
background: '#fd6116', style={{
color: '#fff', padding: '32px',
padding: '2px 12px', position: 'relative',
'border-radius': '999px', display: 'flex',
'font-size': '11px', 'flex-direction': 'column',
'font-weight': '800', 'align-items': 'center',
'text-transform': 'uppercase' border: isPopular() ? '2px solid #fd6116' : '1px solid rgba(16, 11, 47, 0.08)',
}}> }}
Most Popular >
<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> </div>
)} );
}}
<h3 style={{ margin: '0 0 8px', 'font-size': '18px', 'font-weight': '800' }}>{bundle.name}</h3> </For>
<p style={{ margin: '0 0 24px', color: '#64748b', 'font-size': '13px', 'text-align': 'center' }}>{bundle.description}</p> </div>
</Show>
<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>
<div class="table-card" style={{ 'margin-top': '40px', padding: '24px', background: '#f8fafc' }}> <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> <h4 style={{ margin: '0 0 12px', 'font-size': '15px', 'font-weight': '700' }}>Safe & Secure Payments</h4>