@cedros/data-react 0.1.0 → 0.1.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ### Added
6
+ - `TipWidget` self-configuration: fetches tipping config from `GET {dataServerUrl}/site/config/tipping` when no `recipient` prop is provided.
7
+ - `TipWidget` wallet auto-detection: connects to browser Solana wallets (Phantom, Solflare) automatically.
8
+ - `SOL_CURRENCY` and `USDC_CURRENCY` constants now include `mint` addresses for use with `@cedros/trade-react`.
9
+ - `@cedros/trade-react` added as optional peer dependency for tipping payment flow.
10
+ - `BlogSearchInput` — CMD+K keyboard shortcut with debounced client-side search.
11
+ - `FilterDimensionChips` — generic dimension-based tag filtering for blog index.
12
+ - `BookmarkButton` — per-post bookmark toggle.
13
+ - `ContentPaywall` — metered/locked/preview paywall for blog posts.
14
+ - `BlogIndexTemplate` props: `filterDimensions`, `activeFilters`, `onFilterChange`, `onSearch`, `bookmarkedSlugs`, `onBookmarkToggle`.
15
+ - `BlogPostTemplate` props: `isBookmarked`, `onBookmarkToggle`, `slug`, `paywall`.
16
+ - `BlogPaywallConfig` type for paywall configuration including metered free reads.
17
+
18
+ ### Changed
19
+ - `TipWidget` now delegates payment to `TradeApiClient` from `@cedros/trade-react` (build → sign → execute) instead of building Solana transactions directly.
20
+ - `TipWidgetProps.recipient` is now optional (enables self-configuration mode).
21
+ - `BlogTippingConfig` replaced `payClient: TipPayClient` with `dataServerUrl`, `senderAddress`, and `signTransaction` props.
22
+ - `SOL_CURRENCY.mint` changed from `undefined` to `"So11111111111111111111111111111111111111112"`.
23
+
24
+ ### Removed
25
+ - `SolanaMicropayments` component and `createSolanaTipClient`/`detectSolanaWallet` helpers — replaced by `TipWidget` + `@cedros/trade-react`.
26
+ - `ensureRecipientAta` helper — ATA creation is now handled by the trade-react build endpoint (`createsAta: true`).
27
+ - `solanaAtaSetup.ts` and `solanaMicropayments.tsx` files.
28
+ - `TipPayClient` and `TipParams` types — no longer needed.
29
+ - `@solana/web3.js` and `@solana/spl-token` removed from package dependencies.
30
+ - "Ensure Recipient ATA" button removed from admin TippingSection.
31
+
32
+ ### Migration Notes
33
+
34
+ **Tipping (breaking):**
35
+ - Before:
36
+ ```tsx
37
+ import { SolanaMicropayments } from "@cedros/data-react/site-templates";
38
+ <SolanaMicropayments recipient="..." rpcEndpoint="..." />
39
+ ```
40
+ Or with BlogPostTemplate:
41
+ ```tsx
42
+ tipping={{ enabled: true, recipient: "...", currencies: [...], payClient: createSolanaTipClient() }}
43
+ ```
44
+ - After:
45
+ ```tsx
46
+ import { TipWidget } from "@cedros/data-react/site-templates";
47
+ <TipWidget recipient="..." />
48
+ // or self-configuring:
49
+ <TipWidget dataServerUrl="https://..." />
50
+ ```
51
+ With BlogPostTemplate:
52
+ ```tsx
53
+ tipping={{ enabled: true, dataServerUrl: "https://..." }}
54
+ // or manual:
55
+ tipping={{ enabled: true, recipient: "...", currencies: [...] }}
56
+ ```
57
+ Requires `npm install @cedros/trade-react` for tipping to work at runtime.
58
+
59
+ ---
60
+
61
+ ### Previously shipped (included for completeness)
62
+
5
63
  ### Added
6
64
  - `MarkdownContent` renderer based on `react-markdown` + `remark-gfm` + `rehype-slug`.
7
65
  - `bodyMarkdown` support in:
package/README.md CHANGED
@@ -40,7 +40,10 @@ Packaging smoke:
40
40
  - admin components/primitives
41
41
  - `@cedros/data-react/site-templates`
42
42
  - site shell/layout components
43
- - page templates
43
+ - page templates (blog, docs, home, contact, legal, not-found, dashboard)
44
+ - blog features: `BlogSearchInput`, `FilterDimensionChips`, `BookmarkButton`
45
+ - `TipWidget`, `SOL_CURRENCY`, `USDC_CURRENCY`
46
+ - `ContentPaywall`
44
47
  - routing/content helpers
45
48
  - `@cedros/data-react/admin/styles.css`
46
49
  - `@cedros/data-react/site-templates/styles.css`
@@ -127,8 +130,18 @@ Core page templates:
127
130
  - `DashboardOverviewTemplate`
128
131
 
129
132
  Blog templates:
130
- - `BlogIndexTemplate`
131
- - `BlogPostTemplate`
133
+ - `BlogIndexTemplate` — supports search, category/tag filters, bookmarks, and generic filter dimensions
134
+ - `BlogPostTemplate` — supports tipping, paywall, and bookmarks
135
+
136
+ Blog interactive features:
137
+ - `BlogSearchInput` — CMD+K keyboard shortcut, debounced client-side search
138
+ - `FilterDimensionChips` — generic dimension-based tag filtering (replaces category/tag dropdowns)
139
+ - `BookmarkButton` — per-post bookmark toggle (state managed by consumer)
140
+ - `ContentPaywall` — metered/locked/preview paywall with purchase flow via `PaywallPayClient`
141
+
142
+ Tipping:
143
+ - `TipWidget` — self-configuring tip widget with currency selection, preset amounts, and send flow
144
+ - `SOL_CURRENCY`, `USDC_CURRENCY` — predefined currency constants
132
145
 
133
146
  Docs templates:
134
147
  - `DocsIndexTemplate`
@@ -144,8 +157,88 @@ Content rendering and helpers:
144
157
  - `prepareBlogIndex`
145
158
  - `prepareDocsIndex`
146
159
  - `collectFilterValues`
160
+ - `collectDimensionValues`
161
+ - `matchesFilterDimensions`
147
162
  - `buildContentListHref`
148
163
 
