@sudobility/consumables_pages 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +23 -2
- package/dist/CreditBalanceBadge.js +4 -4
- package/dist/CreditStorePage.js +7 -1
- package/dist/LoadingSpinner.d.ts +10 -0
- package/dist/LoadingSpinner.js +13 -0
- package/dist/PurchaseHistoryPage.d.ts +1 -1
- package/dist/PurchaseHistoryPage.js +5 -4
- package/dist/UsageHistoryPage.d.ts +1 -1
- package/dist/UsageHistoryPage.js +8 -3
- package/dist/index.d.ts +1 -0
- package/dist/types.d.ts +8 -2
- package/package.json +4 -3
package/CLAUDE.md
CHANGED
|
@@ -22,6 +22,7 @@ Web-only UI components for consumable credits system.
|
|
|
22
22
|
src/
|
|
23
23
|
├── index.ts # Barrel exports (components + types)
|
|
24
24
|
├── types.ts # All prop/label/formatter interfaces
|
|
25
|
+
├── LoadingSpinner.tsx # Shared internal loading spinner with ARIA attributes
|
|
25
26
|
├── CreditStorePage.tsx # Balance display + grid of credit packages with buy buttons
|
|
26
27
|
├── PurchaseHistoryPage.tsx # Responsive table/cards of purchase records
|
|
27
28
|
├── UsageHistoryPage.tsx # Responsive table/cards of usage records
|
|
@@ -117,13 +118,33 @@ Dependency direction: `consumables_pages` depends on `consumables_client` (peer
|
|
|
117
118
|
- **No direct CSS**: All styling uses Tailwind utility classes. Do not introduce CSS files, CSS modules, or styled-components.
|
|
118
119
|
- **Conditional wrapper element**: CreditBalanceBadge uses `const Wrapper = onClick ? "button" : "span"` for semantic HTML.
|
|
119
120
|
|
|
121
|
+
## Tailwind CSS Setup
|
|
122
|
+
|
|
123
|
+
Consumer apps must include this package's output files in their Tailwind `content` configuration so that Tailwind classes used by these components are included in the final CSS build. Components also support dark mode via `dark:` variant classes -- enable dark mode in the consuming app's Tailwind config to use them.
|
|
124
|
+
|
|
125
|
+
**Tailwind v3 (`tailwind.config.js`)**:
|
|
126
|
+
```js
|
|
127
|
+
module.exports = {
|
|
128
|
+
content: [
|
|
129
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
|
130
|
+
"./node_modules/@sudobility/consumables_pages/dist/**/*.js",
|
|
131
|
+
],
|
|
132
|
+
// ...
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Tailwind v4 (CSS-based config)**:
|
|
137
|
+
```css
|
|
138
|
+
@import "tailwindcss";
|
|
139
|
+
@source "../node_modules/@sudobility/consumables_pages/dist";
|
|
140
|
+
```
|
|
141
|
+
|
|
120
142
|
## Gotchas
|
|
121
143
|
|
|
122
|
-
- **Consumer app must provide Tailwind CSS**: This package emits Tailwind class names but does NOT bundle Tailwind itself. If the consuming app does not have Tailwind configured, components will render unstyled. This is by design.
|
|
144
|
+
- **Consumer app must provide Tailwind CSS**: This package emits Tailwind class names but does NOT bundle Tailwind itself. If the consuming app does not have Tailwind configured, components will render unstyled. This is by design. See the "Tailwind CSS Setup" section above for required content path configuration.
|
|
123
145
|
- **Components are purely presentational**: No hooks are called inside components. The parent app is responsible for calling `useBalance()`, `usePurchaseCredits()`, etc. from `consumables_client` and passing the results as props. Breaking this pattern creates tight coupling.
|
|
124
146
|
- **No internal state**: Components derive everything from props. If you need loading states or error states, they must be passed in as props, not managed internally with `useState`.
|
|
125
147
|
- **Peer dependency on consumables_client**: The package depends on `consumables_client` for TypeScript types (e.g., `CreditPackage`, `CreditBalance`), but it never imports runtime code from it. The peer dependency ensures type compatibility.
|
|
126
|
-
- **Hardcoded "Log in" button text**: The login button in `CreditStorePage` has hardcoded text `"Log in"` rather than using the labels prop. This is the only non-i18n string in the components.
|
|
127
148
|
- **`tsconfig.json` has `noEmit: true`**: The main tsconfig is for checking only. Building requires `tsconfig.build.json` (via `bun run build`).
|
|
128
149
|
|
|
129
150
|
## Testing
|
|
@@ -6,12 +6,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
*/
|
|
7
7
|
export function CreditBalanceBadge({ balance, isLoading, onClick, className, }) {
|
|
8
8
|
if (isLoading) {
|
|
9
|
-
return (_jsx("span", { className: `inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium text-gray-400 ${className || ""}`, children: _jsx("span", { className: "animate-pulse", children: "..." }) }));
|
|
9
|
+
return (_jsx("span", { className: `inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium text-gray-400 dark:text-gray-500 ${className || ""}`, role: "status", "aria-label": "Loading balance", "aria-busy": "true", children: _jsx("span", { className: "animate-pulse", children: "..." }) }));
|
|
10
10
|
}
|
|
11
11
|
if (balance === null)
|
|
12
12
|
return null;
|
|
13
13
|
const Wrapper = onClick ? "button" : "span";
|
|
14
|
-
return (_jsxs(Wrapper, { onClick: onClick, className: `inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${balance > 0
|
|
15
|
-
? "bg-blue-100 text-blue-700"
|
|
16
|
-
: "bg-red-100 text-red-700"} ${onClick ? "cursor-pointer hover:opacity-80" : ""} ${className || ""}`, children: [_jsx("svg", { className: "w-3.5 h-3.5", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-13a1 1 0 10-2 0v.092a4.535 4.535 0 00-1.676.662C6.602 6.234 6 7.009 6 8c0 .99.602 1.765 1.324 2.246.48.32 1.054.545 1.676.662v1.941c-.391-.127-.68-.317-.843-.504a1 1 0 10-1.51 1.31c.562.649 1.413 1.076 2.353 1.253V15a1 1 0 102 0v-.092a4.535 4.535 0 001.676-.662C13.398 13.766 14 12.991 14 12c0-.99-.602-1.765-1.324-2.246A4.535 4.535 0 0011 9.092V7.151c.391.127.68.317.843.504a1 1 0 101.511-1.31c-.563-.649-1.413-1.076-2.354-1.253V5z", clipRule: "evenodd" }) }), balance] }));
|
|
14
|
+
return (_jsxs(Wrapper, { onClick: onClick, "aria-label": onClick ? `Credit balance: ${balance}` : undefined, className: `inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${balance > 0
|
|
15
|
+
? "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300"
|
|
16
|
+
: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300"} ${onClick ? "cursor-pointer hover:opacity-80" : ""} ${className || ""}`, children: [_jsx("svg", { className: "w-3.5 h-3.5", fill: "currentColor", viewBox: "0 0 20 20", "aria-hidden": "true", children: _jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-13a1 1 0 10-2 0v.092a4.535 4.535 0 00-1.676.662C6.602 6.234 6 7.009 6 8c0 .99.602 1.765 1.324 2.246.48.32 1.054.545 1.676.662v1.941c-.391-.127-.68-.317-.843-.504a1 1 0 10-1.51 1.31c.562.649 1.413 1.076 2.353 1.253V15a1 1 0 102 0v-.092a4.535 4.535 0 001.676-.662C13.398 13.766 14 12.991 14 12c0-.99-.602-1.765-1.324-2.246A4.535 4.535 0 0011 9.092V7.151c.391.127.68.317.843.504a1 1 0 101.511-1.31c-.563-.649-1.413-1.076-2.354-1.253V5z", clipRule: "evenodd" }) }), balance] }));
|
|
17
17
|
}
|
package/dist/CreditStorePage.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Credit store page component displaying the user's balance and
|
|
4
|
+
* a responsive grid of purchasable credit packages. Purely presentational --
|
|
5
|
+
* all data and callbacks are passed via props.
|
|
6
|
+
*/
|
|
7
|
+
import { LoadingSpinner } from "./LoadingSpinner";
|
|
2
8
|
/**
|
|
3
9
|
* Renders a credit store with balance display, purchase packages grid,
|
|
4
10
|
* loading/error states, and a login prompt for unauthenticated users.
|
|
5
11
|
* @param props - See {@link CreditStorePageProps} for full prop documentation.
|
|
6
12
|
*/
|
|
7
13
|
export function CreditStorePage({ isAuthenticated, balance, packages, isLoading, isPurchasing, error, onPurchase, onLoginClick, labels, formatters, className, }) {
|
|
8
|
-
return (_jsxs("div", { className: className, children: [_jsx("h1", { className: "text-2xl font-bold mb-6", children: labels.title }), isAuthenticated && balance !== null && (_jsxs("div", { className: "mb-8 p-4 bg-blue-50 rounded-lg border border-blue-200", children: [_jsx("p", { className: "text-sm text-blue-600 font-medium", children: labels.currentBalanceLabel }), _jsx("p", { className: "text-3xl font-bold text-blue-900", children: formatters.formatCredits(balance) })] })), error && (_jsxs("div", { className: "mb-6 p-4 bg-red-50 rounded-lg border border-red-200", children: [_jsx("p", { className: "text-sm font-medium text-red-800", children: labels.errorTitle }), _jsx("p", { className: "text-sm text-red-600", children: error })] })), !isAuthenticated && (_jsxs("div", { className: "mb-6 p-4 bg-yellow-50 rounded-lg border border-yellow-200", children: [_jsx("p", { className: "text-sm text-yellow-800", children: labels.loginRequired }), _jsx("button", { onClick: onLoginClick, className: "mt-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700", children: "Log in" })] })), isLoading &&
|
|
14
|
+
return (_jsxs("div", { className: className, children: [_jsx("h1", { className: "text-2xl font-bold mb-6 dark:text-white", children: labels.title }), isAuthenticated && balance !== null && (_jsxs("div", { className: "mb-8 p-4 bg-blue-50 dark:bg-blue-900/30 rounded-lg border border-blue-200 dark:border-blue-800", children: [_jsx("p", { className: "text-sm text-blue-600 dark:text-blue-400 font-medium", children: labels.currentBalanceLabel }), _jsx("p", { className: "text-3xl font-bold text-blue-900 dark:text-blue-100", children: formatters.formatCredits(balance) })] })), error && (_jsxs("div", { className: "mb-6 p-4 bg-red-50 dark:bg-red-900/30 rounded-lg border border-red-200 dark:border-red-800", role: "alert", children: [_jsx("p", { className: "text-sm font-medium text-red-800 dark:text-red-300", children: labels.errorTitle }), _jsx("p", { className: "text-sm text-red-600 dark:text-red-400", children: error })] })), !isAuthenticated && (_jsxs("div", { className: "mb-6 p-4 bg-yellow-50 dark:bg-yellow-900/30 rounded-lg border border-yellow-200 dark:border-yellow-800", children: [_jsx("p", { className: "text-sm text-yellow-800 dark:text-yellow-300", children: labels.loginRequired }), _jsx("button", { onClick: onLoginClick, className: "mt-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 transition-colors", children: labels.loginButton ?? "Log in" })] })), isLoading && _jsx(LoadingSpinner, {}), !isLoading && packages.length === 0 && (_jsx("p", { className: "text-gray-500 dark:text-gray-400 text-center py-8", children: labels.noProducts })), !isLoading && packages.length > 0 && (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4", children: packages.map((pkg) => (_jsx("div", { className: "p-6 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md transition-shadow", children: _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-3xl font-bold text-gray-900 dark:text-gray-100", children: formatters.formatCredits(pkg.credits) }), formatters.getPackageDescription && (_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: formatters.getPackageDescription(pkg.packageId) })), _jsx("p", { className: "text-xl font-semibold text-blue-600 dark:text-blue-400 mt-3", children: pkg.priceString }), _jsx("button", { onClick: () => onPurchase(pkg.packageId), disabled: isPurchasing || !isAuthenticated, className: "mt-4 w-full px-4 py-2.5 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 dark:bg-blue-500 dark:hover:bg-blue-600", children: isPurchasing
|
|
9
15
|
? labels.purchasingButton
|
|
10
16
|
: labels.purchaseButton })] }) }, pkg.packageId))) }))] }));
|
|
11
17
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared internal loading spinner component used across all page
|
|
3
|
+
* components. Extracted to reduce markup duplication and centralize accessibility
|
|
4
|
+
* attributes.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Renders a centered spinning loading indicator with appropriate ARIA attributes.
|
|
8
|
+
* Internal component -- not exported from the package barrel.
|
|
9
|
+
*/
|
|
10
|
+
export declare function LoadingSpinner(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Shared internal loading spinner component used across all page
|
|
4
|
+
* components. Extracted to reduce markup duplication and centralize accessibility
|
|
5
|
+
* attributes.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Renders a centered spinning loading indicator with appropriate ARIA attributes.
|
|
9
|
+
* Internal component -- not exported from the package barrel.
|
|
10
|
+
*/
|
|
11
|
+
export function LoadingSpinner() {
|
|
12
|
+
return (_jsx("div", { className: "flex justify-center py-12", role: "status", "aria-label": "Loading", "aria-busy": "true", children: _jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 dark:border-blue-400" }) }));
|
|
13
|
+
}
|
|
@@ -9,4 +9,4 @@ import type { PurchaseHistoryPageProps } from "./types";
|
|
|
9
9
|
* Mobile: compact card layout.
|
|
10
10
|
* @param props - See {@link PurchaseHistoryPageProps} for full prop documentation.
|
|
11
11
|
*/
|
|
12
|
-
export declare function PurchaseHistoryPage({ purchases, isLoading, error, onLoadMore, hasMore, labels, formatters, className, }: PurchaseHistoryPageProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function PurchaseHistoryPage({ purchases, isLoading, error, onLoadMore, hasMore, labels, formatters, className, emptyStateComponent, }: PurchaseHistoryPageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { LoadingSpinner } from "./LoadingSpinner";
|
|
2
3
|
/**
|
|
3
4
|
* Renders a paginated list of purchase records.
|
|
4
5
|
* Desktop: table with date, credits, source, and amount columns.
|
|
5
6
|
* Mobile: compact card layout.
|
|
6
7
|
* @param props - See {@link PurchaseHistoryPageProps} for full prop documentation.
|
|
7
8
|
*/
|
|
8
|
-
export function PurchaseHistoryPage({ purchases, isLoading, error, onLoadMore, hasMore, labels, formatters, className, }) {
|
|
9
|
-
return (_jsxs("div", { className: className, children: [_jsx("h1", { className: "text-2xl font-bold mb-6", children: labels.title }), error && (_jsx("div", { className: "mb-4 p-3 bg-red-50 rounded-lg border border-red-200", children: _jsx("p", { className: "text-sm text-red-600", children: error }) })), isLoading &&
|
|
9
|
+
export function PurchaseHistoryPage({ purchases, isLoading, error, onLoadMore, hasMore, labels, formatters, className, emptyStateComponent, }) {
|
|
10
|
+
return (_jsxs("div", { className: className, children: [_jsx("h1", { className: "text-2xl font-bold mb-6 dark:text-white", children: labels.title }), error && (_jsx("div", { className: "mb-4 p-3 bg-red-50 dark:bg-red-900/30 rounded-lg border border-red-200 dark:border-red-800", role: "alert", children: _jsx("p", { className: "text-sm text-red-600 dark:text-red-400", children: error }) })), isLoading && _jsx(LoadingSpinner, {}), !isLoading && purchases.length === 0 && (_jsx(_Fragment, { children: emptyStateComponent ?? (_jsx("p", { className: "text-gray-500 dark:text-gray-400 text-center py-8", children: labels.noRecords })) })), !isLoading && purchases.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "hidden sm:block overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", "aria-label": labels.title, children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-gray-200 dark:border-gray-700", children: [_jsx("th", { className: "text-left py-3 px-4 font-medium text-gray-500 dark:text-gray-400", children: labels.columnDate }), _jsx("th", { className: "text-right py-3 px-4 font-medium text-gray-500 dark:text-gray-400", children: labels.columnCredits }), _jsx("th", { className: "text-left py-3 px-4 font-medium text-gray-500 dark:text-gray-400", children: labels.columnSource }), _jsx("th", { className: "text-right py-3 px-4 font-medium text-gray-500 dark:text-gray-400", children: labels.columnAmount })] }) }), _jsx("tbody", { children: purchases.map((purchase) => (_jsxs("tr", { className: "border-b border-gray-100 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/50", children: [_jsx("td", { className: "py-3 px-4 text-gray-700 dark:text-gray-300", children: formatters.formatDate(purchase.created_at) }), _jsxs("td", { className: "py-3 px-4 text-right font-medium text-green-600 dark:text-green-400", children: ["+", purchase.credits] }), _jsx("td", { className: "py-3 px-4 text-gray-600 dark:text-gray-400", children: formatters.formatSource(purchase.source) }), _jsx("td", { className: "py-3 px-4 text-right text-gray-600 dark:text-gray-400", children: purchase.price_cents != null && purchase.currency
|
|
10
11
|
? formatters.formatAmount(purchase.price_cents, purchase.currency)
|
|
11
|
-
: "-" })] }, purchase.id))) })] }) }), _jsx("div", { className: "sm:hidden space-y-3", children: purchases.map((purchase) => (_jsx("div", { className: "p-4 bg-white rounded-lg border border-gray-200", children: _jsxs("div", { className: "flex justify-between items-start", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-500", children: formatters.formatDate(purchase.created_at) }), _jsx("p", { className: "text-sm text-gray-600", children: formatters.formatSource(purchase.source) })] }), _jsxs("div", { className: "text-right", children: [_jsxs("p", { className: "font-medium text-green-600", children: ["+", purchase.credits] }), purchase.price_cents != null && purchase.currency && (_jsx("p", { className: "text-sm text-gray-500", children: formatters.formatAmount(purchase.price_cents, purchase.currency) }))] })] }) }, purchase.id))) }), hasMore && onLoadMore && (_jsx("div", { className: "mt-4 text-center", children: _jsx("button", { onClick: onLoadMore, className: "px-4 py-2 text-sm text-blue-600 hover:text-blue-800 font-medium", children: labels.loadMore }) }))] }))] }));
|
|
12
|
+
: "-" })] }, purchase.id))) })] }) }), _jsx("div", { className: "sm:hidden space-y-3", children: purchases.map((purchase) => (_jsx("div", { className: "p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700", children: _jsxs("div", { className: "flex justify-between items-start", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: formatters.formatDate(purchase.created_at) }), _jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: formatters.formatSource(purchase.source) })] }), _jsxs("div", { className: "text-right", children: [_jsxs("p", { className: "font-medium text-green-600 dark:text-green-400", children: ["+", purchase.credits] }), purchase.price_cents != null && purchase.currency && (_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: formatters.formatAmount(purchase.price_cents, purchase.currency) }))] })] }) }, purchase.id))) }), hasMore && onLoadMore && (_jsx("div", { className: "mt-4 text-center", children: _jsx("button", { onClick: onLoadMore, className: "px-4 py-2 text-sm text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 font-medium", children: labels.loadMore }) }))] }))] }));
|
|
12
13
|
}
|
|
@@ -9,4 +9,4 @@ import type { UsageHistoryPageProps } from "./types";
|
|
|
9
9
|
* Mobile: compact card layout.
|
|
10
10
|
* @param props - See {@link UsageHistoryPageProps} for full prop documentation.
|
|
11
11
|
*/
|
|
12
|
-
export declare function UsageHistoryPage({ usages, isLoading, error, onLoadMore, hasMore, labels, formatters, className, }: UsageHistoryPageProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function UsageHistoryPage({ usages, isLoading, error, onLoadMore, hasMore, labels, formatters, className, emptyStateComponent, }: UsageHistoryPageProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/UsageHistoryPage.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Usage history page component with responsive table (desktop)
|
|
4
|
+
* and card (mobile) layouts. Supports load-more pagination.
|
|
5
|
+
*/
|
|
6
|
+
import { LoadingSpinner } from "./LoadingSpinner";
|
|
2
7
|
/**
|
|
3
8
|
* Renders a paginated list of usage records.
|
|
4
9
|
* Desktop: table with date and filename columns.
|
|
5
10
|
* Mobile: compact card layout.
|
|
6
11
|
* @param props - See {@link UsageHistoryPageProps} for full prop documentation.
|
|
7
12
|
*/
|
|
8
|
-
export function UsageHistoryPage({ usages, isLoading, error, onLoadMore, hasMore, labels, formatters, className, }) {
|
|
9
|
-
return (_jsxs("div", { className: className, children: [_jsx("h1", { className: "text-2xl font-bold mb-6", children: labels.title }), error && (_jsx("div", { className: "mb-4 p-3 bg-red-50 rounded-lg border border-red-200", children: _jsx("p", { className: "text-sm text-red-600", children: error }) })), isLoading &&
|
|
13
|
+
export function UsageHistoryPage({ usages, isLoading, error, onLoadMore, hasMore, labels, formatters, className, emptyStateComponent, }) {
|
|
14
|
+
return (_jsxs("div", { className: className, children: [_jsx("h1", { className: "text-2xl font-bold mb-6 dark:text-white", children: labels.title }), error && (_jsx("div", { className: "mb-4 p-3 bg-red-50 dark:bg-red-900/30 rounded-lg border border-red-200 dark:border-red-800", role: "alert", children: _jsx("p", { className: "text-sm text-red-600 dark:text-red-400", children: error }) })), isLoading && _jsx(LoadingSpinner, {}), !isLoading && usages.length === 0 && (_jsx(_Fragment, { children: emptyStateComponent ?? (_jsx("p", { className: "text-gray-500 dark:text-gray-400 text-center py-8", children: labels.noRecords })) })), !isLoading && usages.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "hidden sm:block overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", "aria-label": labels.title, children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-gray-200 dark:border-gray-700", children: [_jsx("th", { className: "text-left py-3 px-4 font-medium text-gray-500 dark:text-gray-400", children: labels.columnDate }), _jsx("th", { className: "text-left py-3 px-4 font-medium text-gray-500 dark:text-gray-400", children: labels.columnFilename })] }) }), _jsx("tbody", { children: usages.map((usage) => (_jsxs("tr", { className: "border-b border-gray-100 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/50", children: [_jsx("td", { className: "py-3 px-4 text-gray-700 dark:text-gray-300", children: formatters.formatDate(usage.created_at) }), _jsx("td", { className: "py-3 px-4 text-gray-600 dark:text-gray-400", children: usage.filename || "-" })] }, usage.id))) })] }) }), _jsx("div", { className: "sm:hidden space-y-3", children: usages.map((usage) => (_jsxs("div", { className: "p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700", children: [_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: formatters.formatDate(usage.created_at) }), _jsx("p", { className: "text-sm text-gray-700 dark:text-gray-300 font-medium", children: usage.filename || "-" })] }, usage.id))) }), hasMore && onLoadMore && (_jsx("div", { className: "mt-4 text-center", children: _jsx("button", { onClick: onLoadMore, className: "px-4 py-2 text-sm text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 font-medium", children: labels.loadMore }) }))] }))] }));
|
|
10
15
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { PurchaseHistoryPage } from "./PurchaseHistoryPage";
|
|
|
3
3
|
export { UsageHistoryPage } from "./UsageHistoryPage";
|
|
4
4
|
export { CreditBalanceBadge } from "./CreditBalanceBadge";
|
|
5
5
|
export type { CreditStorePageProps, CreditStorePageLabels, CreditStorePageFormatters, PurchaseHistoryPageProps, PurchaseHistoryPageLabels, PurchaseHistoryPageFormatters, UsageHistoryPageProps, UsageHistoryPageLabels, UsageHistoryPageFormatters, CreditBalanceBadgeProps, } from "./types";
|
|
6
|
+
export type { ConsumableSource } from "@sudobility/types";
|
package/dist/types.d.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* All user-facing strings are defined via Labels for i18n support.
|
|
6
6
|
*/
|
|
7
7
|
import type { CreditPackage } from "@sudobility/consumables_client";
|
|
8
|
-
import type { ConsumablePurchaseRecord, ConsumableUsageRecord } from "@sudobility/types";
|
|
8
|
+
import type { ConsumablePurchaseRecord, ConsumableSource, ConsumableUsageRecord } from "@sudobility/types";
|
|
9
|
+
import type { ReactNode } from "react";
|
|
9
10
|
/** Localizable label strings for the CreditStorePage component. */
|
|
10
11
|
export interface CreditStorePageLabels {
|
|
11
12
|
title: string;
|
|
@@ -16,6 +17,7 @@ export interface CreditStorePageLabels {
|
|
|
16
17
|
noProducts: string;
|
|
17
18
|
errorTitle: string;
|
|
18
19
|
loginRequired: string;
|
|
20
|
+
loginButton?: string;
|
|
19
21
|
}
|
|
20
22
|
/** Formatting functions for the CreditStorePage component. */
|
|
21
23
|
export interface CreditStorePageFormatters {
|
|
@@ -51,7 +53,7 @@ export interface PurchaseHistoryPageLabels {
|
|
|
51
53
|
export interface PurchaseHistoryPageFormatters {
|
|
52
54
|
formatDate: (dateStr: string) => string;
|
|
53
55
|
formatAmount: (cents: number, currency: string) => string;
|
|
54
|
-
formatSource: (source:
|
|
56
|
+
formatSource: (source: ConsumableSource) => string;
|
|
55
57
|
}
|
|
56
58
|
/** Props for the PurchaseHistoryPage component. */
|
|
57
59
|
export interface PurchaseHistoryPageProps {
|
|
@@ -63,6 +65,8 @@ export interface PurchaseHistoryPageProps {
|
|
|
63
65
|
labels: PurchaseHistoryPageLabels;
|
|
64
66
|
formatters: PurchaseHistoryPageFormatters;
|
|
65
67
|
className?: string;
|
|
68
|
+
/** Optional custom component to render when the purchase list is empty. */
|
|
69
|
+
emptyStateComponent?: ReactNode;
|
|
66
70
|
}
|
|
67
71
|
/** Localizable label strings for the UsageHistoryPage component. */
|
|
68
72
|
export interface UsageHistoryPageLabels {
|
|
@@ -86,6 +90,8 @@ export interface UsageHistoryPageProps {
|
|
|
86
90
|
labels: UsageHistoryPageLabels;
|
|
87
91
|
formatters: UsageHistoryPageFormatters;
|
|
88
92
|
className?: string;
|
|
93
|
+
/** Optional custom component to render when the usage list is empty. */
|
|
94
|
+
emptyStateComponent?: ReactNode;
|
|
89
95
|
}
|
|
90
96
|
/** Props for the CreditBalanceBadge component. */
|
|
91
97
|
export interface CreditBalanceBadgeProps {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudobility/consumables_pages",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Web UI components for consumable credits (credit store, purchase history, usage history)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,16 +26,17 @@
|
|
|
26
26
|
"format": "bunx prettier --write \"src/**/*.{ts,tsx}\"",
|
|
27
27
|
"test": "vitest run",
|
|
28
28
|
"test:watch": "vitest",
|
|
29
|
+
"verify": "bun run typecheck && bun run lint && bun run test && bun run build",
|
|
29
30
|
"prepublishOnly": "bun run build"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
|
-
"@sudobility/consumables_client": "^0.0.
|
|
33
|
+
"@sudobility/consumables_client": "^0.0.9",
|
|
33
34
|
"@sudobility/types": "^1.9.54",
|
|
34
35
|
"react": "^18.0.0 || ^19.0.0",
|
|
35
36
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
|
-
"@sudobility/consumables_client": "^0.0.
|
|
39
|
+
"@sudobility/consumables_client": "^0.0.9",
|
|
39
40
|
"@testing-library/jest-dom": "^6.9.1",
|
|
40
41
|
"@testing-library/react": "^16.3.2",
|
|
41
42
|
"@types/bun": "^1.2.8",
|