@the-smithy/hearth 0.19.0 → 0.21.0

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.
@@ -196,6 +196,28 @@ function createCore(options) {
196
196
  }
197
197
  throw err;
198
198
  }
199
+ },
200
+ /**
201
+ * Sign up to volunteer at an event that asks for volunteers (its `volunteering` is set). Post `shifts` when
202
+ * `volunteering.pickShifts` isn't "none", and each answer under its field's `name`. Validation problems
203
+ * (including a full shift or closed sign-up) resolve to { ok: false, fieldErrors }; network and auth failures throw.
204
+ */
205
+ volunteer: async (slug, input, opts = {}) => {
206
+ try {
207
+ const data = await request(`/events/${encodeURIComponent(slug)}/volunteers`, {
208
+ method: "POST",
209
+ body: input,
210
+ headers: opts.ip ? { "X-Forwarded-For": opts.ip } : void 0,
211
+ signal: opts.signal,
212
+ resource: "events.volunteer"
213
+ });
214
+ return { ok: true, ...data };
215
+ } catch (err) {
216
+ if (isHearthError(err) && (err.code === "bad_request" || err.code === "rate_limited" || err.code === "feature_disabled" || err.code === "not_found")) {
217
+ return { ok: false, message: err.message, fieldErrors: err.details ?? {} };
218
+ }
219
+ throw err;
220
+ }
199
221
  }
200
222
  },
201
223
  categories: {
@@ -1,4 +1,4 @@
1
- import { Collection, CollectionEntry, HearthEvent, Site, PageSummary, Page, HearthEventDetail, RegistrationInput, RegistrationResult, CategoryGroup, SitemapEntry, Redirect, AnalyticsReport, Fundraiser, FundraiserDetail, DonationInput, DonationCheckout, HearthForm } from '@the-smithy/hearth-schema';
1
+ import { Collection, CollectionEntry, HearthEvent, Site, PageSummary, Page, HearthEventDetail, RegistrationInput, RegistrationResult, VolunteerInput, VolunteerResult, CategoryGroup, SitemapEntry, Redirect, AnalyticsReport, Fundraiser, FundraiserDetail, DonationInput, DonationCheckout, HearthForm } from '@the-smithy/hearth-schema';
2
2
 
3
3
  type HearthErrorCode = "unauthorized" | "not_found" | "bad_request" | "feature_disabled" | "rate_limited" | "server_error" | "network" | "timeout" | "invalid_response";
4
4
  /** Any failure talking to Hearth. Messages never contain the API key. */
@@ -142,6 +142,21 @@ declare function createCore(options: HearthClientOptions): {
142
142
  message: string;
143
143
  fieldErrors: Record<string, string>;
144
144
  }>;
145
+ /**
146
+ * Sign up to volunteer at an event that asks for volunteers (its `volunteering` is set). Post `shifts` when
147
+ * `volunteering.pickShifts` isn't "none", and each answer under its field's `name`. Validation problems
148
+ * (including a full shift or closed sign-up) resolve to { ok: false, fieldErrors }; network and auth failures throw.
149
+ */
150
+ volunteer: (slug: string, input: VolunteerInput, opts?: {
151
+ ip?: string | null;
152
+ signal?: AbortSignal;
153
+ }) => Promise<({
154
+ ok: true;
155
+ } & VolunteerResult) | {
156
+ ok: false;
157
+ message: string;
158
+ fieldErrors: Record<string, string>;
159
+ }>;
145
160
  };