164
+ ## Tipping (TipWidget)
165
+
166
+ `TipWidget` delegates payment to `@cedros/trade-react` and supports two modes:
167
+
168
+ **Self-configuring** — fetches config from your cedros-data server:
169
+ ```tsx
170
+ <TipWidget dataServerUrl="https://your-data-server.com" />
171
+ ```
172
+
173
+ **Manual** — provide recipient directly (skips config fetch):
174
+ ```tsx
175
+ <TipWidget recipient="SomeWalletAddress" currencies={[SOL_CURRENCY]} />
176
+ ```
177
+
178
+ **With per-post recipient override:**
179
+ ```tsx
180
+ <TipWidget recipient={post.tipRecipient} />
181
+ ```
182
+
183
+ The widget auto-detects browser wallets (Phantom, Solflare) for signing. To use a custom signer:
184
+ ```tsx
185
+ <TipWidget
186
+ recipient="..."
187
+ senderAddress={walletAddress}
188
+ signTransaction={async (base64Tx) => signedBase64Tx}
189
+ />
190
+ ```
191
+
192
+ ### Optional peer dependency
193
+
194
+ Tipping requires `@cedros/trade-react` at runtime:
195
+ ```bash
196
+ npm install @cedros/trade-react
197
+ ```
198
+
199
+ Browser wallet auto-signing also requires `@solana/web3.js` for transaction deserialization. If not installed, provide a `signTransaction` prop instead.
200
+
201
+ ### BlogPostTemplate tipping integration
202
+
203
+ ```tsx
204
+ <BlogPostTemplate
205
+ tipping={{
206
+ enabled: true,
207
+ dataServerUrl: "https://your-data-server.com",
208
+ // OR provide recipient directly:
209
+ // recipient: "WalletAddress",
210
+ // currencies: [SOL_CURRENCY, USDC_CURRENCY],
211
+ // presets: { SOL: [0.01, 0.05, 0.1], USDC: [1, 5, 10] },
212
+ }}
213
+ {...otherProps}
214
+ />
215
+ ```
216
+
217
+ ## Paywall (ContentPaywall)
218
+
219
+ `BlogPostTemplate` supports metered/locked/preview paywalls:
220
+
221
+ ```tsx
222
+ <BlogPostTemplate
223
+ paywall={{
224
+ mode: "preview", // "free" | "preview" | "locked"
225
+ previewParagraphs: 3,
226
+ price: { amount: 5, currency: "USDC", label: "$5" },
227
+ unlocked: false,
228
+ payClient: myPaywallClient,
229
+ remainingFreeReads: 2, // optional metering
230
+ }}
231
+ {...otherProps}
232
+ />
233
+ ```
234
+
235
+ Modes:
236
+ - `free` — full content, no paywall
237
+ - `preview` — shows first N paragraphs + purchase prompt
238
+ - `locked` — no content shown, purchase prompt only
239
+
240
+ When `remainingFreeReads > 0`, metered users see full content with a remaining-reads banner.
241
+
149
242
  ## Markdown and HTML behavior
150
243
 
151
244
  Docs/blog templates default to `bodyMarkdown`.
@@ -1,13 +1,13 @@
1
- export type { AdminPlugin, AdminSectionConfig, AdminGroupConfig, AdminSectionProps, HostContext, PluginContext, PluginRegistry, PluginId, SectionId, QualifiedSectionId, PluginPermission } from "./types.js";
1
+ export type { AdminPlugin, AdminSectionConfig, AdminGroupConfig, AdminSectionProps, HostContext, PluginContext, PluginRegistry, PluginId, SectionId, QualifiedSectionId, PluginPermission, ResolvedSection } from "./types.js";
2
2
  export { cedrosDataPlugin, dataPlugin } from "./plugin.js";
3
3
  export { default as defaultDataPlugin } from "./plugin.js";
4
4
  export { CEDROS_DATA_SECTIONS, CEDROS_DATA_SECTION_IDS, CEDROS_DATA_GROUPS } from "./sectionIds.js";
5
5
  export { AdminButton, Card, TextInput, SelectInput, JsonEditor, StatusNotice } from "./components.js";
6
6
  export { DataTable, type DataTableProps, type DataTableColumn } from "./primitives/DataTable.js";
7
- export { Toolbar, SearchInput, FilterChips, type FilterChipItem } from "./primitives/Toolbar.js";
8
- export { Pagination } from "./primitives/Pagination.js";
9
- export { JsonCodeEditor } from "./primitives/JsonCodeEditor.js";
7
+ export { Toolbar, SearchInput, FilterChips, type ToolbarProps, type SearchInputProps, type FilterChipsProps, type FilterChipItem } from "./primitives/Toolbar.js";
8
+ export { Pagination, type AdminPaginationProps } from "./primitives/Pagination.js";
9
+ export { JsonCodeEditor, type JsonCodeEditorProps } from "./primitives/JsonCodeEditor.js";
10
10
  export { DiffViewer, type DiffBlock } from "./primitives/DiffViewer.js";
11
11
  export { FormFieldRow, FieldTypeSelect, type FormFieldRowValue, type FieldTypeOption } from "./primitives/FormFieldRow.js";
