@jamwidgets/astro 0.1.0 → 0.2.3

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/dist/index.d.ts CHANGED
@@ -6,6 +6,6 @@
6
6
  */
7
7
  export { DEFAULT_ENDPOINT, API_PATH, VISITOR_STORAGE_KEY, type JamWidgetsConfig, type SeriphConfig, // deprecated alias
8
8
  type Comment, type ReactionCounts, type FormSubmitResponse, type SubscribeResponse, type JamwidgetsPost, type SeriphPost, // deprecated alias
9
- type Announcement, type AnnouncementType, type Poll, type PollOption, type PollSettings, type PollWithResults, type ShowResultsMode, type FeedbackType, buildUrl, getSiteKey, getVisitorId, setVisitorId, type SubmitFormOptions, submitForm, type FetchCommentsOptions, fetchComments, type PostCommentOptions, postComment, type FetchReactionsOptions, type FetchReactionsResponse, fetchReactions, type AddReactionOptions, addReaction, type RemoveReactionOptions, removeReaction, type SubscribeOptions, subscribe, type JoinWaitlistOptions, type JoinWaitlistResponse, joinWaitlist, type ViewCountsOptions, type ViewCounts, type RecordViewResponse, getViewCounts, recordView, type SubmitFeedbackOptions, type SubmitFeedbackResponse, submitFeedback, type FetchPollOptions, fetchPoll, type VotePollOptions, type VotePollResponse, votePoll, type FetchAnnouncementsOptions, fetchAnnouncements, type DismissAnnouncementOptions, dismissAnnouncement, type FetchPostsOptions, fetchPosts, type FetchPostOptions, fetchPost, type ControllerStatus, type ControllerListener, type SubscribeState, type FormState, type ReactionsState, type CommentsState, type WaitlistState, type ViewCountsState, type FeedbackState, type PollState, type AnnouncementsState, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
9
+ type Announcement, type AnnouncementType, type Poll, type PollOption, type PollSettings, type PollWithResults, type ShowResultsMode, type FeedbackType, buildUrl, getSiteKey, getVisitorId, setVisitorId, type SubmitFormOptions, submitForm, type FetchCommentsOptions, fetchComments, type PostCommentOptions, postComment, type FetchReactionsOptions, type FetchReactionsResponse, fetchReactions, type AddReactionOptions, addReaction, type RemoveReactionOptions, removeReaction, type SubscribeOptions, subscribe, type JoinWaitlistOptions, type JoinWaitlistResponse, joinWaitlist, type ViewCountsOptions, type ViewCounts, type RecordViewResponse, getViewCounts, recordView, type SubmitFeedbackOptions, type SubmitFeedbackResponse, submitFeedback, type FetchPollOptions, fetchPoll, type VotePollOptions, type VotePollResponse, votePoll, type FetchAnnouncementsOptions, fetchAnnouncements, type DismissAnnouncementOptions, dismissAnnouncement, type FetchPostsOptions, fetchPosts, type FetchPostOptions, fetchPost, type SiteConfig, fetchSiteConfig, POWERED_BY_URL, POWERED_BY_LABEL, type ControllerStatus, type ControllerListener, type SubscribeState, type FormState, type ReactionsState, type CommentsState, type WaitlistState, type ViewCountsState, type FeedbackState, type PollState, type AnnouncementsState, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
10
10
  export { jamwidgetsPostsLoader, seriphPostsLoader, // deprecated alias
11
11
  type JamwidgetsPostsLoaderOptions, type SeriphPostsLoaderOptions, } from "./loader.js";
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ export {
9
9
  // Constants
10
10
  DEFAULT_ENDPOINT, API_PATH, VISITOR_STORAGE_KEY,
11
11
  // Helpers
12
- buildUrl, getSiteKey, getVisitorId, setVisitorId, submitForm, fetchComments, postComment, fetchReactions, addReaction, removeReaction, subscribe, joinWaitlist, getViewCounts, recordView, submitFeedback, fetchPoll, votePoll, fetchAnnouncements, dismissAnnouncement, fetchPosts, fetchPost, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
12
+ buildUrl, getSiteKey, getVisitorId, setVisitorId, submitForm, fetchComments, postComment, fetchReactions, addReaction, removeReaction, subscribe, joinWaitlist, getViewCounts, recordView, submitFeedback, fetchPoll, votePoll, fetchAnnouncements, dismissAnnouncement, fetchPosts, fetchPost, fetchSiteConfig, POWERED_BY_URL, POWERED_BY_LABEL, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
13
13
  // Re-export loader (Astro-specific)
14
14
  export { jamwidgetsPostsLoader, seriphPostsLoader, // deprecated alias
15
15
  } from "./loader.js";
package/dist/loader.d.ts CHANGED
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import { fetchPosts as coreFetchPosts, fetchPost as coreFetchPost, type JamwidgetsPost, type SeriphPost, // deprecated alias
20
20
  type FetchPostsOptions, type FetchPostOptions } from "@jamwidgets/core";
21
+ import type { Loader } from "astro/loaders";
21
22
  export type { JamwidgetsPost, SeriphPost, FetchPostsOptions, FetchPostOptions };
22
23
  export { coreFetchPosts as fetchPosts, coreFetchPost as fetchPost };
23
24
  export interface JamwidgetsPostsLoaderOptions {
@@ -34,29 +35,11 @@ export interface JamwidgetsPostsLoaderOptions {
34
35
  }
35
36
  /** @deprecated Use JamwidgetsPostsLoaderOptions instead */
36
37
  export type SeriphPostsLoaderOptions = JamwidgetsPostsLoaderOptions;
37
- interface LoaderContext {
38
- store: {
39
- set: (entry: {
40
- id: string;
41
- data: JamwidgetsPost;
42
- }) => void;
43
- clear: () => void;
44
- };
45
- logger: {
46
- info: (message: string) => void;
47
- warn: (message: string) => void;
48
- error: (message: string) => void;
49
- };
50
- generateDigest: (data: unknown) => string;
51
- }
52
38
  /**
53
39
  * Creates an Astro content loader that fetches posts from Jamwidgets.
54
40
  *
55
41
  * Posts are fetched at build time and cached by Astro.
56
42
  */
57
- export declare function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions): {
58
- name: string;
59
- load(context: LoaderContext): Promise<void>;
60
- };
43
+ export declare function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions): Loader;
61
44
  /** @deprecated Use jamwidgetsPostsLoader instead */
62
45
  export declare const seriphPostsLoader: typeof jamwidgetsPostsLoader;
package/dist/loader.js CHANGED
@@ -41,7 +41,10 @@ export function jamwidgetsPostsLoader(options) {
41
41
  const response = await fetch(url.toString(), {
42
42
  headers: {
43
43
  "X-Jamwidgets-Key": siteKey,
44
- "X-JamWidgets-Key": siteKey, // backward compat
44
+ // Legacy headers for backward compatibility
45
+ "X-Seriph-Key": siteKey,
46
+ // User-Agent to avoid bot detection (Cloudflare, etc.)
47
+ "User-Agent": "JamwidgetsAstroLoader/1.0",
45
48
  },
46
49
  });
47
50
  if (!response.ok) {
@@ -52,7 +55,7 @@ export function jamwidgetsPostsLoader(options) {
52
55
  for (const post of data.posts) {
53
56
  store.set({
54
57
  id: post.slug,
55
- data: post,
58
+ data: { ...post },
56
59
  });
57
60
  }
58
61
  logger.info(`Loaded ${data.posts.length} posts from Jamwidgets`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jamwidgets/astro",
3
- "version": "0.1.0",
3
+ "version": "0.2.3",
4
4
  "description": "Astro components and content loader for Jamwidgets (forms, comments, reactions, posts)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "./Form": "./src/Form.astro",
25
25
  "./Comments": "./src/Comments.astro",
26
+ "./PoweredBy": "./src/PoweredBy.astro",
26
27
  "./Reactions": "./src/Reactions.astro",
27
28
  "./Views": "./src/Views.astro",
28
29
  "./Poll": "./src/Poll.astro",
@@ -37,6 +38,10 @@
37
38
  "src",
38
39
  "dist"
39
40
  ],
41
+ "scripts": {
42
+ "build": "tsc",
43
+ "dev": "tsc --watch"
44
+ },
40
45
  "keywords": [
41
46
  "astro",
42
47
  "jamwidgets",
@@ -50,16 +55,13 @@
50
55
  ],
51
56
  "license": "MIT",
52
57
  "dependencies": {
53
- "@jamwidgets/core": "0.1.0"
58
+ "@jamwidgets/core": "^0.2.2"
54
59
  },
55
60
  "peerDependencies": {
56
- "astro": "^5.0.0"
61
+ "astro": "^5.0.0 || ^6.0.0 || ^7.0.0"
57
62
  },
58
63
  "devDependencies": {
59
- "typescript": "^5.7.3"
60
- },
61
- "scripts": {
62
- "build": "tsc",
63
- "dev": "tsc --watch"
64
+ "astro": "^7.1.3",
65
+ "typescript": "^5.9.3"
64
66
  }
65
- }
67
+ }
@@ -35,6 +35,8 @@ const {
35
35
 
36
36
  // Build the API URL
37
37
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
38
+
39
+ import PoweredBy from "./PoweredBy.astro";
38
40
  ---
39
41
 
40
42
  <div
@@ -85,6 +87,8 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
85
87
  <div class="jamwidgets-comment-replies"></div>
86
88
  </div>
87
89
  </template>
90
+
91
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
88
92
  </div>
89
93
 
90
94
  <script>
@@ -38,6 +38,8 @@ const {
38
38
 
39
39
  // Build the API URL
40
40
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
41
+
42
+ import PoweredBy from "./PoweredBy.astro";
41
43
  ---
42
44
 
43
45
  <div
@@ -81,6 +83,8 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
81
83
  </button>
82
84
  </div>
83
85
  </form>
86
+
87
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
84
88
  </div>
85
89
 
86
90
  <script>
package/src/Form.astro CHANGED
@@ -40,6 +40,8 @@ const {
40
40
 
41
41
  // Build the API URL
42
42
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
43
+
44
+ import PoweredBy from "./PoweredBy.astro";
43
45
  ---
44
46
 
45
47
  <form
@@ -58,6 +60,8 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
58
60
 
59
61
  <!-- Success/Error message container (hidden by default) -->
60
62
  <div class="jamwidgets-form-message" style="display: none;" aria-live="polite"></div>
63
+
64
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
61
65
  </form>
62
66
 
63
67
  <script>
package/src/Poll.astro CHANGED
@@ -38,6 +38,8 @@ const {
38
38
 
39
39
  // Build the API URL
40
40
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
41
+
42
+ import PoweredBy from "./PoweredBy.astro";
41
43
  ---
42
44
 
43
45
  <div
@@ -60,6 +62,8 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
60
62
  <button type="button" class="jamwidgets-poll-submit" style="display: none;">Vote</button>
61
63
  </div>
62
64
  <div class="jamwidgets-poll-error" style="display: none;"></div>
65
+
66
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
63
67
  </div>
64
68
 
65
69
  <script>
@@ -0,0 +1,83 @@
1
+ ---
2
+ /**
3
+ * JamWidgets "Powered by" footer.
4
+ *
5
+ * Rendered at the bottom of a drop-in widget. It hydrates hidden and only
6
+ * reveals itself when the site is on the Free tier (no custom branding) — the
7
+ * gate is the `hasCustomBranding` flag from `GET /api/v1/site`. Paid tiers
8
+ * (Pro/Team) keep it hidden, and if the config fetch fails we stay hidden so a
9
+ * paying site is never wrongly branded.
10
+ *
11
+ * Included by the block widgets (Comments, Form, Subscribe, Poll, Feedback,
12
+ * Waitlist); not the ambient ones (Reactions, Views, Announcements).
13
+ */
14
+ interface Props {
15
+ /** Your site key (required) */
16
+ siteKey: string;
17
+ /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
18
+ endpoint?: string;
19
+ }
20
+
21
+ const DEFAULT_ENDPOINT = "https://jamwidgets.com";
22
+ const API_PATH = "/api/v1";
23
+
24
+ const { siteKey, endpoint = DEFAULT_ENDPOINT } = Astro.props;
25
+ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
26
+ ---
27
+
28
+ <div
29
+ class="jamwidgets-powered-by"
30
+ data-jamwidgets-branding
31
+ data-endpoint={baseUrl}
32
+ data-site-key={siteKey}
33
+ hidden
34
+ >
35
+ <a href="https://jamwidgets.com/?utm_source=powered_by" target="_blank" rel="noopener noreferrer">
36
+ Powered by JamWidgets
37
+ </a>
38
+ </div>
39
+
40
+ <script>
41
+ // Reveal the footer only for sites without custom branding (Free tier).
42
+ // Site config is fetched once per (endpoint, siteKey); on any failure we
43
+ // leave the footer hidden (fail closed — never brand a paying site).
44
+ const brandingCache = new Map<string, Promise<boolean>>();
45
+
46
+ function hasCustomBranding(baseUrl: string, siteKey: string): Promise<boolean> {
47
+ const key = `${baseUrl}::${siteKey}`;
48
+ let pending = brandingCache.get(key);
49
+ if (!pending) {
50
+ pending = fetch(`${baseUrl}/site`, { headers: { "X-JamWidgets-Key": siteKey } })
51
+ .then((res) => (res.ok ? res.json() : null))
52
+ .then((data) => data?.site_config?.hasCustomBranding === true)
53
+ .catch(() => true);
54
+ brandingCache.set(key, pending);
55
+ }
56
+ return pending;
57
+ }
58
+
59
+ document.querySelectorAll<HTMLElement>("[data-jamwidgets-branding]").forEach((el) => {
60
+ const baseUrl = el.dataset.endpoint;
61
+ const siteKey = el.dataset.siteKey;
62
+ if (!baseUrl || !siteKey) return;
63
+ hasCustomBranding(baseUrl, siteKey).then((custom) => {
64
+ if (!custom) el.removeAttribute("hidden");
65
+ });
66
+ });
67
+ </script>
68
+
69
+ <style>
70
+ .jamwidgets-powered-by {
71
+ margin-top: 0.75rem;
72
+ text-align: center;
73
+ font-size: 0.75rem;
74
+ line-height: 1;
75
+ }
76
+ .jamwidgets-powered-by a {
77
+ color: var(--jamwidgets-muted-color, #9ca3af);
78
+ text-decoration: none;
79
+ }
80
+ .jamwidgets-powered-by a:hover {
81
+ text-decoration: underline;
82
+ }
83
+ </style>
@@ -49,6 +49,8 @@ const {
49
49
 
50
50
  // Build the API URL
51
51
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
52
+
53
+ import PoweredBy from "./PoweredBy.astro";
52
54
  ---
53
55
 
54
56
  <form
@@ -79,6 +81,8 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
79
81
 
80
82
  <!-- Success/Error message container (hidden by default) -->
81
83
  <div class="jamwidgets-subscribe-message" style="display: none;" aria-live="polite"></div>
84
+
85
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
82
86
  </form>
83
87
 
84
88
  <script>
@@ -193,27 +197,41 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
193
197
  </script>
194
198
 
195
199
  <style is:global>
196
- @layer jamwidgets {
197
- .jamwidgets-subscribe {
198
- --jamwidgets-border-color: #d1d5db;
199
- --jamwidgets-input-bg: #ffffff;
200
- --jamwidgets-input-text: #111827;
201
- --jamwidgets-button-bg: #4f46e5;
202
- --jamwidgets-button-text: #ffffff;
203
- --jamwidgets-button-hover: #4338ca;
204
- --jamwidgets-success-bg: #dcfce7;
205
- --jamwidgets-success-text: #166534;
206
- --jamwidgets-success-border: #bbf7d0;
207
- --jamwidgets-error-bg: #fef2f2;
208
- --jamwidgets-error-text: #991b1b;
209
- --jamwidgets-error-border: #fecaca;
210
- --jamwidgets-focus-ring: rgba(79, 70, 229, 0.5);
211
-
212
- position: relative;
213
- }
200
+ .jamwidgets-subscribe {
201
+ --jamwidgets-border-color: #d1d5db;
202
+ --jamwidgets-input-bg: #ffffff;
203
+ --jamwidgets-input-text: #111827;
204
+ --jamwidgets-button-bg: #4f46e5;
205
+ --jamwidgets-button-text: #ffffff;
206
+ --jamwidgets-button-hover: #4338ca;
207
+ --jamwidgets-success-bg: #dcfce7;
208
+ --jamwidgets-success-text: #166534;
209
+ --jamwidgets-success-border: #bbf7d0;
210
+ --jamwidgets-error-bg: #fef2f2;
211
+ --jamwidgets-error-text: #991b1b;
212
+ --jamwidgets-error-border: #fecaca;
213
+ --jamwidgets-focus-ring: rgba(79, 70, 229, 0.5);
214
+ position: relative;
215
+ }
214
216
 
215
- /* Dark theme */
216
- .jamwidgets-subscribe.jamwidgets-theme-dark {
217
+ /* Dark theme */
218
+ .jamwidgets-subscribe.jamwidgets-theme-dark {
219
+ --jamwidgets-border-color: #4b5563;
220
+ --jamwidgets-input-bg: #1f2937;
221
+ --jamwidgets-input-text: #f9fafb;
222
+ --jamwidgets-button-bg: #6366f1;
223
+ --jamwidgets-button-hover: #818cf8;
224
+ --jamwidgets-success-bg: rgba(34, 197, 94, 0.15);
225
+ --jamwidgets-success-text: #86efac;
226
+ --jamwidgets-success-border: rgba(34, 197, 94, 0.3);
227
+ --jamwidgets-error-bg: rgba(239, 68, 68, 0.15);
228
+ --jamwidgets-error-text: #fca5a5;
229
+ --jamwidgets-error-border: rgba(239, 68, 68, 0.3);
230
+ }
231
+
232
+ /* Auto theme - follows prefers-color-scheme */
233
+ @media (prefers-color-scheme: dark) {
234
+ .jamwidgets-subscribe.jamwidgets-theme-auto {
217
235
  --jamwidgets-border-color: #4b5563;
218
236
  --jamwidgets-input-bg: #1f2937;
219
237
  --jamwidgets-input-text: #f9fafb;
@@ -226,89 +244,72 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
226
244
  --jamwidgets-error-text: #fca5a5;
227
245
  --jamwidgets-error-border: rgba(239, 68, 68, 0.3);
228
246
  }
247
+ }
229
248
 
230
- /* Auto theme - follows prefers-color-scheme */
231
- @media (prefers-color-scheme: dark) {
232
- .jamwidgets-subscribe.jamwidgets-theme-auto {
233
- --jamwidgets-border-color: #4b5563;
234
- --jamwidgets-input-bg: #1f2937;
235
- --jamwidgets-input-text: #f9fafb;
236
- --jamwidgets-button-bg: #6366f1;
237
- --jamwidgets-button-hover: #818cf8;
238
- --jamwidgets-success-bg: rgba(34, 197, 94, 0.15);
239
- --jamwidgets-success-text: #86efac;
240
- --jamwidgets-success-border: rgba(34, 197, 94, 0.3);
241
- --jamwidgets-error-bg: rgba(239, 68, 68, 0.15);
242
- --jamwidgets-error-text: #fca5a5;
243
- --jamwidgets-error-border: rgba(239, 68, 68, 0.3);
244
- }
245
- }
246
-
247
- .jamwidgets-subscribe-input-group {
248
- display: flex;
249
- gap: 0.5rem;
250
- }
249
+ .jamwidgets-subscribe-input-group {
250
+ display: flex;
251
+ gap: 0.5rem;
252
+ }
251
253
 
252
- .jamwidgets-subscribe-input {
253
- flex: 1;
254
- padding: 0.625rem 0.875rem;
255
- border: 1px solid var(--jamwidgets-border-color);
256
- border-radius: 0.375rem;
257
- background-color: var(--jamwidgets-input-bg);
258
- color: var(--jamwidgets-input-text);
259
- font-size: 0.875rem;
260
- line-height: 1.25rem;
261
- }
254
+ .jamwidgets-subscribe-input {
255
+ flex: 1;
256
+ padding: 0.625rem 0.875rem;
257
+ border: 1px solid var(--jamwidgets-border-color);
258
+ border-radius: 0.375rem;
259
+ background-color: var(--jamwidgets-input-bg);
260
+ color: var(--jamwidgets-input-text);
261
+ font-size: 0.875rem;
262
+ line-height: 1.25rem;
263
+ }
262
264
 
263
- .jamwidgets-subscribe-input:focus {
264
- outline: none;
265
- border-color: var(--jamwidgets-button-bg);
266
- box-shadow: 0 0 0 3px var(--jamwidgets-focus-ring);
267
- }
265
+ .jamwidgets-subscribe-input:focus {
266
+ outline: none;
267
+ border-color: var(--jamwidgets-button-bg);
268
+ box-shadow: 0 0 0 3px var(--jamwidgets-focus-ring);
269
+ }
268
270
 
269
- .jamwidgets-subscribe-input::placeholder {
270
- color: #9ca3af;
271
- }
271
+ .jamwidgets-subscribe-input::placeholder {
272
+ color: #9ca3af;
273
+ }
272
274
 
273
- .jamwidgets-subscribe-button {
274
- padding: 0.625rem 1rem;
275
- border: none;
276
- border-radius: 0.375rem;
277
- background-color: var(--jamwidgets-button-bg);
278
- color: var(--jamwidgets-button-text);
279
- font-size: 0.875rem;
280
- font-weight: 500;
281
- cursor: pointer;
282
- transition: background-color 0.15s ease;
283
- white-space: nowrap;
284
- }
275
+ .jamwidgets-subscribe-button {
276
+ padding: 0.625rem 1rem;
277
+ border: none;
278
+ border-radius: 0.375rem;
279
+ background-color: var(--jamwidgets-button-bg);
280
+ color: var(--jamwidgets-button-text);
281
+ font-size: 0.875rem;
282
+ font-weight: 500;
283
+ cursor: pointer;
284
+ transition: background-color 0.15s ease;
285
+ white-space: nowrap;
286
+ }
285
287
 
286
- .jamwidgets-subscribe-button:hover:not(:disabled) {
287
- background-color: var(--jamwidgets-button-hover);
288
- }
288
+ .jamwidgets-subscribe-button:hover:not(:disabled) {
289
+ background-color: var(--jamwidgets-button-hover);
290
+ }
289
291
 
290
- .jamwidgets-subscribe-button:disabled {
291
- opacity: 0.6;
292
- cursor: not-allowed;
293
- }
292
+ .jamwidgets-subscribe-button:disabled {
293
+ opacity: 0.6;
294
+ cursor: not-allowed;
295
+ }
294
296
 
295
- .jamwidgets-subscribe-message {
296
- margin-top: 0.75rem;
297
- padding: 0.625rem 0.875rem;
298
- border-radius: 0.375rem;
299
- font-size: 0.875rem;
300
- }
297
+ .jamwidgets-subscribe-message {
298
+ margin-top: 0.75rem;
299
+ padding: 0.625rem 0.875rem;
300
+ border-radius: 0.375rem;
301
+ font-size: 0.875rem;
302
+ }
301
303
 
302
- .jamwidgets-subscribe-success {
303
- background-color: var(--jamwidgets-success-bg);
304
- color: var(--jamwidgets-success-text);
305
- border: 1px solid var(--jamwidgets-success-border);
306
- }
304
+ .jamwidgets-subscribe-success {
305
+ background-color: var(--jamwidgets-success-bg);
306
+ color: var(--jamwidgets-success-text);
307
+ border: 1px solid var(--jamwidgets-success-border);
308
+ }
307
309
 
308
- .jamwidgets-subscribe-error {
309
- background-color: var(--jamwidgets-error-bg);
310
- color: var(--jamwidgets-error-text);
311
- border: 1px solid var(--jamwidgets-error-border);
312
- }
310
+ .jamwidgets-subscribe-error {
311
+ background-color: var(--jamwidgets-error-bg);
312
+ color: var(--jamwidgets-error-text);
313
+ border: 1px solid var(--jamwidgets-error-border);
313
314
  }
314
315
  </style>
@@ -40,6 +40,8 @@ const {
40
40
 
41
41
  // Build the API URL
42
42
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
43
+
44
+ import PoweredBy from "./PoweredBy.astro";
43
45
  ---
44
46
 
45
47
  <div
@@ -62,6 +64,8 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
62
64
  </button>
63
65
  </form>
64
66
  <div class="jamwidgets-waitlist-message" style="display: none;"></div>
67
+
68
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
65
69
  </div>
66
70
 
67
71
  <script>
package/src/index.ts CHANGED
@@ -95,6 +95,12 @@ export {
95
95
  type FetchPostOptions,
96
96
  fetchPost,
97
97
 
98
+ // Site config + branding
99
+ type SiteConfig,
100
+ fetchSiteConfig,
101
+ POWERED_BY_URL,
102
+ POWERED_BY_LABEL,
103
+
98
104
  // Controllers
99
105
  type ControllerStatus,
100
106
  type ControllerListener,
package/src/loader.ts CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  type FetchPostsOptions,
29
29
  type FetchPostOptions,
30
30
  } from "@jamwidgets/core";
31
+ import type { Loader, LoaderContext } from "astro/loaders";
31
32
 
32
33
  // Re-export types and functions from core
33
34
  export type { JamwidgetsPost, SeriphPost, FetchPostsOptions, FetchPostOptions };
@@ -49,19 +50,6 @@ export interface JamwidgetsPostsLoaderOptions {
49
50
  /** @deprecated Use JamwidgetsPostsLoaderOptions instead */
50
51
  export type SeriphPostsLoaderOptions = JamwidgetsPostsLoaderOptions;
51
52
 
52
- interface LoaderContext {
53
- store: {
54
- set: (entry: { id: string; data: JamwidgetsPost }) => void;
55
- clear: () => void;
56
- };
57
- logger: {
58
- info: (message: string) => void;
59
- warn: (message: string) => void;
60
- error: (message: string) => void;
61
- };
62
- generateDigest: (data: unknown) => string;
63
- }
64
-
65
53
  interface ApiResponse {
66
54
  posts: JamwidgetsPost[];
67
55
  total: number;
@@ -72,7 +60,7 @@ interface ApiResponse {
72
60
  *
73
61
  * Posts are fetched at build time and cached by Astro.
74
62
  */
75
- export function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions) {
63
+ export function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions): Loader {
76
64
  const {
77
65
  endpoint = DEFAULT_ENDPOINT,
78
66
  tag,
@@ -101,7 +89,10 @@ export function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions) {
101
89
  const response = await fetch(url.toString(), {
102
90
  headers: {
103
91
  "X-Jamwidgets-Key": siteKey,
104
- "X-JamWidgets-Key": siteKey, // backward compat
92
+ // Legacy headers for backward compatibility
93
+ "X-Seriph-Key": siteKey,
94
+ // User-Agent to avoid bot detection (Cloudflare, etc.)
95
+ "User-Agent": "JamwidgetsAstroLoader/1.0",
105
96
  },
106
97
  });
107
98
 
@@ -118,7 +109,7 @@ export function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions) {
118
109
  for (const post of data.posts) {
119
110
  store.set({
120
111
  id: post.slug,
121
- data: post,
112
+ data: { ...post },
122
113
  });
123
114
  }
124
115