146
161
  categories: {
147
162
  /** The site's category groups, each with its categories. */
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { H as HearthCore, a as HearthClientOptions } from './client-B3n7p9lK.js';
2
- export { C as CallOptions, b as CollectionPage, D as DEFAULT_BASE_URL, E as EventCollectionPage, F as FormResult, c as FormValues, d as HearthError, e as HearthErrorCode, f as HearthHooks, N as NextFetchOptions, P as PreviewSession, R as RequestContext, g as RequestOptions, h as findRedirect, i as isHearthError, n as normalizeSlug } from './client-B3n7p9lK.js';
3
- export { AnalyticsReport, AnalyticsRow, Block, BlockOf, CategoryGroup, CategoryRef, Collection, CollectionEntry, CollectionRef, DonationCheckout, DonationInput, EntryDetails, EventCustomFields, EventRegistration, EventTicket, Fundraiser, FundraiserDetail, HearthEvent, HearthEventDetail, KnownBlock, Page, PageMeta, PageSummary, Redirect, RegistrationInput, RegistrationResult, ResolvedBlock, Site, SiteAnalytics, SiteSettings, SitemapEntry, UnknownBlock, WebhookPayload, videoEmbedUrl } from '@the-smithy/hearth-schema';
1
+ import { H as HearthCore, a as HearthClientOptions } from './client-IImehdSM.js';
2
+ export { C as CallOptions, b as CollectionPage, D as DEFAULT_BASE_URL, E as EventCollectionPage, F as FormResult, c as FormValues, d as HearthError, e as HearthErrorCode, f as HearthHooks, N as NextFetchOptions, P as PreviewSession, R as RequestContext, g as RequestOptions, h as findRedirect, i as isHearthError, n as normalizeSlug } from './client-IImehdSM.js';
3
+ export { AnalyticsReport, AnalyticsRow, Block, BlockOf, CategoryGroup, CategoryRef, Collection, CollectionEntry, CollectionRef, DonationCheckout, DonationInput, EntryDetails, EventCustomFields, EventRegistration, EventTicket, EventVolunteerShift, EventVolunteering, Fundraiser, FundraiserDetail, HearthEvent, HearthEventDetail, ImageRef, KnownBlock, Page, PageMeta, PageSummary, Position, Redirect, RegistrationInput, RegistrationResult, ResolvedBlock, Site, SiteAnalytics, SiteSettings, SitemapEntry, UnknownBlock, VolunteerInput, VolunteerResult, WebhookPayload, directionsUrl, focalPosition, pinned, videoEmbedUrl } from '@the-smithy/hearth-schema';
4
4
 
5
5
  /** A plugin adds resources (and can use the core's `request`) under its own names. */
6
6
  type HearthPlugin<T extends object = object> = {
package/dist/index.js CHANGED
@@ -5,10 +5,12 @@ import {
5
5
  findRedirect,
6
6
  isHearthError,
7
7
  normalizeSlug
8
- } from "./chunk-62OMCVBE.js";
8
+ } from "./chunk-V47RFBJE.js";
9
9
 
10
10
  // src/index.ts
11
11
  import { videoEmbedUrl } from "@the-smithy/hearth-schema";
12
+ import { focalPosition } from "@the-smithy/hearth-schema";
13
+ import { directionsUrl, pinned } from "@the-smithy/hearth-schema";
12
14
  function definePlugin(setup) {
13
15
  return { setup };
14
16
  }
@@ -27,8 +29,11 @@ export {
27
29
  HearthError,
28
30
  createHearthClient,
29
31
  definePlugin,
32
+ directionsUrl,
30
33
  findRedirect,
34
+ focalPosition,
31
35
  isHearthError,
32
36
  normalizeSlug,
37
+ pinned,
33
38
  videoEmbedUrl
34
39
  };
package/dist/next.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as HearthCore, b as CollectionPage, E as EventCollectionPage, F as FormResult, c as FormValues } from './client-B3n7p9lK.js';
1
+ import { H as HearthCore, b as CollectionPage, E as EventCollectionPage, F as FormResult, c as FormValues } from './client-IImehdSM.js';
2
2
  import * as _the_smithy_hearth_schema from '@the-smithy/hearth-schema';
3
3
  import { Site, PageMeta } from '@the-smithy/hearth-schema';
4
4
  import { Metadata, MetadataRoute } from 'next';