12
- export { ConfirmDialog } from "./primitives/ConfirmDialog.js";
12
+ export { ConfirmDialog, type ConfirmDialogProps } from "./primitives/ConfirmDialog.js";
13
13
  export { ToastProvider, useToast, InlineAlert, type AlertTone } from "./primitives/alerts.js";
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- interface ConfirmDialogProps {
2
+ export interface ConfirmDialogProps {
3
3
  open: boolean;
4
4
  title: string;
5
5
  description?: string;
@@ -11,4 +11,3 @@ interface ConfirmDialogProps {
11
11
  children?: ReactNode;
12
12
  }
13
13
  export declare function ConfirmDialog({ open, title, description, confirmLabel, cancelLabel, tone, onConfirm, onCancel, children }: ConfirmDialogProps): React.JSX.Element | null;
14
- export {};
@@ -1,4 +1,4 @@
1
- interface JsonCodeEditorProps {
1
+ export interface JsonCodeEditorProps {
2
2
  value: string;
3
3
  onChange: (value: string) => void;
4
4
  label?: string;
@@ -7,4 +7,3 @@ interface JsonCodeEditorProps {
7
7
  onValidJson?: (value: unknown) => void;
8
8
  }
9
9
  export declare function JsonCodeEditor({ value, onChange, label, placeholder, readOnly, onValidJson }: JsonCodeEditorProps): React.JSX.Element;
10
- export {};
@@ -1,8 +1,7 @@
1
- interface PaginationProps {
1
+ export interface AdminPaginationProps {
2
2
  page: number;
3
3
  pageSize: number;
4
4
  total: number;
5
5
  onPageChange: (page: number) => void;
6
6
  }
7
- export declare function Pagination({ page, pageSize, total, onPageChange }: PaginationProps): React.JSX.Element;
8
- export {};
7
+ export declare function Pagination({ page, pageSize, total, onPageChange }: AdminPaginationProps): React.JSX.Element;
@@ -1,10 +1,10 @@
1
1
  import type { ReactNode } from "react";
2
- interface ToolbarProps {
2
+ export interface ToolbarProps {
3
3
  left?: ReactNode;
4
4
  right?: ReactNode;
5
5
  }
6
6
  export declare function Toolbar({ left, right }: ToolbarProps): React.JSX.Element;
7
- interface SearchInputProps {
7
+ export interface SearchInputProps {
8
8
  value: string;
9
9
  onChange: (value: string) => void;
10
10
  placeholder?: string;
@@ -15,9 +15,8 @@ export interface FilterChipItem {
15
15
  label: string;
16
16
  active: boolean;
17
17
  }
18
- interface FilterChipsProps {
18
+ export interface FilterChipsProps {
19
19
  items: FilterChipItem[];
20
20
  onToggle: (key: string) => void;
21
21
  }
22
22
  export declare function FilterChips({ items, onToggle }: FilterChipsProps): React.JSX.Element;
23
- export {};
@@ -101,37 +101,12 @@ export default function TippingSection({ pluginContext }) {
101
101
  }
102
102
  await save(normalizePayload(parsed));
103
103
  }, [rawEditor, save]);
104
- const handleEnsureAta = useCallback(async () => {
105
- const splCurrencies = config.currencies.filter((c) => c.mint && c.enabled);
106
- if (!config.recipient || splCurrencies.length === 0)
107
- return;
108
- setLoading(true);
109
- setStatus("");
110
- setTone("neutral");
111
- try {
112
- const { ensureRecipientAta } = await import("../../site-templates/solanaAtaSetup.js");
113
- const results = [];
114
- for (const cur of splCurrencies) {
115
- const ata = await ensureRecipientAta(config.recipient, cur.mint);
116
- results.push(`${cur.symbol}: ${ata}`);
117
- }
118
- setStatus(`ATAs ready: ${results.join(", ")}`);
119
- setTone("success");
120
- }
121
- catch (error) {
122
- setStatus(`ATA creation failed: ${error.message}`);
123
- setTone("error");
124
- }
125
- finally {
126
- setLoading(false);
127
- }
128
- }, [config.recipient, config.currencies]);
129
104
  return (_jsxs("div", { className: "cedros-data", children: [_jsxs("header", { className: "cedros-data__header", children: [_jsxs("div", { children: [_jsx("h2", { className: "cedros-data__title", children: "Tipping" }), _jsx("p", { className: "cedros-data__subtitle", children: "Configure tipping widget for your site." }), !canWrite && (_jsx("p", { className: "cedros-data__subtitle", children: "Read-only mode. Missing `data:settings:write` permission." }))] }), _jsxs("div", { className: "cedros-data-actions", children: [_jsx(AdminButton, { variant: "secondary", onClick: () => void load(), disabled: loading, children: "Refresh" }), _jsx(AdminButton, { variant: "primary", onClick: () => void saveForm(), disabled: loading || !canWrite, title: !canWrite ? "Requires data:settings:write" : undefined, children: "Save Form" }), _jsx(AdminButton, { variant: "secondary", onClick: () => void applyRaw(), disabled: loading || !canWrite, title: !canWrite ? "Requires data:settings:write" : undefined, children: "Save JSON" })] })] }), _jsxs("div", { className: "cedros-data-grid cedros-data-grid--two", children: [_jsxs(Card, { title: "Tipping Settings", subtitle: "Basic tipping configuration.", children: [_jsxs("label", { className: "cedros-data-checkbox-row", children: [_jsx("input", { type: "checkbox", checked: config.enabled, onChange: (e) => setConfig((prev) => ({ ...prev, enabled: e.target.checked })), disabled: !canWrite }), "Enabled"] }), _jsx(TextInput, { label: "Recipient", value: config.recipient, onChange: (e) => setConfig((prev) => ({ ...prev, recipient: e.target.value })), placeholder: "Wallet address or identifier", disabled: !canWrite }), _jsx(TextInput, { label: "Label", value: config.label, onChange: (e) => setConfig((prev) => ({ ...prev, label: e.target.value })), placeholder: "Leave a tip", disabled: !canWrite }), _jsx(TextInput, { label: "Description", value: config.description, onChange: (e) => setConfig((prev) => ({ ...prev, description: e.target.value })), placeholder: "Optional description", disabled: !canWrite }), _jsxs("label", { className: "cedros-data-checkbox-row", children: [_jsx("input", { type: "checkbox", checked: config.allowPerPostRecipient, onChange: (e) => setConfig((prev) => ({ ...prev, allowPerPostRecipient: e.target.checked })), disabled: !canWrite }), "Allow per-post recipient"] }), _jsx("p", { className: "cedros-data__subtitle", style: { margin: 0 }, children: "When enabled, individual blog posts can override the site-wide tip recipient." }), _jsx("h4", { style: { margin: "1rem 0 0.5rem" }, children: "Currencies" }), config.currencies.map((cur) => (_jsxs("label", { className: "cedros-data-checkbox-row", children: [_jsx("input", { type: "checkbox", checked: cur.enabled, disabled: !canWrite || cur.symbol === "SOL", onChange: (e) => {
130
105
  setConfig((prev) => ({
131
106
  ...prev,
132
107
  currencies: prev.currencies.map((c) => c.symbol === cur.symbol ? { ...c, enabled: e.target.checked } : c),
133
108
  }));
134
- } }), cur.symbol, cur.symbol === "SOL" ? " (always enabled)" : ""] }, cur.symbol))), config.currencies.some((c) => c.mint && c.enabled) && (_jsx(AdminButton, { variant: "secondary", disabled: !config.recipient || loading, onClick: () => void handleEnsureAta(), title: !config.recipient ? "Set recipient address first" : undefined, children: "Ensure Recipient ATA" }))] }), _jsx(Card, { title: "Tipping JSON", subtitle: "Advanced editor for currencies, presets, and more.", children: _jsx(JsonEditor, { label: "site_settings/tipping", value: rawEditor, onChange: (e) => setRawEditor(e.target.value), disabled: !canWrite }) })] }), status && _jsx(StatusNotice, { tone: tone, message: status })] }));
109
+ } }), cur.symbol, cur.symbol === "SOL" ? " (always enabled)" : ""] }, cur.symbol)))] }), _jsx(Card, { title: "Tipping JSON", subtitle: "Advanced editor for currencies, presets, and more.", children: _jsx(JsonEditor, { label: "site_settings/tipping", value: rawEditor, onChange: (e) => setRawEditor(e.target.value), disabled: !canWrite }) })] }), status && _jsx(StatusNotice, { tone: tone, message: status })] }));
135
110
  }
