@burdenoff/website-sdk 0.0.0-dev

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.
Files changed (119) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +357 -0
  3. package/dist/analytics/index.d.mts +27 -0
  4. package/dist/analytics/index.d.ts +27 -0
  5. package/dist/analytics/index.js +36 -0
  6. package/dist/analytics/index.js.map +1 -0
  7. package/dist/analytics/index.mjs +34 -0
  8. package/dist/analytics/index.mjs.map +1 -0
  9. package/dist/client/index.d.mts +29 -0
  10. package/dist/client/index.d.ts +29 -0
  11. package/dist/client/index.js +261 -0
  12. package/dist/client/index.js.map +1 -0
  13. package/dist/client/index.mjs +255 -0
  14. package/dist/client/index.mjs.map +1 -0
  15. package/dist/components/contact.d.mts +144 -0
  16. package/dist/components/contact.d.ts +144 -0
  17. package/dist/components/contact.js +806 -0
  18. package/dist/components/contact.js.map +1 -0
  19. package/dist/components/contact.mjs +780 -0
  20. package/dist/components/contact.mjs.map +1 -0
  21. package/dist/components/electron-download.d.mts +32 -0
  22. package/dist/components/electron-download.d.ts +32 -0
  23. package/dist/components/electron-download.js +131 -0
  24. package/dist/components/electron-download.js.map +1 -0
  25. package/dist/components/electron-download.mjs +108 -0
  26. package/dist/components/electron-download.mjs.map +1 -0
  27. package/dist/components/launch-countdown.d.mts +15 -0
  28. package/dist/components/launch-countdown.d.ts +15 -0
  29. package/dist/components/launch-countdown.js +89 -0
  30. package/dist/components/launch-countdown.js.map +1 -0
  31. package/dist/components/launch-countdown.mjs +87 -0
  32. package/dist/components/launch-countdown.mjs.map +1 -0
  33. package/dist/components/newsletter.d.mts +155 -0
  34. package/dist/components/newsletter.d.ts +155 -0
  35. package/dist/components/newsletter.js +880 -0
  36. package/dist/components/newsletter.js.map +1 -0
  37. package/dist/components/newsletter.mjs +854 -0
  38. package/dist/components/newsletter.mjs.map +1 -0
  39. package/dist/components/partners.d.mts +88 -0
  40. package/dist/components/partners.d.ts +88 -0
  41. package/dist/components/partners.js +1169 -0
  42. package/dist/components/partners.js.map +1 -0
  43. package/dist/components/partners.mjs +1143 -0
  44. package/dist/components/partners.mjs.map +1 -0
  45. package/dist/components/pricing.d.mts +194 -0
  46. package/dist/components/pricing.d.ts +194 -0
  47. package/dist/components/pricing.js +813 -0
  48. package/dist/components/pricing.js.map +1 -0
  49. package/dist/components/pricing.mjs +791 -0
  50. package/dist/components/pricing.mjs.map +1 -0
  51. package/dist/components/problems-solved.d.mts +37 -0
  52. package/dist/components/problems-solved.d.ts +37 -0
  53. package/dist/components/problems-solved.js +157 -0
  54. package/dist/components/problems-solved.js.map +1 -0
  55. package/dist/components/problems-solved.mjs +135 -0
  56. package/dist/components/problems-solved.mjs.map +1 -0
  57. package/dist/components/resource-links.d.mts +28 -0
  58. package/dist/components/resource-links.d.ts +28 -0
  59. package/dist/components/resource-links.js +229 -0
  60. package/dist/components/resource-links.js.map +1 -0
  61. package/dist/components/resource-links.mjs +227 -0
  62. package/dist/components/resource-links.mjs.map +1 -0
  63. package/dist/components/social-links.d.mts +37 -0
  64. package/dist/components/social-links.d.ts +37 -0
  65. package/dist/components/social-links.js +268 -0
  66. package/dist/components/social-links.js.map +1 -0
  67. package/dist/components/social-links.mjs +266 -0
  68. package/dist/components/social-links.mjs.map +1 -0
  69. package/dist/components/unsubscribe.d.mts +14 -0
  70. package/dist/components/unsubscribe.d.ts +14 -0
  71. package/dist/components/unsubscribe.js +538 -0
  72. package/dist/components/unsubscribe.js.map +1 -0
  73. package/dist/components/unsubscribe.mjs +517 -0
  74. package/dist/components/unsubscribe.mjs.map +1 -0
  75. package/dist/components/waitlist.d.mts +70 -0
  76. package/dist/components/waitlist.d.ts +70 -0
  77. package/dist/components/waitlist.js +864 -0
  78. package/dist/components/waitlist.js.map +1 -0
  79. package/dist/components/waitlist.mjs +841 -0
  80. package/dist/components/waitlist.mjs.map +1 -0
  81. package/dist/components/website-switcher.d.mts +27 -0
  82. package/dist/components/website-switcher.d.ts +27 -0
  83. package/dist/components/website-switcher.js +168 -0
  84. package/dist/components/website-switcher.js.map +1 -0
  85. package/dist/components/website-switcher.mjs +146 -0
  86. package/dist/components/website-switcher.mjs.map +1 -0
  87. package/dist/content/index.d.mts +247 -0
  88. package/dist/content/index.d.ts +247 -0
  89. package/dist/content/index.js +2458 -0
  90. package/dist/content/index.js.map +1 -0
  91. package/dist/content/index.mjs +2428 -0
  92. package/dist/content/index.mjs.map +1 -0
  93. package/dist/index.d.mts +72 -0
  94. package/dist/index.d.ts +72 -0
  95. package/dist/index.js +6273 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/index.mjs +6204 -0
  98. package/dist/index.mjs.map +1 -0
  99. package/dist/product/index.d.mts +94 -0
  100. package/dist/product/index.d.ts +94 -0
  101. package/dist/product/index.js +282 -0
  102. package/dist/product/index.js.map +1 -0
  103. package/dist/product/index.mjs +278 -0
  104. package/dist/product/index.mjs.map +1 -0
  105. package/dist/provider-D7Rij9UM.d.mts +86 -0
  106. package/dist/provider-D7Rij9UM.d.ts +86 -0
  107. package/dist/seo/index.d.mts +64 -0
  108. package/dist/seo/index.d.ts +64 -0
  109. package/dist/seo/index.js +61 -0
  110. package/dist/seo/index.js.map +1 -0
  111. package/dist/seo/index.mjs +59 -0
  112. package/dist/seo/index.mjs.map +1 -0
  113. package/dist/utils/index.d.mts +21 -0
  114. package/dist/utils/index.d.ts +21 -0
  115. package/dist/utils/index.js +12 -0
  116. package/dist/utils/index.js.map +1 -0
  117. package/dist/utils/index.mjs +10 -0
  118. package/dist/utils/index.mjs.map +1 -0
  119. package/package.json +168 -0