@@ -204,6 +204,14 @@ declare function createHearthNext(client: HearthCore, { tag, revalidate }?: Hear
204
204
  registerAction: (slug: string, { returnPath }?: {
205
205
  returnPath?: string;
206
206
  }) => (_prev: FormResult | null, formData: FormData) => Promise<FormResult>;
207
+ /**
208
+ * A volunteer sign-up form's server action, for `useActionState(action, null)`:
209
+ * export const volunteer = async (slug: string, prev: FormResult | null, fd: FormData) => volunteerAction(slug)(prev, fd);
210
+ * Fields: `name`, `email`, one `shift` per chosen shift id, the honeypot `_gotcha`, and every other field as an
211
+ * answer under its name (a ticked checkbox posts "on"). Resolves to { ok: true, message } with the
212
+ * event's thank-you message.
213
+ */
214
+ volunteerAction: (slug: string) => (_prev: FormResult | null, formData: FormData) => Promise<FormResult>;
207
215
  /** Whether this request is a draft preview (to show a "Preview" banner with an exit link). */
208
216
  isPreview: () => Promise<boolean>;
209
217
  /**
package/dist/next.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  findRedirect,
3
3
  isHearthError,
4
4
  normalizeSlug
5
- } from "./chunk-62OMCVBE.js";
5
+ } from "./chunk-V47RFBJE.js";
6
6
  import {
7
7
  parseWebhook,
8
8
  tagsForWebhook
@@ -129,6 +129,33 @@ function createHearthNext(client, { tag = "hearth", revalidate = 3600 } = {}) {
129
129
  if (result.status === "pending") redirect(result.checkoutUrl);
130
130
  return { ok: true, message: "You're registered. A confirmation is on its way to your email." };
131
131
  },
132
+ /**
133
+ * A volunteer sign-up form's server action, for `useActionState(action, null)`:
134
+ * export const volunteer = async (slug: string, prev: FormResult | null, fd: FormData) => volunteerAction(slug)(prev, fd);
135
+ * Fields: `name`, `email`, one `shift` per chosen shift id, the honeypot `_gotcha`, and every other field as an
136
+ * answer under its name (a ticked checkbox posts "on"). Resolves to { ok: true, message } with the
137
+ * event's thank-you message.
138
+ */
139
+ volunteerAction: (slug) => async (_prev, formData) => {
140
+ const h = await headers();
141
+ const answers = {};
142
+ for (const [key, value] of formData.entries()) {
143
+ if (typeof value !== "string" || key.startsWith("$") || ["name", "email", "shift", "_gotcha"].includes(key)) continue;
144
+ answers[key] = value;
145
+ }
146
+ const result = await client.events.volunteer(
147
+ slug,
148
+ {
149
+ name: String(formData.get("name") ?? "").trim(),
150
+ email: String(formData.get("email") ?? "").trim(),
151
+ shifts: formData.getAll("shift").map(String),
152
+ answers,
153
+ _gotcha: String(formData.get("_gotcha") ?? "") || void 0
154
+ },
155
+ { ip: clientIp(h) }
156
+ );
157
+ return result.ok ? { ok: true, message: result.message } : { ok: false, message: result.message, fieldErrors: result.fieldErrors };
158
+ },
132
159
  /** Whether this request is a draft preview (to show a "Preview" banner with an exit link). */
133
160
  isPreview: async () => Boolean(await previewToken()),
134
161
  /**
package/llms.md CHANGED
@@ -4,6 +4,10 @@ Hearth is a headless CMS. This package is a server-only client for its Delivery
4
4
  client components: the API key is secret.
5
5
 
6
6
  ## Setup
7
+ - Sites made with `pnpm create @the-smithy/hearth-site` already have all of this, and every block, page and form,
8
+ in `src/hearth/` (the client is `@/hearth/client`). Don't rebuild them: style the site in `src/theme/` (tokens,
9
+ and Tailwind classes per slot; `src/hearth/theme/defaults.ts` lists the slots) and read the site's `CLAUDE.md`.
10
+ `pnpm hearth:update` brings in new releases. The rest of this section is for other Next.js apps.
7
11
  - `pnpm add @the-smithy/hearth` (public on npm; no registry config or token needed). It's useless without a
8
12
  Hearth site's API key. The full guide is in Hearth under API docs (Markdown download).
9
13
  - env: `HEARTH_SITE`, `HEARTH_API_KEY`, optionally `HEARTH_WEBHOOK_SECRET`
@@ -41,6 +45,11 @@ client components: the API key is secret.
41
45
  - Tickets: `events.register(slug, { ticket, name, email, phone?, teamName?, attendees?, notes?, returnUrl }, { ip? })`:
42
46
  `{ ok: true, status: "confirmed" }`, `{ ok: true, status: "pending", checkoutUrl }` or `{ ok: false, message, fieldErrors }`.
43
47
  Events with on-site registration have `registration: { open, closesAt, tickets[], fundraiser }`.
48
+ - Volunteers: `events.volunteer(slug, { name, email, shifts?, answers? }, { ip? })`: `{ ok: true, signupId, message }` or
49
+ `{ ok: false, message, fieldErrors }`. Events asking for volunteers have `volunteering: { open, closesAt, intro, fields,
50
+ pickShifts: "none" | "one" | "many", shifts[], submitLabel, successMessage, fundraiser }`.
51
+ The `volunteerSignup` block places that form anywhere: its `data.event` is the Event (no `blocks`, `volunteering` always set);
52
+ without `eventSlug` it stands for the event whose page it's on, and an event page with one leaves out its own form.
44
53
  - Giving (sites with the feature): `fundraisers.list({ status?, category?, limit? }): Fundraiser[]`,
45
54
  `fundraisers.get(slug): FundraiserDetail | null`, `donations.checkout({ amountCents, fundraiser?, name?, email?,
46
55
  anonymous?, message?, returnUrl }, { ip? })`: `{ ok: true, checkoutUrl }` or `{ ok: false, message, fieldErrors }`.
@@ -53,6 +62,15 @@ client components: the API key is secret.
53
62
  `openGraph`, `twitter`, `image { url, width, height, alt }`, `jsonLd[]`.
54
63
  - Each block is `{ id, type, data }`; fields live on `data`. Narrow by `type`; use `BlockOf<"hero">` for one type.
55
64
  - Unknown future types arrive as `UnknownBlock`. Always handle a fallback.
65
+ - Images are `ImageRef = { src, alt, width?, height?, focal?: { x, y } }`. `focal` is the spot to keep in view when the
66
+ image is cropped (percentages across and down, set in Hearth's media library). For an image with `object-fit: cover`,
67
+ set `style={{ objectPosition: focalPosition(image, frameRatio) }}` (`focalPosition` from `@the-smithy/hearth`; the
68
+ frame's width / height centres the spot as far as the crop allows, omit it for frames whose shape changes). Don't
69
+ apply it to uncropped images (`object-contain`, logos).
70
+ - Places (`location`, and each of `locations`' `items`) carry `position?: Position = { lat, lng }`, the map pin set in
71
+ Hearth. Draw maps from it, never by looking addresses up: the template uses MapLibre GL with OpenFreeMap tiles (free,
72
+ no key, no cookies). `pinned(places)` keeps the places with a pin; `directionsUrl(place)` links to directions by
73
+ name and address. Without a pin, show no map.
56
74
 
57
75
  ## Next.js (`@the-smithy/hearth/next`)
58
76
  - `hearthNext.getPage(slug)`, `getPages()`, `getSite()`, `getEvents(opts)`, `getEvent(slug)`:
@@ -69,6 +87,8 @@ client components: the API key is secret.
69
87
  - Tickets: `registerAction(slug)` for a registration form (`ticket`, `name`, `email`, `phone`, `teamName`, `notes`,
70
88
  one `attendee` per name); free tickets resolve to `{ ok: true, message }`, paid ones redirect to checkout and return
71
89
  to `/registered`.
90
+ - Volunteers: `volunteerAction(slug)` for a sign-up form (`name`, `email`, one `shift` per chosen shift, every other
91
+ field as an answer under its name); resolves to `{ ok: true, message }` with the event's thank-you message.
72
92
  - Draft preview: `app/api/preview/route.ts`: `export const GET = hearthNext.previewHandler()` and
73
93
  `app/api/preview/exit/route.ts`: `export const GET = hearthNext.exitPreviewHandler()`. While previewing,
74
94
  `getPage`, `getEvent` and `getFundraiser` return drafts. Every site renders the SDK's live preview in its root
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the-smithy/hearth",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Typed client for the Hearth Delivery API: content, SEO, forms and publish webhooks.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -49,7 +49,7 @@
49
49
  "llms.md"
50
50
  ],
51
51
  "dependencies": {
52
- "@the-smithy/hearth-schema": "^0.19.0"
52
+ "@the-smithy/hearth-schema": "^0.21.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "next": ">=15",