136
111
  function normalizePayload(value) {
137
112
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -7,4 +7,4 @@
7
7
  export { CmsContent, type CmsContentProps } from "./CmsContent.js";
8
8
  export { getOrCreateVisitorId } from "./visitor.js";
9
9
  export { sanitizeCmsHtml, renderCmsMarkdown } from "./sanitize.js";
10
- export type { CmsPageRecord, SiteDataRecord, SanitizeOptions, ContentType, } from "./types.js";
10
+ export type { CmsPageRecord, PageMetadataInput, SiteDataRecord, SanitizeOptions, ContentType, MeteredReadsInfo, } from "./types.js";
@@ -1,4 +1,4 @@
1
- import type { BuildPageMetadataOptions, CmsPageRecord, ServerFetchOptions } from "./types.js";
1
+ import type { BuildPageMetadataOptions, CmsPageRecord, PageMetadataInput, ServerFetchOptions } from "./types.js";
2
2
  /**
3
3
  * Metadata object compatible with Next.js `generateMetadata` return type.
4
4
  * Kept as a plain object so this module doesn't import `next` as a dependency.
@@ -26,13 +26,15 @@ export interface PageMetadata {
26
26
  other?: Record<string, string>;
27
27
  }
28
28
  /**
29
- * Builds a Next.js-compatible Metadata object from a CMS page record.
29
+ * Builds a Next.js-compatible Metadata object from a page record.
30
30
  *
31
- * @param page - CMS page record from the entries API
31
+ * Accepts both wrapped CmsPageRecord shapes and flat page records.
32
+ *
33
+ * @param page - Page record (wrapped or flat)
32
34
  * @param options - Optional site data, origin, path, and mode
33
35
  * @returns Metadata object suitable for Next.js `generateMetadata`
34
36
  */
35
- export declare function buildPageMetadata(page: CmsPageRecord, options?: BuildPageMetadataOptions): PageMetadata;
37
+ export declare function buildPageMetadata(page: CmsPageRecord | PageMetadataInput, options?: BuildPageMetadataOptions): PageMetadata;
36
38
  /**
37
39
  * Convenience wrapper that fetches a page by slug, then builds metadata.
38
40
  *
@@ -1,28 +1,35 @@
1
1
  import { fetchJson, resolveServerUrl } from "./fetch.js";
2
+ /** Resolves a field from the wrapped payload or flat top-level. */
3
+ function field(page, key) {
4
+ const rec = page;
5
+ const payload = rec.payload;
6
+ return payload?.[key] ?? rec[key];
7
+ }
2
8
  /**
3
- * Builds a Next.js-compatible Metadata object from a CMS page record.
9
+ * Builds a Next.js-compatible Metadata object from a page record.
10
+ *
11
+ * Accepts both wrapped CmsPageRecord shapes and flat page records.
4
12
  *
5
- * @param page - CMS page record from the entries API
13
+ * @param page - Page record (wrapped or flat)
6
14
  * @param options - Optional site data, origin, path, and mode
7
15
  * @returns Metadata object suitable for Next.js `generateMetadata`
8
16
  */
9
17
  export function buildPageMetadata(page, options) {
10
- const p = page.payload;
11
18
  const site = options?.siteData;
12
19
  const origin = options?.origin?.replace(/\/+$/, "") ?? "";
13
- const path = options?.path ?? p.route ?? "";
20
+ const path = options?.path ?? field(page, "route") ?? "";
14
21
  const mode = options?.mode ?? "page";
15
22
  const siteName = site?.siteName ?? site?.siteTitle ?? "";
16
- const title = p.title ?? siteName ?? undefined;
17
- const description = p.description ?? site?.defaultDescription ?? undefined;
18
- const ogTitle = p.ogTitle ?? p.title ?? undefined;
19
- const ogDescription = p.ogDescription ?? p.description ?? undefined;
20
- const ogImages = buildOgImages(p.ogImage, site?.defaultOgImage);
23
+ const title = field(page, "title") ?? siteName ?? undefined;
24
+ const description = field(page, "description") ?? site?.defaultDescription ?? undefined;
25
+ const ogTitle = field(page, "ogTitle") ?? field(page, "title") ?? undefined;
26
+ const ogDescription = field(page, "ogDescription") ?? field(page, "description") ?? undefined;
27
+ const ogImages = buildOgImages(field(page, "ogImage"), site?.defaultOgImage);
21
28
  const pageUrl = origin ? `${origin}${path}` : undefined;
22
29
  const ogType = mode === "blog" ? "article" : "website";
23
- const twitterTitle = p.twitterTitle ?? p.ogTitle ?? p.title ?? undefined;
24
- const twitterDescription = p.twitterDescription ?? p.ogDescription ?? p.description ?? undefined;
25
- const canonical = resolveCanonical(origin, path, mode, p.slug);
30
+ const twitterTitle = field(page, "twitterTitle") ?? field(page, "ogTitle") ?? field(page, "title") ?? undefined;
31
+ const twitterDescription = field(page, "twitterDescription") ?? field(page, "ogDescription") ?? field(page, "description") ?? undefined;
32
+ const canonical = resolveCanonical(origin, path, mode, field(page, "slug"));
26
33
  const metadata = {
27
34
  title,
28
35
  description,
@@ -112,31 +119,34 @@ function resolveCanonical(origin, path, mode, slug) {
112
119
  if (!origin)
113
120
  return undefined;
114
121
  if (mode === "docs" && slug) {
115
- return `${origin}/blog/${slug}`;
122
+ return `${origin}/docs/${slug}`;
116
123
  }
117
124
  return `${origin}${path}`;
118
125
  }
119
126
  function buildBlogJsonLd(page, origin, path) {
120
- const p = page.payload;
127
+ const updatedAt = field(page, "updatedAt");
121
128
  const jsonLd = {
122
129
  "@context": "https://schema.org",
123
130
  "@type": "BlogPosting",
124
- headline: p.title,
125
- description: p.description,
126
- datePublished: p.publishedAt,
127
- dateModified: p.updatedAt ?? page.updated_at,
131
+ headline: field(page, "title"),
132
+ description: field(page, "description"),
133
+ datePublished: field(page, "publishedAt"),
134
+ dateModified: updatedAt ?? page.updated_at,
128
135
  };
129
136
  if (origin) {
130
137
  jsonLd.url = `${origin}${path}`;
131
138
  }
132
- if (p.author) {
133
- jsonLd.author = { "@type": "Person", name: p.author };
139
+ const author = field(page, "author");
140
+ if (author) {
141
+ jsonLd.author = { "@type": "Person", name: author };
134
142
  }
135
- if (p.ogImage) {
136
- jsonLd.image = p.ogImage;
143
+ const ogImage = field(page, "ogImage");
144
+ if (ogImage) {
145
+ jsonLd.image = ogImage;
137
146
  }
138
- if (p.wordCount) {
139
- jsonLd.wordCount = p.wordCount;
147
+ const wordCount = field(page, "wordCount");
148
+ if (wordCount) {
149
+ jsonLd.wordCount = wordCount;
140
150
  }
141
151
  return JSON.stringify(jsonLd);
142
152
  }
@@ -11,4 +11,4 @@ export { buildPageMetadata, generatePageMetadata, type PageMetadata, } from "./m
11
11
  export { loadSitemapEntries } from "./sitemap.js";
12
12
  export { fetchBlogPost } from "./entries.js";
13
13
  export { listBlogSlugs, listContentSlugs, listLearnPathIds, } from "./slugs.js";
14
- export type { BuildPageMetadataOptions, CmsPageRecord, MeteredReadsInfo, SiteDataRecord, SitemapEntry, ServerFetchOptions, ContentType, } from "./types.js";
14
+ export type { BuildPageMetadataOptions, CmsPageRecord, MeteredReadsInfo, PageMetadataInput, SiteDataRecord, SitemapEntry, ServerFetchOptions, ContentType, } from "./types.js";
@@ -63,6 +63,18 @@ export interface SanitizeOptions {
63
63
  /** Allowed URL protocols for href/src attributes. */
64
64
  allowedProtocols?: string[];
65
65
  }
66
+ /**
67
+ * Flexible input for `buildPageMetadata`.
68
+ *
69
+ * Accepts both the wrapped CmsPageRecord shape (`{ entry_key, payload, updated_at }`)
70
+ * and flat page records where fields live at the top level (`{ title, ogImage, ... }`).
71
+ */
72
+ export interface PageMetadataInput {
73
+ entry_key?: string;
74
+ payload?: Record<string, unknown>;
75
+ updated_at?: string;
76
+ [key: string]: unknown;
77
+ }
66
78
  /** Options for building page metadata. */
67
79
  export interface BuildPageMetadataOptions {
68
80
  siteData?: SiteDataRecord;
@@ -1,6 +1,6 @@
1
1
  import type { SiteNavigationItem } from "./SiteLayout.js";
2
2
  import { type BlogIndexEntry, type FilterDimension, type FilterDimensionValues } from "./contentIndex.js";
3
- import { type TipCurrency, type TipPayClient } from "./tipControls.js";
3
+ import { type TipCurrency } from "./tipControls.js";
4
4
  import { type PaywallPrice, type PaywallPayClient } from "./paywallControls.js";
5
5
  export interface BlogPostSummary extends BlogIndexEntry {
6
6
  author?: string;
@@ -39,14 +39,18 @@ export interface BlogTippingConfig {
39
39
  enabled: boolean;
40
40
  label?: string;
41
41
  description?: string;
42
- recipient: string;
43
- currencies: TipCurrency[];
42
+ recipient?: string;
43
+ currencies?: TipCurrency[];
44
44
  presets?: Record<string, number[]>;
45
- payClient: TipPayClient;
45
+ dataServerUrl?: string;
46
46
  /** When true, `recipientOverride` takes precedence over `recipient`. */
47
47
  allowPerPostRecipient?: boolean;
48
48
  /** Per-post tip recipient (used when `allowPerPostRecipient` is true). */
49
49
  recipientOverride?: string;
50
+ /** Sender wallet address (auto-detected if omitted). */
51
+ senderAddress?: string;
52
+ /** Signs a base64 serialized transaction, returns signed base64. */
53
+ signTransaction?: (serializedTx: string) => Promise<string>;
50
54
  }
51
55
  export interface BlogPaywallConfig {
52
56
  mode: "free" | "preview" | "locked";
@@ -30,9 +30,9 @@ export function BlogPostTemplate({ siteTitle, navigation, title, bodyMarkdown, b
30
30
  bodyMarkdown,
31
31
  bodyHtml,
32
32
  allowUnsafeHtmlFallback
33
- })] }), tipping?.enabled && (_jsx("section", { className: "cedros-site__card", children: _jsx(TipWidget, { recipient: tipping.allowPerPostRecipient && tipping.recipientOverride
33
+ })] }), tipping?.enabled && (_jsx("section", { className: "cedros-site__card", children: _jsx(TipWidget, { dataServerUrl: tipping.dataServerUrl, recipient: tipping.allowPerPostRecipient && tipping.recipientOverride
34
34
  ? tipping.recipientOverride
35
- : tipping.recipient, currencies: tipping.currencies, presets: tipping.presets, payClient: tipping.payClient, label: tipping.label, description: tipping.description }) })), relatedPosts.length > 0 && (_jsxs("section", { className: "cedros-site__card", children: [_jsx("h2", { style: { margin: 0, fontSize: "1.02rem" }, children: "Related posts" }), _jsx("div", { className: "cedros-site__content-grid", style: { marginTop: "0.75rem" }, children: relatedPosts.map((post) => (_jsxs("article", { className: "cedros-site__entry-card", children: [_jsx("h3", { className: "cedros-site__entry-title", style: { marginTop: 0 }, children: _jsx("a", { href: `${basePath}/${post.slug}`, children: post.title }) }), post.excerpt && _jsx("p", { className: "cedros-site__subtitle", children: post.excerpt })] }, post.slug))) })] }))] }));
35
+ : tipping.recipient, currencies: tipping.currencies, presets: tipping.presets, label: tipping.label, description: tipping.description, senderAddress: tipping.senderAddress, signTransaction: tipping.signTransaction }) })), relatedPosts.length > 0 && (_jsxs("section", { className: "cedros-site__card", children: [_jsx("h2", { style: { margin: 0, fontSize: "1.02rem" }, children: "Related posts" }), _jsx("div", { className: "cedros-site__content-grid", style: { marginTop: "0.75rem" }, children: relatedPosts.map((post) => (_jsxs("article", { className: "cedros-site__entry-card", children: [_jsx("h3", { className: "cedros-site__entry-title", style: { marginTop: 0 }, children: _jsx("a", { href: `${basePath}/${post.slug}`, children: post.title }) }), post.excerpt && _jsx("p", { className: "cedros-site__subtitle", children: post.excerpt })] }, post.slug))) })] }))] }));
36
36
  }