@@ -0,0 +1,278 @@
1
+ import { createContext, useState, useCallback, useLayoutEffect, useMemo, useContext } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ var __defProp = Object.defineProperty;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
7
+
8
+ // src/client/security.ts
9
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
10
+ var RESERVED_HEADERS = /* @__PURE__ */ new Set([
11
+ "authorization",
12
+ "cookie",
13
+ "x-actor-id",
14
+ "x-actor-type",
15
+ "x-consent-granted-by",
16
+ "x-consent-scope",
17
+ "x-consent-granted-at",
18
+ "x-consent-expires-at"
19
+ ]);
20
+ function validateGatewayUrl(value) {
21
+ let url;
22
+ try {
23
+ url = new URL(value);
24
+ } catch {
25
+ throw new Error("Gateway URL must be a valid absolute URL");
26
+ }
27
+ if (url.protocol === "https:") {
28
+ return url.toString();
29
+ }
30
+ if (url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase())) {
31
+ return url.toString();
32
+ }
33
+ throw new Error("Gateway URL must use HTTPS unless it targets localhost");
34
+ }
35
+ function sanitizeHeaders(headers) {
36
+ if (!headers) return void 0;
37
+ return Object.fromEntries(
38
+ Object.entries(headers).filter(
39
+ ([key]) => !RESERVED_HEADERS.has(key.toLowerCase())
40
+ )
41
+ );
42
+ }
43
+
44
+ // src/client/graphql-client.ts
45
+ var _WebSDKClient = class _WebSDKClient {
46
+ constructor(config) {
47
+ __publicField(this, "config");
48
+ __publicField(this, "queryCache", /* @__PURE__ */ new Map());
49
+ __publicField(this, "inflight", /* @__PURE__ */ new Map());
50
+ this.config = {
51
+ ...config,
52
+ gatewayUrl: validateGatewayUrl(config.gatewayUrl),
53
+ headers: sanitizeHeaders(config.headers)
54
+ };
55
+ }
56
+ getConfig() {
57
+ return this.config;
58
+ }
59
+ /**
60
+ * Execute a GraphQL query (with deduplication and caching)
61
+ */
62
+ async query(query, variables) {
63
+ const cacheKey = JSON.stringify({ query, variables });
64
+ const cached = this.queryCache.get(cacheKey);
65
+ if (cached) {
66
+ if (Date.now() - cached.ts < _WebSDKClient.CACHE_TTL) {
67
+ return cached.data;
68
+ }
69
+ this.queryCache.delete(cacheKey);
70
+ }
71
+ const existing = this.inflight.get(cacheKey);
72
+ if (existing) {
73
+ return existing;
74
+ }
75
+ const promise = this.request(query, variables).then((result) => {
76
+ if (result.data && !result.errors?.length) {
77
+ if (this.queryCache.size >= _WebSDKClient.MAX_CACHE_ENTRIES) {
78
+ const oldestEntry = this.queryCache.keys().next().value;
79
+ if (oldestEntry) {
80
+ this.queryCache.delete(oldestEntry);
81
+ }
82
+ }
83
+ this.queryCache.set(cacheKey, { data: result, ts: Date.now() });
84
+ }
85
+ return result;
86
+ }).finally(() => {
87
+ this.inflight.delete(cacheKey);
88
+ });
89
+ this.inflight.set(cacheKey, promise);
90
+ return promise;
91
+ }
92
+ /**
93
+ * Execute a GraphQL mutation
94
+ */
95
+ async mutate(mutation, variables) {
96
+ return this.request(mutation, variables);
97
+ }
98
+ async request(query, variables) {
99
+ try {
100
+ const response = await fetch(this.config.gatewayUrl, {
101
+ method: "POST",
102
+ headers: {
103
+ "Content-Type": "application/json",
104
+ "x-product-id": this.config.productId,
105
+ ...this.config.headers
106
+ },
107
+ body: JSON.stringify({ query, variables })
108
+ });
109
+ if (!response.ok) {
110
+ console.error(
111
+ "[WebSDK] GraphQL request failed",
112
+ response.status,
113
+ response.statusText
114
+ );
115
+ return {
116
+ errors: [
117
+ {
118
+ message: `HTTP ${response.status}: ${response.statusText}`,
119
+ extensions: { code: `HTTP_${response.status}` }
120
+ }
121
+ ]
122
+ };
123
+ }
124
+ return await response.json();
125
+ } catch (error) {
126
+ console.error(
127
+ "[WebSDK] Network error",
128
+ error instanceof Error ? error.message : "unknown error"
129
+ );
130
+ return {
131
+ errors: [
132
+ {
133
+ message: error instanceof Error ? error.message : "Network error",
134
+ extensions: { code: "NETWORK_ERROR" }
135
+ }
136
+ ]
137
+ };
138
+ }
139
+ }
140
+ };
141
+ __publicField(_WebSDKClient, "CACHE_TTL", 5 * 60 * 1e3);
142
+ // 5 minutes
143
+ __publicField(_WebSDKClient, "MAX_CACHE_ENTRIES", 100);
144
+ var WebSDKContext = createContext(null);
145
+ function useWebSDK() {
146
+ const client = useContext(WebSDKContext);
147
+ if (!client) {
148
+ throw new Error("useWebSDK must be used within a WebSDKProvider");
149
+ }
150
+ return client;
151
+ }
152
+ function useWebSDKConfig() {
153
+ const client = useWebSDK();
154
+ return client.getConfig();
155
+ }
156
+ var PRODUCT_FIELDS_BASE = `
157
+ id
158
+ name
159
+ slug
160
+ description
161
+ status
162
+ logoUrl
163
+ website
164
+ appUrl
165
+ docsUrl
166
+ contactEmail
167
+ supportEmail
168
+ contactPhone
169
+ contactAddress
170
+ seoTitle
171
+ seoDescription
172
+ seoKeywords
173
+ ogImage
174
+ socialLinks
175
+ recaptchaSiteKey
176
+ rybbitSiteId
177
+ notificationEmail
178
+ metadata
179
+ `;
180
+ var PUBLIC_PRODUCT_QUERY = `
181
+ query PublicProduct($slug: String!) {
182
+ publicProduct(slug: $slug) {
183
+ ${PRODUCT_FIELDS_BASE}
184
+ resourceLinks
185
+ }
186
+ }
187
+ `;
188
+ var PUBLIC_PRODUCT_QUERY_FALLBACK = `
189
+ query PublicProductFallback($slug: String!) {
190
+ publicProduct(slug: $slug) {
191
+ ${PRODUCT_FIELDS_BASE}
192
+ }
193
+ }
194
+ `;
195
+ var ProductContext = createContext({
196
+ product: null,
197
+ loading: true,
198
+ error: null,
199
+ hasProvider: false,
200
+ refetch: () => {
201
+ }
202
+ });
203
+ function ProductProvider({ children, fallback }) {
204
+ const client = useWebSDK();
205
+ const config = useWebSDKConfig();
206
+ const [product, setProduct] = useState(
207
+ fallback ? fallback : null
208
+ );
209
+ const [loading, setLoading] = useState(true);
210
+ const [error, setError] = useState(null);
211
+ const fetchProduct = useCallback(async () => {
212
+ if (!config.productSlug) {
213
+ setLoading(false);
214
+ return;
215
+ }
216
+ setLoading(true);
217
+ setError(null);
218
+ try {
219
+ let result = await client.query(
220
+ PUBLIC_PRODUCT_QUERY,
221
+ { slug: config.productSlug }
222
+ );
223
+ const schemaMissingResourceLinks = result.errors?.some(
224
+ (e) => /Cannot query field ["']?resourceLinks["']?/i.test(e?.message ?? "")
225
+ );
226
+ if (schemaMissingResourceLinks) {
227
+ result = await client.query(
228
+ PUBLIC_PRODUCT_QUERY_FALLBACK,
229
+ { slug: config.productSlug }
230
+ );
231
+ }
232
+ if (result.errors?.length) {
233
+ setError(result.errors[0]?.message ?? "Unknown error");
234
+ } else if (result.data?.publicProduct) {
235
+ setProduct(result.data.publicProduct);
236
+ }
237
+ } catch (err) {
238
+ setError(err instanceof Error ? err.message : "Failed to fetch product");
239
+ } finally {
240
+ setLoading(false);
241
+ }
242
+ }, [client, config.productSlug]);
243
+ useLayoutEffect(() => {
244
+ fetchProduct();
245
+ }, [fetchProduct]);
246
+ const value = useMemo(
247
+ () => ({
248
+ product,
249
+ loading,
250
+ error,
251
+ hasProvider: true,
252
+ refetch: fetchProduct
253
+ }),
254
+ [product, loading, error, fetchProduct]
255
+ );
256
+ return /* @__PURE__ */ jsx(ProductContext.Provider, { value, children });
257
+ }
258
+ function useProduct() {
259
+ return useContext(ProductContext);
260
+ }
261
+ function useProductConfig() {
262
+ const { product } = useProduct();
263
+ const config = useWebSDKConfig();
264
+ return {
265
+ // Prefer product-level config, fall back to SDK config
266
+ recaptchaSiteKey: product?.recaptchaSiteKey ?? config.recaptchaSiteKey ?? null,
267
+ rybbitSiteId: product?.rybbitSiteId ?? config.rybbitSiteId ?? null,
268
+ contactEmail: product?.contactEmail ?? null,
269
+ supportEmail: product?.supportEmail ?? null,
270
+ notificationEmail: product?.notificationEmail ?? product?.contactEmail ?? null,
271
+ productName: product?.name ?? null,
272
+ logoUrl: product?.logoUrl ?? null
273
+ };
274
+ }
275
+
276
+ export { ProductProvider, useProduct, useProductConfig };
277
+ //# sourceMappingURL=index.mjs.map
278
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/security.ts","../../src/client/graphql-client.ts","../../src/client/provider.tsx","../../src/product/product-provider.tsx"],"names":["createContext","useMemo","jsx","useContext"],"mappings":"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","file":"index.mjs","sourcesContent":["const LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"::1\"]);\nconst RESERVED_HEADERS = new Set([\n \"authorization\",\n \"cookie\",\n \"x-actor-id\",\n \"x-actor-type\",\n \"x-consent-granted-by\",\n \"x-consent-scope\",\n \"x-consent-granted-at\",\n \"x-consent-expires-at\",\n]);\n\nexport function validateGatewayUrl(value: string): string {\n let url: URL;\n\n try {\n url = new URL(value);\n } catch {\n throw new Error(\"Gateway URL must be a valid absolute URL\");\n }\n\n if (url.protocol === \"https:\") {\n return url.toString();\n }\n\n if (\n url.protocol === \"http:\" &&\n LOOPBACK_HOSTS.has(url.hostname.toLowerCase())\n ) {\n return url.toString();\n }\n\n throw new Error(\"Gateway URL must use HTTPS unless it targets localhost\");\n}\n\nexport function sanitizeHeaders(\n headers?: Record<string, string>,\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n\n return Object.fromEntries(\n Object.entries(headers).filter(\n ([key]) => !RESERVED_HEADERS.has(key.toLowerCase()),\n ),\n );\n}\n","/**\n * Lightweight GraphQL client for the Burdenoff Web SDK.\n * Uses native fetch - zero external dependencies.\n * All calls are unauthenticated (public gateway operations only).\n */\n\nimport { sanitizeHeaders, validateGatewayUrl } from \"./security\";\n\nexport interface WebSDKClientConfig {\n /** Global public gateway URL (e.g. \"https://api.burdenoff.com/global/graphql\") */\n gatewayUrl: string;\n /** Platform product ID for scoping operations */\n productId: string;\n /** Product slug (used for public product queries) */\n productSlug?: string;\n /** reCAPTCHA site key (if not fetched from product config) */\n recaptchaSiteKey?: string;\n /** Rybbit analytics site ID */\n rybbitSiteId?: string;\n /** Custom headers to include in all requests */\n headers?: Record<string, string>;\n}\n\nexport interface GraphQLResponse<T = Record<string, unknown>> {\n data?: T;\n errors?: Array<{\n message: string;\n locations?: Array<{ line: number; column: number }>;\n path?: Array<string | number>;\n extensions?: Record<string, unknown>;\n }>;\n}\n\nexport class WebSDKClient {\n private config: WebSDKClientConfig;\n private queryCache = new Map<\n string,\n { data: GraphQLResponse<unknown>; ts: number }\n >();\n private inflight = new Map<string, Promise<GraphQLResponse<unknown>>>();\n private static CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n private static MAX_CACHE_ENTRIES = 100;\n\n constructor(config: WebSDKClientConfig) {\n this.config = {\n ...config,\n gatewayUrl: validateGatewayUrl(config.gatewayUrl),\n headers: sanitizeHeaders(config.headers),\n };\n }\n\n getConfig(): WebSDKClientConfig {\n return this.config;\n }\n\n /**\n * Execute a GraphQL query (with deduplication and caching)\n */\n async query<T = Record<string, unknown>>(\n query: string,\n variables?: Record<string, unknown>,\n ): Promise<GraphQLResponse<T>> {\n const cacheKey = JSON.stringify({ query, variables });\n\n // Return cached response if still valid, evict if expired\n const cached = this.queryCache.get(cacheKey);\n if (cached) {\n if (Date.now() - cached.ts < WebSDKClient.CACHE_TTL) {\n return cached.data as GraphQLResponse<T>;\n }\n this.queryCache.delete(cacheKey);\n }\n\n // Deduplicate: if same query is already in-flight, reuse the promise\n const existing = this.inflight.get(cacheKey);\n if (existing) {\n return existing as Promise<GraphQLResponse<T>>;\n }\n\n const promise = this.request<T>(query, variables)\n .then((result) => {\n // Cache successful responses only\n if (result.data && !result.errors?.length) {\n if (this.queryCache.size >= WebSDKClient.MAX_CACHE_ENTRIES) {\n const oldestEntry = this.queryCache.keys().next().value;\n if (oldestEntry) {\n this.queryCache.delete(oldestEntry);\n }\n }\n this.queryCache.set(cacheKey, { data: result, ts: Date.now() });\n }\n return result;\n })\n .finally(() => {\n this.inflight.delete(cacheKey);\n });\n\n this.inflight.set(cacheKey, promise);\n return promise;\n }\n\n /**\n * Execute a GraphQL mutation\n */\n async mutate<T = Record<string, unknown>>(\n mutation: string,\n variables?: Record<string, unknown>,\n ): Promise<GraphQLResponse<T>> {\n return this.request<T>(mutation, variables);\n }\n\n private async request<T>(\n query: string,\n variables?: Record<string, unknown>,\n ): Promise<GraphQLResponse<T>> {\n try {\n const response = await fetch(this.config.gatewayUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-product-id\": this.config.productId,\n ...this.config.headers,\n },\n body: JSON.stringify({ query, variables }),\n });\n\n if (!response.ok) {\n console.error(\n \"[WebSDK] GraphQL request failed\",\n response.status,\n response.statusText,\n );\n return {\n errors: [\n {\n message: `HTTP ${response.status}: ${response.statusText}`,\n extensions: { code: `HTTP_${response.status}` },\n },\n ],\n };\n }\n\n return (await response.json()) as GraphQLResponse<T>;\n } catch (error) {\n console.error(\n \"[WebSDK] Network error\",\n error instanceof Error ? error.message : \"unknown error\",\n );\n return {\n errors: [\n {\n message: error instanceof Error ? error.message : \"Network error\",\n extensions: { code: \"NETWORK_ERROR\" },\n },\n ],\n };\n }\n }\n}\n","/**\n * React context provider for the Web SDK client.\n * Wraps the application with WebSDKClient configuration.\n */\n\"use client\";\n\nimport { createContext, useContext, useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { resolveGatewayUrl } from \"./gateway-resolver\";\nimport { WebSDKClient, type WebSDKClientConfig } from \"./graphql-client\";\n\nconst WebSDKContext = createContext<WebSDKClient | null>(null);\n\nexport interface WebSDKProviderProps {\n /** SDK client configuration */\n config: WebSDKClientConfig;\n /** Child components */\n children: ReactNode;\n}\n\n/**\n * Provider component that makes the WebSDKClient available to all child components.\n *\n * @example\n * ```tsx\n * <WebSDKProvider config={{\n * gatewayUrl: \"https://api.burdenoff.com/global/graphql\",\n * productId: \"your-product-id\",\n * productSlug: \"your-product\",\n * }}>\n * <App />\n * </WebSDKProvider>\n * ```\n */\nexport function WebSDKProvider({ config, children }: WebSDKProviderProps) {\n const client = useMemo(() => {\n const resolvedConfig = {\n ...config,\n gatewayUrl: resolveGatewayUrl(config.gatewayUrl),\n };\n return new WebSDKClient(resolvedConfig);\n }, [config]);\n\n return (\n <WebSDKContext.Provider value={client}>{children}</WebSDKContext.Provider>\n );\n}\n\n/**\n * Hook to access the WebSDKClient instance.\n * Must be used within a WebSDKProvider.\n */\nexport function useWebSDK(): WebSDKClient {\n const client = useContext(WebSDKContext);\n if (!client) {\n throw new Error(\"useWebSDK must be used within a WebSDKProvider\");\n }\n return client;\n}\n\n/**\n * Hook to access the SDK configuration.\n * Useful for components that need config values like productId, recaptchaSiteKey, etc.\n */\nexport function useWebSDKConfig(): WebSDKClientConfig {\n const client = useWebSDK();\n return client.getConfig();\n}\n","/**\n * Product Provider\n *\n * React context and hooks for accessing product metadata from the global tenant service.\n * Fetches product info (name, logo, SEO, contact, reCAPTCHA key, etc.) on mount\n * and provides it to all child components.\n */\n\"use client\";\n\nimport {\n createContext,\n useContext,\n useState,\n useLayoutEffect,\n useCallback,\n useMemo,\n} from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { useWebSDK, useWebSDKConfig } from \"../client/provider\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ProductData {\n id: string;\n name: string;\n slug: string;\n description?: string | null;\n status: string;\n logoUrl?: string | null;\n website?: string | null;\n appUrl?: string | null;\n docsUrl?: string | null;\n contactEmail?: string | null;\n supportEmail?: string | null;\n contactPhone?: string | null;\n contactAddress?: string | null;\n seoTitle?: string | null;\n seoDescription?: string | null;\n seoKeywords?: string | null;\n ogImage?: string | null;\n socialLinks?: Record<string, string> | null;\n /**\n * Resource links for the website footer \"Resources\" column\n * (JSON: { docs, community, careers, team, companies, ... }).\n * Same shape as socialLinks. Free-form keys; the ResourceLinks\n * component renders known keys and silently ignores the rest.\n * Backed by Product.resourceLinks on the backend (global-tenant-svc).\n */\n resourceLinks?: Record<string, string> | null;\n recaptchaSiteKey?: string | null;\n rybbitSiteId?: string | null;\n notificationEmail?: string | null;\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface ProductProviderProps {\n /** Child components */\n children: ReactNode;\n /** Optional fallback product data (used while loading) */\n fallback?: Partial<ProductData>;\n}\n\n// ============================================================================\n// GraphQL Query\n// ============================================================================\n\n/**\n * Body of fields requested for every product fetch — shared by the primary\n * query and the legacy-backend fallback below. Kept as a string fragment\n * (not a real GraphQL `fragment` block) so we can append/strip\n * `resourceLinks` without touching anything else.\n */\nconst PRODUCT_FIELDS_BASE = `\n id\n name\n slug\n description\n status\n logoUrl\n website\n appUrl\n docsUrl\n contactEmail\n supportEmail\n contactPhone\n contactAddress\n seoTitle\n seoDescription\n seoKeywords\n ogImage\n socialLinks\n recaptchaSiteKey\n rybbitSiteId\n notificationEmail\n metadata\n`;\n\n/**\n * Primary query — includes the recently-added `resourceLinks` field.\n * Backends that have run the corresponding `global-tenant-svc` migration\n * (PR #64) expose this field; older ones don't, in which case\n * GraphQL validation rejects the whole query with\n * `Cannot query field \"resourceLinks\" on type \"PublicProduct\"`. The fetch\n * helper detects that specific error and transparently retries the\n * fallback below, so the SDK works on both pre- and post-migration\n * deployments without consumer-side gating.\n */\nconst PUBLIC_PRODUCT_QUERY = `\n query PublicProduct($slug: String!) {\n publicProduct(slug: $slug) {\n ${PRODUCT_FIELDS_BASE}\n resourceLinks\n }\n }\n`;\n\n/**\n * Fallback query for older backend deployments. Identical to the primary\n * minus `resourceLinks`. Consumers see `product.resourceLinks === undefined`\n * and any consumer (e.g. `<ResourceLinks/>`) falls through to prop / default\n * resolution paths.\n */\nconst PUBLIC_PRODUCT_QUERY_FALLBACK = `\n query PublicProductFallback($slug: String!) {\n publicProduct(slug: $slug) {\n ${PRODUCT_FIELDS_BASE}\n }\n }\n`;\n\n// ============================================================================\n// Context\n// ============================================================================\n\ninterface ProductContextValue {\n product: ProductData | null;\n loading: boolean;\n error: string | null;\n hasProvider: boolean;\n refetch: () => void;\n}\n\nconst ProductContext = createContext<ProductContextValue>({\n product: null,\n loading: true,\n error: null,\n hasProvider: false,\n refetch: () => {},\n});\n\n// ============================================================================\n// Provider\n// ============================================================================\n\n/**\n * ProductProvider fetches and provides product metadata to child components.\n * Uses the product slug from WebSDKConfig to fetch from the public API.\n *\n * @example\n * ```tsx\n * <WebSDKProvider config={sdkConfig}>\n * <ProductProvider>\n * <App />\n * </ProductProvider>\n * </WebSDKProvider>\n * ```\n */\nexport function ProductProvider({ children, fallback }: ProductProviderProps) {\n const client = useWebSDK();\n const config = useWebSDKConfig();\n const [product, setProduct] = useState<ProductData | null>(\n fallback ? (fallback as ProductData) : null,\n );\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const fetchProduct = useCallback(async () => {\n if (!config.productSlug) {\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n let result = await client.query<{ publicProduct: ProductData | null }>(\n PUBLIC_PRODUCT_QUERY,\n { slug: config.productSlug },\n );\n\n // Backwards-compat fallback: if the backend's `publicProduct` schema\n // doesn't yet expose `resourceLinks` (deployed before\n // `global-tenant-svc` PR #64), the primary query fails validation\n // with a \"Cannot query field …\" error and `result.data` is null.\n // Retry the fallback query that omits `resourceLinks`; consumers\n // gracefully see `product.resourceLinks === undefined` and fall\n // through their own resolution paths.\n const schemaMissingResourceLinks = result.errors?.some((e) =>\n /Cannot query field [\"']?resourceLinks[\"']?/i.test(e?.message ?? \"\"),\n );\n if (schemaMissingResourceLinks) {\n result = await client.query<{ publicProduct: ProductData | null }>(\n PUBLIC_PRODUCT_QUERY_FALLBACK,\n { slug: config.productSlug },\n );\n }\n\n if (result.errors?.length) {\n setError(result.errors[0]?.message ?? \"Unknown error\");\n } else if (result.data?.publicProduct) {\n setProduct(result.data.publicProduct);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to fetch product\");\n } finally {\n setLoading(false);\n }\n }, [client, config.productSlug]);\n\n // Use useLayoutEffect for earliest possible fetch after commit (before paint).\n // Runs when slug or client changes. Client-side query cache + dedup ensures\n // no duplicate network requests even if React StrictMode fires this twice.\n useLayoutEffect(() => {\n fetchProduct();\n }, [fetchProduct]);\n\n const value = useMemo(\n () => ({\n product,\n loading,\n error,\n hasProvider: true,\n refetch: fetchProduct,\n }),\n [product, loading, error, fetchProduct],\n );\n\n return (\n <ProductContext.Provider value={value}>{children}</ProductContext.Provider>\n );\n}\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\n/**\n * Hook to access the current product data.\n *\n * @example\n * ```tsx\n * const { product, loading } = useProduct();\n * if (loading) return <Loading />;\n * return <h1>{product?.name}</h1>;\n * ```\n */\nexport function useProduct(): ProductContextValue {\n return useContext(ProductContext);\n}\n\n/**\n * Hook to get product-specific configuration.\n * Merges SDK config with fetched product data.\n * Useful for getting reCAPTCHA keys, analytics IDs, etc.\n *\n * @example\n * ```tsx\n * const { recaptchaSiteKey, rybbitSiteId } = useProductConfig();\n * ```\n */\nexport function useProductConfig(): {\n recaptchaSiteKey: string | null;\n rybbitSiteId: string | null;\n contactEmail: string | null;\n supportEmail: string | null;\n notificationEmail: string | null;\n productName: string | null;\n logoUrl: string | null;\n} {\n const { product } = useProduct();\n const config = useWebSDKConfig();\n\n return {\n // Prefer product-level config, fall back to SDK config\n recaptchaSiteKey:\n product?.recaptchaSiteKey ?? config.recaptchaSiteKey ?? null,\n rybbitSiteId: product?.rybbitSiteId ?? config.rybbitSiteId ?? null,\n contactEmail: product?.contactEmail ?? null,\n supportEmail: product?.supportEmail ?? null,\n notificationEmail:\n product?.notificationEmail ?? product?.contactEmail ?? null,\n productName: product?.name ?? null,\n logoUrl: product?.logoUrl ?? null,\n };\n}\n"]}
@@ -0,0 +1,86 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * Lightweight GraphQL client for the Burdenoff Web SDK.
6
+ * Uses native fetch - zero external dependencies.
7
+ * All calls are unauthenticated (public gateway operations only).
8
+ */
9
+ interface WebSDKClientConfig {
10
+ /** Global public gateway URL (e.g. "https://api.burdenoff.com/global/graphql") */
11
+ gatewayUrl: string;
12
+ /** Platform product ID for scoping operations */
13
+ productId: string;
14
+ /** Product slug (used for public product queries) */
15
+ productSlug?: string;
16
+ /** reCAPTCHA site key (if not fetched from product config) */
17
+ recaptchaSiteKey?: string;
18
+ /** Rybbit analytics site ID */
19
+ rybbitSiteId?: string;
20
+ /** Custom headers to include in all requests */
21
+ headers?: Record<string, string>;
22
+ }
23
+ interface GraphQLResponse<T = Record<string, unknown>> {
24
+ data?: T;
25
+ errors?: Array<{
26
+ message: string;
27
+ locations?: Array<{
28
+ line: number;
29
+ column: number;
30
+ }>;
31
+ path?: Array<string | number>;
32
+ extensions?: Record<string, unknown>;
33
+ }>;
34
+ }
35
+ declare class WebSDKClient {
36
+ private config;
37
+ private queryCache;
38
+ private inflight;
39
+ private static CACHE_TTL;
40
+ private static MAX_CACHE_ENTRIES;
41
+ constructor(config: WebSDKClientConfig);
42
+ getConfig(): WebSDKClientConfig;
43
+ /**
44
+ * Execute a GraphQL query (with deduplication and caching)
45
+ */
46
+ query<T = Record<string, unknown>>(query: string, variables?: Record<string, unknown>): Promise<GraphQLResponse<T>>;
47
+ /**
48
+ * Execute a GraphQL mutation
49
+ */
50
+ mutate<T = Record<string, unknown>>(mutation: string, variables?: Record<string, unknown>): Promise<GraphQLResponse<T>>;
51
+ private request;
52
+ }
53
+
54
+ interface WebSDKProviderProps {
55
+ /** SDK client configuration */
56
+ config: WebSDKClientConfig;
57
+ /** Child components */
58
+ children: ReactNode;
59
+ }
60
+ /**
61
+ * Provider component that makes the WebSDKClient available to all child components.
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * <WebSDKProvider config={{
66
+ * gatewayUrl: "https://api.burdenoff.com/global/graphql",
67
+ * productId: "your-product-id",
68
+ * productSlug: "your-product",
69
+ * }}>
70
+ * <App />
71
+ * </WebSDKProvider>
72
+ * ```
73
+ */
74
+ declare function WebSDKProvider({ config, children }: WebSDKProviderProps): react_jsx_runtime.JSX.Element;
75
+ /**
76
+ * Hook to access the WebSDKClient instance.
77
+ * Must be used within a WebSDKProvider.
78
+ */
79
+ declare function useWebSDK(): WebSDKClient;
80
+ /**
81
+ * Hook to access the SDK configuration.
82
+ * Useful for components that need config values like productId, recaptchaSiteKey, etc.
83
+ */
84
+ declare function useWebSDKConfig(): WebSDKClientConfig;
85
+
86
+ export { WebSDKClient as W, type WebSDKClientConfig as a, WebSDKProvider as b, type WebSDKProviderProps as c, useWebSDKConfig as d, useWebSDK as u };
@@ -0,0 +1,86 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * Lightweight GraphQL client for the Burdenoff Web SDK.
6
+ * Uses native fetch - zero external dependencies.
7
+ * All calls are unauthenticated (public gateway operations only).
8
+ */
9
+ interface WebSDKClientConfig {
10
+ /** Global public gateway URL (e.g. "https://api.burdenoff.com/global/graphql") */
11
+ gatewayUrl: string;
12
+ /** Platform product ID for scoping operations */
13
+ productId: string;
14
+ /** Product slug (used for public product queries) */
15
+ productSlug?: string;
16
+ /** reCAPTCHA site key (if not fetched from product config) */
17
+ recaptchaSiteKey?: string;
18
+ /** Rybbit analytics site ID */
19
+ rybbitSiteId?: string;
20
+ /** Custom headers to include in all requests */
21
+ headers?: Record<string, string>;
22
+ }
23
+ interface GraphQLResponse<T = Record<string, unknown>> {
24
+ data?: T;
25
+ errors?: Array<{
26
+ message: string;
27
+ locations?: Array<{
28
+ line: number;
29
+ column: number;
30
+ }>;
31
+ path?: Array<string | number>;
32
+ extensions?: Record<string, unknown>;
33
+ }>;
34
+ }
35
+ declare class WebSDKClient {
36
+ private config;
37
+ private queryCache;
38
+ private inflight;
39
+ private static CACHE_TTL;
40
+ private static MAX_CACHE_ENTRIES;
41
+ constructor(config: WebSDKClientConfig);
42
+ getConfig(): WebSDKClientConfig;
43
+ /**
44
+ * Execute a GraphQL query (with deduplication and caching)
45
+ */
46
+ query<T = Record<string, unknown>>(query: string, variables?: Record<string, unknown>): Promise<GraphQLResponse<T>>;
47
+ /**
48
+ * Execute a GraphQL mutation
49
+ */
50
+ mutate<T = Record<string, unknown>>(mutation: string, variables?: Record<string, unknown>): Promise<GraphQLResponse<T>>;
51
+ private request;
52
+ }
53
+
54
+ interface WebSDKProviderProps {
55
+ /** SDK client configuration */
56
+ config: WebSDKClientConfig;
57
+ /** Child components */
58
+ children: ReactNode;
59
+ }
60
+ /**
61
+ * Provider component that makes the WebSDKClient available to all child components.
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * <WebSDKProvider config={{
66
+ * gatewayUrl: "https://api.burdenoff.com/global/graphql",
67
+ * productId: "your-product-id",
68
+ * productSlug: "your-product",
69
+ * }}>
70
+ * <App />
71
+ * </WebSDKProvider>
72
+ * ```
73
+ */
74
+ declare function WebSDKProvider({ config, children }: WebSDKProviderProps): react_jsx_runtime.JSX.Element;
75
+ /**
76
+ * Hook to access the WebSDKClient instance.
77
+ * Must be used within a WebSDKProvider.
78
+ */
79
+ declare function useWebSDK(): WebSDKClient;
80
+ /**
81
+ * Hook to access the SDK configuration.
82
+ * Useful for components that need config values like productId, recaptchaSiteKey, etc.
83
+ */
84
+ declare function useWebSDKConfig(): WebSDKClientConfig;
85
+
86
+ export { WebSDKClient as W, type WebSDKClientConfig as a, WebSDKProvider as b, type WebSDKProviderProps as c, useWebSDKConfig as d, useWebSDK as u };
@@ -0,0 +1,64 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * PageHead Component
5
+ * Manages dynamic meta tags for SEO optimization on a per-page basis.
6
+ * Supports JSON-LD structured data, Open Graph, Twitter Cards, favicons, and robots directives.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * <PageHead
11
+ * title="About Us"
12
+ * description="Learn about our mission and team"
13
+ * productName="MyProduct"
14
+ * keywords="about, team, mission"
15
+ * image="/og-about.png"
16
+ * url="https://myproduct.com/about"
17
+ * favicon="/favicon.ico"
18
+ * robots="index, follow"
19
+ * locale="en_US"
20
+ * twitterHandle="@burdenoff"
21
+ * themeColor="#1a1a2e"
22
+ * structuredData={{
23
+ * "@context": "https://schema.org",
24
+ * "@type": "WebPage",
25
+ * name: "About Us",
26
+ * }}
27
+ * />
28
+ * ```
29
+ */
30
+ interface PageHeadProps {
31
+ /** Page title */
32
+ title: string;
33
+ /** Meta description */
34
+ description?: string;
35
+ /** Product/brand name (appended to title) */
36
+ productName?: string;
37
+ /** SEO keywords */
38
+ keywords?: string;
39
+ /** Open Graph image URL */
40
+ image?: string;
41
+ /** Page canonical URL */
42
+ url?: string;
43
+ /** Favicon URL */
44
+ favicon?: string;
45
+ /** Robots directive (e.g., "index, follow") */
46
+ robots?: string;
47
+ /** Open Graph locale (e.g., "en_US") */
48
+ locale?: string;
49
+ /** Twitter handle (e.g., "@burdenoff") */
50
+ twitterHandle?: string;
51
+ /** Theme color for mobile browsers */
52
+ themeColor?: string;
53
+ /** JSON-LD structured data objects */
54
+ structuredData?: Record<string, unknown> | Array<Record<string, unknown>>;
55
+ /** Additional meta tags */
56
+ additionalMeta?: Array<{
57
+ name?: string;
58
+ property?: string;
59
+ content: string;
60
+ }>;
61
+ }
62
+ declare function PageHead({ title, description, productName, keywords, image, url, favicon, robots, locale, twitterHandle, themeColor, structuredData, additionalMeta, }: PageHeadProps): react_jsx_runtime.JSX.Element;
63
+
64
+ export { PageHead, type PageHeadProps };
@@ -0,0 +1,64 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * PageHead Component
5
+ * Manages dynamic meta tags for SEO optimization on a per-page basis.
6
+ * Supports JSON-LD structured data, Open Graph, Twitter Cards, favicons, and robots directives.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * <PageHead
11
+ * title="About Us"
12
+ * description="Learn about our mission and team"
13
+ * productName="MyProduct"
14
+ * keywords="about, team, mission"
15
+ * image="/og-about.png"
16
+ * url="https://myproduct.com/about"
17
+ * favicon="/favicon.ico"
18
+ * robots="index, follow"
19
+ * locale="en_US"
20
+ * twitterHandle="@burdenoff"
21
+ * themeColor="#1a1a2e"
22
+ * structuredData={{
23
+ * "@context": "https://schema.org",
24
+ * "@type": "WebPage",
25
+ * name: "About Us",
26
+ * }}
27
+ * />
28
+ * ```
29
+ */
30
+ interface PageHeadProps {
31
+ /** Page title */
32
+ title: string;
33
+ /** Meta description */
34
+ description?: string;
35
+ /** Product/brand name (appended to title) */
36
+ productName?: string;
37
+ /** SEO keywords */
38
+ keywords?: string;
39
+ /** Open Graph image URL */
40
+ image?: string;
41
+ /** Page canonical URL */
42
+ url?: string;
43
+ /** Favicon URL */
44
+ favicon?: string;
45
+ /** Robots directive (e.g., "index, follow") */
46
+ robots?: string;
47
+ /** Open Graph locale (e.g., "en_US") */
48
+ locale?: string;
49
+ /** Twitter handle (e.g., "@burdenoff") */
50
+ twitterHandle?: string;
51
+ /** Theme color for mobile browsers */
52
+ themeColor?: string;
53
+ /** JSON-LD structured data objects */
54
+ structuredData?: Record<string, unknown> | Array<Record<string, unknown>>;
55
+ /** Additional meta tags */
56
+ additionalMeta?: Array<{
57
+ name?: string;
58
+ property?: string;
59
+ content: string;
60
+ }>;
61
+ }
62
+ declare function PageHead({ title, description, productName, keywords, image, url, favicon, robots, locale, twitterHandle, themeColor, structuredData, additionalMeta, }: PageHeadProps): react_jsx_runtime.JSX.Element;
63
+
64
+ export { PageHead, type PageHeadProps };