37
37
  function BlogIndexControls({ basePath, query, category, tag, sort, categories, tags }) {
38
38
  return (_jsxs("form", { method: "get", action: basePath, className: "cedros-site__controls cedros-site__card", children: [_jsxs("label", { className: "cedros-site__control", children: [_jsx("span", { children: "Search" }), _jsx("input", { type: "search", name: "q", defaultValue: query, placeholder: "Search blog posts" })] }), _jsxs("label", { className: "cedros-site__control", children: [_jsx("span", { children: "Category" }), _jsxs("select", { name: "category", defaultValue: category, children: [_jsx("option", { value: "", children: "All" }), categories.map((entry) => (_jsx("option", { value: entry, children: entry }, entry)))] })] }), _jsxs("label", { className: "cedros-site__control", children: [_jsx("span", { children: "Tag" }), _jsxs("select", { name: "tag", defaultValue: tag, children: [_jsx("option", { value: "", children: "All" }), tags.map((entry) => (_jsx("option", { value: entry, children: entry }, entry)))] })] }), _jsxs("label", { className: "cedros-site__control", children: [_jsx("span", { children: "Sort" }), _jsxs("select", { name: "sort", defaultValue: sort, children: [_jsx("option", { value: "newest", children: "Newest" }), _jsx("option", { value: "oldest", children: "Oldest" }), _jsx("option", { value: "title-asc", children: "Title A-Z" }), _jsx("option", { value: "title-desc", children: "Title Z-A" })] })] }), _jsxs("div", { className: "cedros-site__control-actions", children: [_jsx("button", { className: "cedros-site__nav-link", type: "submit", children: "Apply" }), _jsx("a", { className: "cedros-site__nav-link", href: basePath, children: "Clear" })] })] }));
@@ -195,6 +195,10 @@
195
195
  background: color-mix(in srgb, var(--cds-link) 9%, var(--cds-muted-bg) 91%);
196
196
  }
197
197
 
198
+ .cedros-site__docs-item--depth-0 {
199
+ padding-left: 0;
200
+ }
201
+
198
202
  .cedros-site__docs-item--depth-1 {
199
203
  padding-left: 0.9rem;
200
204
  }
@@ -312,6 +316,10 @@
312
316
  font-size: 0.8rem;
313
317
  }
314
318
 
319
+ .cedros-site__toc-link--depth-2 {
320
+ padding-left: 0;
321
+ }
322
+
315
323
  .cedros-site__toc-link--depth-3 {
316
324
  padding-left: 0.65rem;
317
325
  }
@@ -240,6 +240,12 @@
240
240
  }
241
241
  }
242
242
 
243
+ /* ─── Docs article modifier ──── */
244
+
245
+ .cedros-site__docs-article {
246
+ overflow: hidden;
247
+ }
248
+
243
249
  /* ─── TOC scroll-spy active link ──── */
244
250
 
245
251
  .cedros-site__toc-link--active {
@@ -15,14 +15,12 @@ export { Breadcrumbs, ContentPagination, type PaginationProps } from "./contentU
15
15
  export { HomePageTemplate, type HomePageTemplateProps, type HomeFeature } from "./HomePageTemplate.js";
16
16
  export { DocsIndexTemplate, DocArticleTemplate, type DocsIndexTemplateProps, type DocArticleTemplateProps, type DocsIndexItem } from "./DocsTemplates.js";
17
17
  export { BlogIndexTemplate, BlogPostTemplate, type BlogIndexTemplateProps, type BlogPostTemplateProps, type BlogPostSummary, type BlogTippingConfig, type BlogPaywallConfig } from "./BlogTemplates.js";
18
- export { TipWidget } from "./tipControls.js";
19
- export type { TipWidgetProps, TipCurrency, TipParams, TipPayClient } from "./tipControls.js";
18
+ export { TipWidget, SOL_CURRENCY, USDC_CURRENCY } from "./tipControls.js";
19
+ export type { TipWidgetProps, TipCurrency } from "./tipControls.js";
20
20
  export { ContentPaywall } from "./paywallControls.js";
21
21
  export type { ContentPaywallProps, PaywallPrice, PaywallPayClient } from "./paywallControls.js";
22
22
  export { BlogSearchInput, FilterDimensionChips, BookmarkButton } from "./blogControls.js";
23
23
  export type { BlogSearchInputProps, FilterDimensionChipsProps, BookmarkButtonProps } from "./blogControls.js";
24
- export { SolanaMicropayments, createSolanaTipClient, detectSolanaWallet, SOL_CURRENCY, USDC_CURRENCY, type SolanaMicropaymentsProps, } from "./solanaMicropayments.js";
25
- export { ensureRecipientAta } from "./solanaAtaSetup.js";
26
24
  export { LegalPageTemplate, type LegalPageTemplateProps } from "./LegalPageTemplate.js";
27
25
  export { ContactPageTemplate, type ContactPageTemplateProps, type ContactDetail } from "./ContactPageTemplate.js";
28
26
  export { NotFoundTemplate, type NotFoundTemplateProps } from "./NotFoundTemplate.js";
@@ -15,11 +15,9 @@ export { Breadcrumbs, ContentPagination } from "./contentUi.js";
15
15
  export { HomePageTemplate } from "./HomePageTemplate.js";
16
16
  export { DocsIndexTemplate, DocArticleTemplate } from "./DocsTemplates.js";
17
17
  export { BlogIndexTemplate, BlogPostTemplate } from "./BlogTemplates.js";
18
- export { TipWidget } from "./tipControls.js";
18
+ export { TipWidget, SOL_CURRENCY, USDC_CURRENCY } from "./tipControls.js";
19
19
  export { ContentPaywall } from "./paywallControls.js";
20
20
  export { BlogSearchInput, FilterDimensionChips, BookmarkButton } from "./blogControls.js";
21
- export { SolanaMicropayments, createSolanaTipClient, detectSolanaWallet, SOL_CURRENCY, USDC_CURRENCY, } from "./solanaMicropayments.js";
22
- export { ensureRecipientAta } from "./solanaAtaSetup.js";
23
21
  export { LegalPageTemplate } from "./LegalPageTemplate.js";
24
22
  export { ContactPageTemplate } from "./ContactPageTemplate.js";
25
23
  export { NotFoundTemplate } from "./NotFoundTemplate.js";
@@ -4,21 +4,38 @@ export interface TipCurrency {
4
4
  logo: string;
5
5
  mint?: string;
6
6
  }
7
- export interface TipParams {
8
- currency: TipCurrency;
9
- amount: number;
10
- recipient: string;
11
- }
12
- /** Provided by @cedros/pay-react (or any compatible implementation). */
13
- export interface TipPayClient {
14
- sendTip(params: TipParams): Promise<void>;
15
- }
16
7
  export interface TipWidgetProps {
17
- recipient: string;
18
- currencies: TipCurrency[];
8
+ /** cedros-data server URL for config fetch; defaults to "" (relative). */
9
+ dataServerUrl?: string;
10
+ /** Tip recipient address. When provided, config fetch is skipped (manual mode). */
11
+ recipient?: string;
12
+ currencies?: TipCurrency[];
19
13
  presets?: Record<string, number[]>;
20
- payClient: TipPayClient;
21
14
  label?: string;
22
15
  description?: string;
16
+ /** Sender wallet address. Auto-detected from window.solana if omitted. */
17
+ senderAddress?: string;
18
+ /** Signs a base64 serialized transaction, returns signed base64. Uses browser wallet if omitted. */
19
+ signTransaction?: (serializedTx: string) => Promise<string>;
20
+ }
21
+ export declare const SOL_CURRENCY: TipCurrency;
22
+ export declare const USDC_CURRENCY: TipCurrency;
23
+ interface SolanaWallet {
24
+ isConnected: boolean;
25
+ publicKey: {
26
+ toString(): string;
27
+ } | null;
28
+ connect(): Promise<{
29
+ publicKey: {
30
+ toString(): string;
31
+ };
32
+ }>;
33
+ signTransaction(tx: unknown): Promise<unknown>;
34
+ }
35
+ declare global {
36
+ interface Window {
37
+ solana?: SolanaWallet;
38
+ }
23
39
  }
24
- export declare function TipWidget({ recipient, currencies, presets, payClient, label, description }: TipWidgetProps): React.JSX.Element;
40
+ export declare function TipWidget({ dataServerUrl, recipient: recipientProp, currencies: currenciesProp, presets: presetsProp, label: labelProp, description: descriptionProp, senderAddress: senderProp, signTransaction: signProp, }: TipWidgetProps): React.JSX.Element;
41
+ export {};
@@ -1,7 +1,107 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useState } from "react";
4
- export function TipWidget({ recipient, currencies, presets, payClient, label = "Leave a tip", description }) {
3
+ import { useCallback, useEffect, useState } from "react";
4
+ // ---------------------------------------------------------------------------
5
+ // Currency constants
6
+ // ---------------------------------------------------------------------------
7
+ export const SOL_CURRENCY = {
8
+ symbol: "SOL",
9
+ decimals: 9,
10
+ logo: "",
11
+ mint: "So11111111111111111111111111111111111111112",
12
+ };
13
+ export const USDC_CURRENCY = {
14
+ symbol: "USDC",
15
+ decimals: 6,
16
+ logo: "",
17
+ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
18
+ };
19
+ function getWalletAddress() {
20
+ if (typeof window === "undefined")
21
+ return null;
22
+ return window.solana?.publicKey?.toString() ?? null;
23
+ }
24
+ async function connectAndGetAddress() {
25
+ const w = typeof window !== "undefined" ? window.solana : undefined;
26
+ if (!w)
27
+ throw new Error("No Solana wallet detected. Please install Phantom or another wallet.");
28
+ if (!w.isConnected) {
29
+ const { publicKey } = await w.connect();
30
+ return publicKey.toString();
31
+ }
32
+ if (!w.publicKey)
33
+ throw new Error("Wallet connected but no public key available.");
34
+ return w.publicKey.toString();
35
+ }
36
+ /** Signs base64 tx via browser wallet. Requires @solana/web3.js at runtime. */
37
+ async function signWithBrowserWallet(serializedTx) {
38
+ const wallet = window.solana;
39
+ if (!wallet)
40
+ throw new Error("No Solana wallet detected");
41
+ if (!wallet.isConnected)
42
+ await wallet.connect();
43
+ let Transaction;
44
+ try {
45
+ ({ Transaction } = await import("@solana/web3.js"));
46
+ }
47
+ catch {
48
+ throw new Error("Browser wallet signing requires @solana/web3.js. " +
49
+ "Install it or provide a signTransaction prop.");
50
+ }
51
+ const txBytes = Uint8Array.from(atob(serializedTx), (c) => c.charCodeAt(0));
52
+ const tx = Transaction.from(txBytes);
53
+ const signed = (await wallet.signTransaction(tx));
54
+ const signedBytes = signed.serialize();
55
+ return btoa(String.fromCharCode(...signedBytes));
56
+ }
57
+ function useTipConfig(dataServerUrl, skip) {
58
+ const [config, setConfig] = useState(null);
59
+ const [loading, setLoading] = useState(!skip);
60
+ const [error, setError] = useState(null);
61
+ useEffect(() => {
62
+ if (skip)
63
+ return;
64
+ let cancelled = false;
65
+ (async () => {
66
+ try {
67
+ const res = await fetch(`${dataServerUrl}/site/config/tipping`);
68
+ if (!res.ok)
69
+ throw new Error(`Tipping config: ${res.status}`);
70
+ const data = (await res.json());
71
+ if (!cancelled) {
72
+ setConfig(data);
73
+ setLoading(false);
74
+ }
75
+ }
76
+ catch (err) {
77
+ if (!cancelled) {
78
+ setError(err instanceof Error ? err.message : String(err));
79
+ setLoading(false);
80
+ }
81
+ }
82
+ })();
83
+ return () => { cancelled = true; };
84
+ }, [dataServerUrl, skip]);
85
+ return { config, loading, error };
86
+ }
87
+ export function TipWidget({ dataServerUrl = "", recipient: recipientProp, currencies: currenciesProp, presets: presetsProp, label: labelProp, description: descriptionProp, senderAddress: senderProp, signTransaction: signProp, }) {
88
+ const manualMode = !!recipientProp;
89
+ const { config, loading: configLoading } = useTipConfig(dataServerUrl, manualMode);
90
+ const recipient = recipientProp ?? config?.recipient ?? "";
91
+ const currencies = currenciesProp ?? config?.currencies ?? [SOL_CURRENCY];
92
+ const presets = presetsProp ?? config?.presets;
93
+ const label = labelProp ?? config?.label ?? "Leave a tip";
94
+ const description = descriptionProp ?? config?.description;
95
+ const [walletAddress, setWalletAddress] = useState(senderProp ?? null);
96
+ useEffect(() => {
97
+ if (senderProp) {
98
+ setWalletAddress(senderProp);
99
+ return;
100
+ }
101
+ const detected = getWalletAddress();
102
+ if (detected)
103
+ setWalletAddress(detected);
104
+ }, [senderProp]);
5
105
  const [selectedIndex, setSelectedIndex] = useState(0);
6
106
  const [amount, setAmount] = useState("");
7
107
  const [status, setStatus] = useState("idle");
@@ -9,7 +109,7 @@ export function TipWidget({ recipient, currencies, presets, payClient, label = "
9
109
  const selectedCurrency = currencies[selectedIndex] ?? currencies[0];
10
110
  const currentPresets = selectedCurrency ? presets?.[selectedCurrency.symbol] : undefined;
11
111
  const handleSubmit = useCallback(async () => {
12
- if (!selectedCurrency)
112
+ if (!selectedCurrency || !recipient)
13
113
  return;
14
114
  const numericAmount = parseFloat(amount);
15
115
  if (!numericAmount || numericAmount <= 0)
@@ -17,27 +117,41 @@ export function TipWidget({ recipient, currencies, presets, payClient, label = "
17
117
  setStatus("loading");
18
118
  setErrorMessage("");
19
119
  try {
20
- await payClient.sendTip({ currency: selectedCurrency, amount: numericAmount, recipient });
120
+ let sender = walletAddress;
121
+ if (!sender) {
122
+ sender = await connectAndGetAddress();
123
+ setWalletAddress(sender);
124
+ }
125
+ const mint = selectedCurrency.mint ?? SOL_CURRENCY.mint;
126
+ const rawAmount = String(Math.round(numericAmount * 10 ** selectedCurrency.decimals));
127
+ let TradeApiClient;
128
+ try {
129
+ ({ TradeApiClient } = await import("@cedros/trade-react"));
130
+ }
131
+ catch {
132
+ throw new Error("TipWidget requires @cedros/trade-react. Install it as a dependency.");
133
+ }
134
+ const client = new TradeApiClient(dataServerUrl);
135
+ const buildResult = await client.buildTransfer({ sender, recipient, mint, amount: rawAmount });
136
+ const sign = signProp ?? signWithBrowserWallet;
137
+ const signedTx = await sign(buildResult.transaction);
138
+ const result = await client.executeTransfer(signedTx);
139
+ if (result.status === "failed")
140
+ throw new Error("Transaction failed on-chain");
21
141
  setStatus("success");
22
142
  setAmount("");
23
143
  }
24
144
  catch (err) {
25
- const error = err instanceof Error ? err : new Error(String(err));
26
145
  setStatus("error");
27
- setErrorMessage(error.message);
146
+ setErrorMessage(err instanceof Error ? err.message : String(err));
28
147
  }
29
- }, [selectedCurrency, amount, payClient, recipient]);
30
- if (!selectedCurrency)
148
+ }, [selectedCurrency, amount, recipient, walletAddress, signProp, dataServerUrl]);
149
+ // Early returns: loading, disabled, or no recipient
150
+ if (!manualMode && configLoading)
151
+ return _jsx("div", { className: "cedros-site__tip-widget" });
152
+ if (!manualMode && config && !config.enabled)
153
+ return _jsx("div", { className: "cedros-site__tip-widget" });
154
+ if (!recipient || !selectedCurrency)
31
155
  return _jsx("div", { className: "cedros-site__tip-widget" });
32
- return (_jsxs("div", { className: "cedros-site__tip-widget", children: [_jsx("h3", { className: "cedros-site__tip-title", children: label }), description && _jsx("p", { className: "cedros-site__tip-description", children: description }), currencies.length > 1 && (_jsx("select", { className: "cedros-site__tip-currency", value: selectedIndex, onChange: (e) => {
33
- setSelectedIndex(Number(e.target.value));
34
- setAmount("");
35
- setStatus("idle");
36
- }, children: currencies.map((c, i) => (_jsx("option", { value: i, children: c.symbol }, c.symbol))) })), currentPresets && currentPresets.length > 0 && (_jsx("div", { className: "cedros-site__tip-presets", children: currentPresets.map((presetAmount) => (_jsxs("button", { type: "button", className: "cedros-site__pill", onClick: () => {
37
- setAmount(String(presetAmount));
38
- setStatus("idle");
39
- }, children: [presetAmount, " ", selectedCurrency.symbol] }, presetAmount))) })), _jsx("div", { className: "cedros-site__tip-amount", children: _jsx("input", { type: "number", min: "0", step: "any", placeholder: `Amount in ${selectedCurrency.symbol}`, value: amount, onChange: (e) => {
40
- setAmount(e.target.value);
41
- setStatus("idle");
42
- } }) }), _jsx("button", { type: "button", className: "cedros-site__tip-submit", disabled: status === "loading" || !amount || parseFloat(amount) <= 0, onClick: () => void handleSubmit(), children: status === "loading" ? "Sending..." : "Send Tip" }), status === "success" && (_jsx("p", { className: "cedros-site__tip-status cedros-site__tip-status--success", children: "Tip sent successfully!" })), status === "error" && (_jsx("p", { className: "cedros-site__tip-status cedros-site__tip-status--error", children: errorMessage || "Failed to send tip." }))] }));
156
+ return (_jsxs("div", { className: "cedros-site__tip-widget", children: [_jsx("h3", { className: "cedros-site__tip-title", children: label }), description && _jsx("p", { className: "cedros-site__tip-description", children: description }), currencies.length > 1 && (_jsx("select", { className: "cedros-site__tip-currency", value: selectedIndex, onChange: (e) => { setSelectedIndex(Number(e.target.value)); setAmount(""); setStatus("idle"); }, children: currencies.map((c, i) => (_jsx("option", { value: i, children: c.symbol }, c.symbol))) })), currentPresets && currentPresets.length > 0 && (_jsx("div", { className: "cedros-site__tip-presets", children: currentPresets.map((presetAmount) => (_jsxs("button", { type: "button", className: "cedros-site__pill", onClick: () => { setAmount(String(presetAmount)); setStatus("idle"); }, children: [presetAmount, " ", selectedCurrency.symbol] }, presetAmount))) })), _jsx("div", { className: "cedros-site__tip-amount", children: _jsx("input", { type: "number", min: "0", step: "any", placeholder: `Amount in ${selectedCurrency.symbol}`, value: amount, onChange: (e) => { setAmount(e.target.value); setStatus("idle"); } }) }), _jsx("button", { type: "button", className: "cedros-site__tip-submit", disabled: status === "loading" || !amount || parseFloat(amount) <= 0, onClick: () => void handleSubmit(), children: status === "loading" ? "Sending..." : "Send Tip" }), status === "success" && (_jsx("p", { className: "cedros-site__tip-status cedros-site__tip-status--success", children: "Tip sent successfully!" })), status === "error" && (_jsx("p", { className: "cedros-site__tip-status cedros-site__tip-status--error", children: errorMessage || "Failed to send tip." }))] }));
43
157
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedros/data-react",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "React components, page templates, and Next.js integration for cedros-data",
5
5
  "type": "module",
6
6
  "main": "./dist/react/index.js",
@@ -40,11 +40,15 @@
40
40
  },
41
41
  "peerDependencies": {
42
42
  "react": "^18.0.0 || ^19.0.0",
43
- "react-dom": "^18.0.0 || ^19.0.0"
43
+ "react-dom": "^18.0.0 || ^19.0.0",
44
+ "@cedros/trade-react": "^0.1.0"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "@cedros/trade-react": {
48
+ "optional": true
49
+ }
44
50
  },
45
51
  "dependencies": {
46
- "@solana/spl-token": "^0.4.0",
47
- "@solana/web3.js": "^1.98.4",
48
52
  "highlight.js": "^11.11.1",
49
53
  "react-markdown": "^10.1.0",
50
54
  "rehype-autolink-headings": "^7.1.0",