@sonordev/agency-site-kit 0.1.2 → 0.2.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.
Files changed (45) hide show
  1. package/dist/cache-CE_ujDK4.d.cts +15 -0
  2. package/dist/cache-CE_ujDK4.d.ts +15 -0
  3. package/dist/{chunk-XM2QD3AK.cjs → chunk-EN6QQMKQ.cjs} +12 -6
  4. package/dist/chunk-EN6QQMKQ.cjs.map +1 -0
  5. package/dist/chunk-FCKDJ4MJ.cjs +8 -0
  6. package/dist/chunk-FCKDJ4MJ.cjs.map +1 -0
  7. package/dist/{chunk-JTI3F3QY.cjs → chunk-GZXXGWHE.cjs} +2 -2
  8. package/dist/{chunk-JTI3F3QY.cjs.map → chunk-GZXXGWHE.cjs.map} +1 -1
  9. package/dist/chunk-N7TVKUYQ.js +6 -0
  10. package/dist/chunk-N7TVKUYQ.js.map +1 -0
  11. package/dist/{chunk-OMOF4VR5.js → chunk-OWGVZNCO.js} +11 -5
  12. package/dist/chunk-OWGVZNCO.js.map +1 -0
  13. package/dist/{chunk-QBLWP25X.cjs → chunk-RVNASHPZ.cjs} +4 -3
  14. package/dist/chunk-RVNASHPZ.cjs.map +1 -0
  15. package/dist/{chunk-RLVW7WEK.js → chunk-VVPJ4UFO.js} +2 -2
  16. package/dist/{chunk-RLVW7WEK.js.map → chunk-VVPJ4UFO.js.map} +1 -1
  17. package/dist/{chunk-A4I4IK7V.js → chunk-ZEAOULCH.js} +4 -3
  18. package/dist/chunk-ZEAOULCH.js.map +1 -0
  19. package/dist/index.cjs +14 -14
  20. package/dist/index.d.cts +6 -0
  21. package/dist/index.d.ts +6 -0
  22. package/dist/index.js +3 -3
  23. package/dist/layout/index.cjs +3 -3
  24. package/dist/layout/index.d.cts +6 -1
  25. package/dist/layout/index.d.ts +6 -1
  26. package/dist/layout/index.js +2 -2
  27. package/dist/portfolio/index.cjs +3 -3
  28. package/dist/portfolio/index.js +1 -1
  29. package/dist/portfolio/server.cjs +13 -5
  30. package/dist/portfolio/server.cjs.map +1 -1
  31. package/dist/portfolio/server.d.cts +1 -0
  32. package/dist/portfolio/server.d.ts +1 -0
  33. package/dist/portfolio/server.js +10 -5
  34. package/dist/portfolio/server.js.map +1 -1
  35. package/dist/revalidate.cjs +98 -0
  36. package/dist/revalidate.cjs.map +1 -0
  37. package/dist/revalidate.d.cts +53 -0
  38. package/dist/revalidate.d.ts +53 -0
  39. package/dist/revalidate.js +93 -0
  40. package/dist/revalidate.js.map +1 -0
  41. package/package.json +7 -1
  42. package/dist/chunk-A4I4IK7V.js.map +0 -1
  43. package/dist/chunk-OMOF4VR5.js.map +0 -1
  44. package/dist/chunk-QBLWP25X.cjs.map +0 -1
  45. package/dist/chunk-XM2QD3AK.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/revalidate.ts"],"names":[],"mappings":"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","file":"revalidate.js","sourcesContent":["/**\n * @sonordev/agency-site-kit/revalidate — On-demand cache revalidation\n *\n * Route handler factory the Sonor Portal calls when portfolio content changes\n * (publish / update / unpublish / delete), so agency sites reflect Portal\n * publishes near-instantly instead of waiting out the ISR window.\n *\n * @example\n * ```ts\n * // app/api/revalidate/route.ts\n * import { createRevalidateHandler } from '@sonordev/agency-site-kit/revalidate'\n *\n * export const POST = createRevalidateHandler()\n * ```\n *\n * Auth accepts the project API key — the same `SONOR_API_KEY` the rest of\n * site-kit uses, sent by the Portal as `Authorization: Bearer` or `x-api-key` —\n * or a legacy `REVALIDATION_SECRET` via body/query. No extra env var needed.\n *\n * The request body matches what the Portal's SiteRevalidationService sends\n * (and is a superset of the legacy /api/seo-revalidate contract):\n * `{ paths?, path?, slug?, slugs?, tags?, tag?, revalidateAll? }`.\n */\n\nimport { PORTFOLIO_CACHE_TAG } from './shared/cache';\n\nexport { PORTFOLIO_CACHE_TAG };\n\nexport interface CreateRevalidateHandlerOptions {\n /**\n * Shared secret the caller must present. Defaults to the site's\n * SONOR_API_KEY / UPTRADE_API_KEY / REVALIDATION_SECRET env vars\n * (any of them is accepted).\n */\n secret?: string;\n /** Route prefix for portfolio detail pages. Defaults to '/work'. */\n basePath?: string;\n /** Tags revalidated when the request names none. Defaults to ['portfolio']. */\n defaultTags?: string[];\n /** Extra literal paths revalidated on every call (e.g. '/'). */\n extraPaths?: string[];\n}\n\ninterface RevalidateRequestBody {\n paths?: unknown;\n path?: unknown;\n slug?: unknown;\n slugs?: unknown;\n tags?: unknown;\n tag?: unknown;\n revalidateAll?: unknown;\n secret?: unknown;\n}\n\nfunction asStringArray(value: unknown): string[] {\n if (typeof value === 'string') return [value];\n if (Array.isArray(value)) {\n return value.filter((v): v is string => typeof v === 'string' && v.length > 0);\n }\n return [];\n}\n\nfunction jsonResponse(status: number, body: Record<string, unknown>): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n\n/**\n * Creates the POST handler for the site's revalidation endpoint.\n *\n * On each authorized call it:\n * 1. Revalidates the requested cache tags (default: PORTFOLIO_CACHE_TAG),\n * which busts every tagged portfolio fetch — list, detail, categories,\n * brand config, and sitemap data — in one shot.\n * 2. Revalidates the requested paths plus `basePath` and `/sitemap.xml`,\n * so the statically rendered routes re-render on the next visit.\n */\nexport function createRevalidateHandler(\n options: CreateRevalidateHandlerOptions = {},\n): (request: Request) => Promise<Response> {\n const basePath = (options.basePath ?? '/work').replace(/\\/+$/, '') || '/work';\n\n return async function POST(request: Request): Promise<Response> {\n let body: RevalidateRequestBody = {};\n try {\n body = (await request.json()) as RevalidateRequestBody;\n } catch {\n // Empty or non-JSON body — fall through to defaults.\n }\n\n // ── Auth ────────────────────────────────────────────────────────────────\n const acceptedSecrets = [\n options.secret,\n process.env.SONOR_API_KEY,\n process.env.UPTRADE_API_KEY,\n process.env.NEXT_PUBLIC_UPTRADE_API_KEY,\n process.env.REVALIDATION_SECRET,\n ].filter((s): s is string => !!s);\n\n if (acceptedSecrets.length === 0) {\n console.error(\n '[@sonordev/agency-site-kit/revalidate] No secret configured — set SONOR_API_KEY or pass { secret } to createRevalidateHandler.',\n );\n return jsonResponse(401, { error: 'Revalidation endpoint not configured' });\n }\n\n const bearer = request.headers.get('authorization')?.replace(/^Bearer\\s+/i, '').trim();\n const url = new URL(request.url);\n const token =\n request.headers.get('x-api-key')?.trim() ||\n bearer ||\n url.searchParams.get('secret') ||\n (typeof body.secret === 'string' ? body.secret : undefined);\n\n if (!token || !acceptedSecrets.includes(token)) {\n return jsonResponse(401, {\n error: 'Unauthorized — use Authorization: Bearer <SONOR_API_KEY>, x-api-key, or secret',\n });\n }\n\n // ── Revalidate ──────────────────────────────────────────────────────────\n try {\n const { revalidatePath, revalidateTag } = await import('next/cache');\n const revalidated: string[] = [];\n\n // Tags first: one revalidateTag busts every tagged portfolio fetch.\n // Deduped — the Portal sends both `tags` and a legacy single `tag`.\n const tags = [...new Set([\n ...asStringArray(body.tags),\n ...asStringArray(body.tag),\n ])];\n const effectiveTags = tags.length > 0 ? tags : (options.defaultTags ?? [PORTFOLIO_CACHE_TAG]);\n for (const tag of effectiveTags) {\n // Next 16 takes a cache profile ('max' = revalidate everywhere);\n // earlier versions ignore the extra argument.\n (revalidateTag as (tag: string, profile?: string) => void)(tag, 'max');\n revalidated.push(`tag:${tag}`);\n }\n\n if (body.revalidateAll === true) {\n revalidatePath('/', 'layout');\n revalidated.push('layout:/');\n }\n\n // Paths: explicit ones, slug shorthands, plus the hub + sitemap always.\n const slugPaths = [...asStringArray(body.slug), ...asStringArray(body.slugs)].map(\n (slug) => `${basePath}/${slug.replace(/^\\/+/, '')}`,\n );\n const paths = new Set<string>([\n basePath,\n '/sitemap.xml',\n ...(options.extraPaths ?? []),\n ...asStringArray(body.paths),\n ...asStringArray(body.path),\n ...slugPaths,\n ]);\n\n for (const p of paths) {\n const normalized = p.startsWith('/') ? p : `/${p}`;\n try {\n revalidatePath(normalized, 'page');\n revalidated.push(normalized);\n } catch (err) {\n console.warn(`[@sonordev/agency-site-kit/revalidate] Failed to revalidate ${normalized}:`, err);\n }\n }\n\n return jsonResponse(200, { revalidated: true, targets: revalidated });\n } catch (err) {\n console.error('[@sonordev/agency-site-kit/revalidate] Revalidation failed:', err);\n return jsonResponse(500, { error: 'Revalidation failed' });\n }\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonordev/agency-site-kit",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Premium portfolio showcase components for Sonor-powered agency websites",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,6 +37,11 @@
37
37
  "types": "./dist/layout/index.d.ts",
38
38
  "import": "./dist/layout/index.js",
39
39
  "require": "./dist/layout/index.cjs"
40
+ },
41
+ "./revalidate": {
42
+ "types": "./dist/revalidate.d.ts",
43
+ "import": "./dist/revalidate.js",
44
+ "require": "./dist/revalidate.cjs"
40
45
  }
41
46
  },
42
47
  "publishConfig": {
@@ -62,6 +67,7 @@
62
67
  },
63
68
  "devDependencies": {
64
69
  "@types/node": "^25.5.0",
70
+ "next": "^16.0.0",
65
71
  "@types/react": "^18.3.0",
66
72
  "@types/react-dom": "^18.3.0",
67
73
  "glob": "^13.0.6",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/shared/api.ts"],"names":[],"mappings":";AAOA,IAAM,eAAA,GAAkB,sBAAA;AACxB,IAAM,kBAAA,GAAqB,IAAA;AAYpB,SAAS,YAAA,GAAiC;AAC/C,EAAA,MAAM,SACJ,OAAA,CAAQ,GAAA,CAAI,aAAA,IACZ,OAAA,CAAQ,IAAI,eAAA,IACZ,EAAA;AAEF,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN;AAAA,KAEF;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GAAA,CACJ,OAAA,CAAQ,GAAA,CAAI,aAAA,IACZ,OAAA,CAAQ,IAAI,yBAAA,IACZ,eAAA,EACA,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAEpB,EAAA,OAAO,EAAE,QAAQ,MAAA,EAAO;AAC1B;AAqBA,eAAsB,QAAA,CACpB,IAAA,EACA,OAAA,GAA2B,EAAC,EACT;AACnB,EAAA,MAAM;AAAA,IACJ,UAAA,GAAa,kBAAA;AAAA,IACb,OAAA,EAAS,eAAe,EAAC;AAAA,IACzB,MAAA,EAAQ,cAAA;AAAA,IACR,MAAA,EAAQ,cAAA;AAAA,IACR,GAAG;AAAA,GACL,GAAI,OAAA;AAEJ,EAAA,IAAI,SAAS,cAAA,IAAkB,EAAA;AAC/B,EAAA,IAAI,SAAS,cAAA,IAAkB,EAAA;AAE/B,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,MAAA,EAAQ;AACtB,IAAA,MAAM,SAAS,YAAA,EAAa;AAC5B,IAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,IAAA,IAAI,CAAC,MAAA,EAAQ,MAAA,GAAS,MAAA,CAAO,MAAA;AAC7B,IAAA,IAAI,CAAC,MAAA,EAAQ,MAAA,GAAS,MAAA,CAAO,MAAA;AAAA,EAC/B;AAEA,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,MAAM,CAAA,EAAG,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,CAAA,CAAA,EAAI,IAAI,CAAA,CAAE,CAAA,CAAA;AAEhE,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,MAChC,GAAG,YAAA;AAAA,MACH,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,WAAA,EAAa,MAAA;AAAA,QACb,GAAG;AAAA,OACL;AAAA,MACA,IAAA,EAAM,EAAE,UAAA;AAAW,KACL,CAAA;AAEhB,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,mDAAmD,QAAA,CAAS,MAAM,IAAI,QAAA,CAAS,UAAU,WAAM,GAAG,CAAA;AAAA,OACpG;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN,qDAAqD,GAAG,CAAA,CAAA,CAAA;AAAA,MACxD,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,KAC3C;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAUA,eAAsB,MAAA,CACpB,IAAA,EACA,UAAA,EACA,MAAA,EACA,MAAA,EACmB;AACnB,EAAA,OAAO,SAAY,IAAA,EAAM;AAAA,IACvB,MAAA,EAAQ,KAAA;AAAA,IACR,UAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AACH","file":"chunk-A4I4IK7V.js","sourcesContent":["/**\n * @sonordev/agency-site-kit — API fetch utilities\n *\n * Server-side only. Reads SONOR_API_KEY and SONOR_API_URL from process.env.\n * Uses Next.js ISR caching via `next: { revalidate }` on every fetch.\n */\n\nconst DEFAULT_API_URL = 'https://api.sonor.io';\nconst DEFAULT_REVALIDATE = 3600; // 1 hour\n\ninterface ApiConfig {\n apiKey: string;\n apiUrl: string;\n}\n\n/**\n * Reads API configuration from environment variables.\n * Falls back to UPTRADE_API_KEY for legacy compatibility.\n * Returns null if no API key is found.\n */\nexport function getApiConfig(): ApiConfig | null {\n const apiKey =\n process.env.SONOR_API_KEY ||\n process.env.UPTRADE_API_KEY ||\n '';\n\n if (!apiKey) {\n console.warn(\n '[@sonordev/agency-site-kit] Missing SONOR_API_KEY environment variable. ' +\n 'Portfolio data will not be fetched.',\n );\n return null;\n }\n\n const apiUrl = (\n process.env.SONOR_API_URL ||\n process.env.NEXT_PUBLIC_SONOR_API_URL ||\n DEFAULT_API_URL\n ).replace(/\\/+$/, '');\n\n return { apiKey, apiUrl };\n}\n\ninterface ApiFetchOptions extends Omit<RequestInit, 'headers'> {\n /** ISR revalidation interval in seconds. Defaults to 3600. Set to 0 for no caching. */\n revalidate?: number;\n /** Additional headers to merge with defaults */\n headers?: Record<string, string>;\n /** Override API key (e.g. passed from layout props) */\n apiKey?: string;\n /** Override API URL */\n apiUrl?: string;\n}\n\n/**\n * Fetch wrapper for the public portfolio API.\n *\n * - Injects `x-api-key` header automatically\n * - Parses JSON responses\n * - Uses Next.js ISR caching via `next.revalidate`\n * - Returns null on failure instead of throwing\n */\nexport async function apiFetch<T>(\n path: string,\n options: ApiFetchOptions = {},\n): Promise<T | null> {\n const {\n revalidate = DEFAULT_REVALIDATE,\n headers: extraHeaders = {},\n apiKey: overrideApiKey,\n apiUrl: overrideApiUrl,\n ...fetchOptions\n } = options;\n\n let apiKey = overrideApiKey || '';\n let apiUrl = overrideApiUrl || '';\n\n if (!apiKey || !apiUrl) {\n const config = getApiConfig();\n if (!config) return null;\n if (!apiKey) apiKey = config.apiKey;\n if (!apiUrl) apiUrl = config.apiUrl;\n }\n\n const url = `${apiUrl}${path.startsWith('/') ? path : `/${path}`}`;\n\n try {\n const response = await fetch(url, {\n ...fetchOptions,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n ...extraHeaders,\n },\n next: { revalidate },\n } as RequestInit);\n\n if (!response.ok) {\n console.warn(\n `[@sonordev/agency-site-kit] API request failed: ${response.status} ${response.statusText} — ${url}`,\n );\n return null;\n }\n\n const data = await response.json();\n return data as T;\n } catch (error) {\n console.warn(\n `[@sonordev/agency-site-kit] API request error for ${url}:`,\n error instanceof Error ? error.message : error,\n );\n return null;\n }\n}\n\n/**\n * GET shorthand for apiFetch.\n *\n * @param path API path (e.g. \"/api/public/portfolio/items\")\n * @param revalidate ISR revalidation in seconds (default 3600)\n * @param apiKey Optional override API key\n * @param apiUrl Optional override API URL\n */\nexport async function apiGet<T>(\n path: string,\n revalidate?: number,\n apiKey?: string,\n apiUrl?: string,\n): Promise<T | null> {\n return apiFetch<T>(path, {\n method: 'GET',\n revalidate,\n apiKey,\n apiUrl,\n });\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/shared/brand.ts","../src/layout/AgencySiteKitLayout.tsx"],"names":[],"mappings":"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","file":"chunk-OMOF4VR5.js","sourcesContent":["/**\n * @sonordev/agency-site-kit — Brand customization system\n *\n * Generates CSS custom properties from a BrandConfig object.\n * Supports light/dark mode with automatic fallbacks.\n */\n\nimport type { BrandConfig } from '../types';\n\n// ---------------------------------------------------------------------------\n// Hex to RGB helper\n// ---------------------------------------------------------------------------\n\n/**\n * Converts a hex color string to an \"R, G, B\" string for use in rgb()/rgba().\n * Supports 3-digit (#abc) and 6-digit (#aabbcc) hex values.\n * Returns null for invalid inputs.\n */\nexport function hexToRgb(hex: string): string | null {\n if (!hex || typeof hex !== 'string') return null;\n\n const cleaned = hex.replace(/^#/, '');\n\n let r: number;\n let g: number;\n let b: number;\n\n if (cleaned.length === 3) {\n r = parseInt(cleaned[0] + cleaned[0], 16);\n g = parseInt(cleaned[1] + cleaned[1], 16);\n b = parseInt(cleaned[2] + cleaned[2], 16);\n } else if (cleaned.length === 6) {\n r = parseInt(cleaned.substring(0, 2), 16);\n g = parseInt(cleaned.substring(2, 4), 16);\n b = parseInt(cleaned.substring(4, 6), 16);\n } else {\n return null;\n }\n\n if (isNaN(r) || isNaN(g) || isNaN(b)) return null;\n\n return `${r}, ${g}, ${b}`;\n}\n\n// ---------------------------------------------------------------------------\n// Default theme values\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_BRAND_CONFIG: BrandConfig = {\n primary: '#6366f1',\n secondary: '#8b5cf6',\n background: '#ffffff',\n backgroundElevated: '#f8fafc',\n surface: '#f1f5f9',\n surfaceHover: '#e2e8f0',\n surfaceBorder: '#e2e8f0',\n textPrimary: '#0f172a',\n textSecondary: '#475569',\n textTertiary: '#94a3b8',\n fontHeading: \"'Inter', system-ui, -apple-system, sans-serif\",\n fontBody: \"'Inter', system-ui, -apple-system, sans-serif\",\n radius: {\n sm: '0.375rem',\n md: '0.5rem',\n lg: '0.75rem',\n },\n};\n\nexport const DEFAULT_DARK_MODE: Partial<BrandConfig> = {\n background: '#1a1a1f',\n backgroundElevated: '#222228',\n surface: '#2a2a30',\n surfaceHover: '#333338',\n surfaceBorder: '#334155',\n textPrimary: '#f8fafc',\n textSecondary: '#cbd5e1',\n textTertiary: '#64748b',\n};\n\n// ---------------------------------------------------------------------------\n// CSS variable generation\n// ---------------------------------------------------------------------------\n\nfunction buildVariables(config: BrandConfig): string {\n const primaryRgb = hexToRgb(config.primary) || '99, 102, 241';\n const secondaryRgb = hexToRgb(config.secondary) || '139, 92, 246';\n\n const radius = config.radius || DEFAULT_BRAND_CONFIG.radius!;\n\n return [\n // Primary tokens (--sk-*)\n `--sk-primary: ${config.primary};`,\n `--sk-primary-rgb: ${primaryRgb};`,\n `--sk-secondary: ${config.secondary};`,\n `--sk-secondary-rgb: ${secondaryRgb};`,\n `--sk-bg: ${config.background};`,\n `--sk-bg-elevated: ${config.backgroundElevated};`,\n `--sk-surface: ${config.surface};`,\n `--sk-surface-hover: ${config.surfaceHover};`,\n `--sk-border: ${config.surfaceBorder};`,\n `--sk-text-primary: ${config.textPrimary};`,\n `--sk-text-secondary: ${config.textSecondary};`,\n `--sk-text-tertiary: ${config.textTertiary};`,\n `--sk-radius-sm: ${radius.sm};`,\n `--sk-radius: ${radius.md};`,\n `--sk-radius-lg: ${radius.lg};`,\n `--sk-font-heading: ${config.fontHeading};`,\n `--sk-font: ${config.fontBody};`,\n ].join('\\n ');\n}\n\nfunction buildDarkVariables(base: BrandConfig): string {\n const dark = {\n ...DEFAULT_DARK_MODE,\n ...base.darkMode,\n // Primary/secondary carry over unless explicitly overridden\n primary: base.darkMode?.primary || base.primary,\n secondary: base.darkMode?.secondary || base.secondary,\n };\n\n const primaryRgb = hexToRgb(dark.primary!) || hexToRgb(base.primary) || '99, 102, 241';\n const secondaryRgb = hexToRgb(dark.secondary!) || hexToRgb(base.secondary) || '139, 92, 246';\n\n return [\n // Primary tokens (--sk-*)\n `--sk-primary: ${dark.primary};`,\n `--sk-primary-rgb: ${primaryRgb};`,\n `--sk-secondary: ${dark.secondary};`,\n `--sk-secondary-rgb: ${secondaryRgb};`,\n `--sk-bg: ${dark.background || base.background || DEFAULT_DARK_MODE.background};`,\n `--sk-bg-elevated: ${dark.backgroundElevated || base.backgroundElevated || DEFAULT_DARK_MODE.backgroundElevated};`,\n `--sk-surface: ${dark.surface || base.surface || DEFAULT_DARK_MODE.surface};`,\n `--sk-surface-hover: ${dark.surfaceHover || base.surfaceHover || DEFAULT_DARK_MODE.surfaceHover};`,\n `--sk-border: ${dark.surfaceBorder || base.surfaceBorder || DEFAULT_DARK_MODE.surfaceBorder};`,\n `--sk-text-primary: ${dark.textPrimary || base.textPrimary || DEFAULT_DARK_MODE.textPrimary};`,\n `--sk-text-secondary: ${dark.textSecondary || base.textSecondary || DEFAULT_DARK_MODE.textSecondary};`,\n `--sk-text-tertiary: ${dark.textTertiary || base.textTertiary || DEFAULT_DARK_MODE.textTertiary};`,\n ].join('\\n ');\n}\n\n/**\n * Generates a complete CSS string with all --sk-* custom properties.\n *\n * Produces:\n * - `:root` / `[data-theme=\"light\"]` block with light mode values\n * - `[data-theme=\"dark\"]` block with dark mode values\n * - `@media (prefers-color-scheme: dark)` fallback for auto detection\n *\n * Usage: inject the returned string into a `<style>` tag.\n */\nexport function generateBrandCSS(config: BrandConfig): string {\n const merged: BrandConfig = {\n ...DEFAULT_BRAND_CONFIG,\n ...config,\n radius: {\n ...DEFAULT_BRAND_CONFIG.radius!,\n ...config.radius,\n },\n };\n\n const lightVars = buildVariables(merged);\n const darkVars = buildDarkVariables(merged);\n\n return `\n:root, [data-theme=\"light\"] {\n ${lightVars}\n}\n\n[data-theme=\"dark\"] {\n ${darkVars}\n}\n\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n ${darkVars}\n }\n}\n`.trim();\n}\n\n/**\n * Merges a partial brand config with defaults, returning a complete BrandConfig.\n */\nexport function mergeBrandConfig(partial: Partial<BrandConfig>): BrandConfig {\n return {\n ...DEFAULT_BRAND_CONFIG,\n ...partial,\n radius: {\n ...DEFAULT_BRAND_CONFIG.radius!,\n ...partial.radius,\n },\n };\n}\n","/**\n * @sonordev/agency-site-kit — AgencySiteKitLayout\n *\n * Server component that wraps children with brand CSS variables.\n * Fetches the brand configuration from the Portal API and injects\n * a <style> tag with all --sk-* custom properties (plus --agency-* aliases for backwards compatibility).\n *\n * Usage in app/layout.tsx:\n *\n * ```tsx\n * import { AgencySiteKitLayout } from '@sonordev/agency-site-kit';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html lang=\"en\">\n * <body>\n * <AgencySiteKitLayout>\n * {children}\n * </AgencySiteKitLayout>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\n\nimport React from 'react';\nimport { apiGet } from '../shared/api';\nimport { generateBrandCSS, mergeBrandConfig } from '../shared/brand';\nimport type { BrandConfig, PortfolioConfigResponse } from '../types';\n\nfunction portalApiOrigin(apiUrl: string): string {\n try {\n return new URL(apiUrl.replace(/\\/$/, '')).origin\n } catch {\n return 'https://api.sonor.io'\n }\n}\n\nexport interface AgencySiteKitLayoutProps {\n children: React.ReactNode;\n /** Override the API key (defaults to process.env.SONOR_API_KEY) */\n apiKey?: string;\n /** Override the API URL (defaults to process.env.SONOR_API_URL or https://api.sonor.io) */\n apiUrl?: string;\n /** Override brand config instead of fetching from API */\n brandConfig?: Partial<BrandConfig>;\n /** Force a specific theme: \"light\" | \"dark\". Omit for system preference. */\n theme?: 'light' | 'dark';\n /** Additional class name for the wrapper div */\n className?: string;\n}\n\n/**\n * Fetches brand configuration from the Portal API.\n * Returns a merged BrandConfig with defaults applied.\n * Falls back to defaults if the API call fails.\n */\nasync function fetchBrandConfig(\n apiKey?: string,\n apiUrl?: string,\n): Promise<BrandConfig> {\n const response = await apiGet<PortfolioConfigResponse>(\n '/api/public/portfolio/config',\n 3600,\n apiKey,\n apiUrl,\n );\n\n if (response?.brand) {\n return mergeBrandConfig(response.brand);\n }\n\n return mergeBrandConfig({});\n}\n\n/**\n * AgencySiteKitLayout — Server component wrapper\n *\n * Fetches the agency's brand configuration and injects CSS custom properties\n * so all child components can use --sk-* variables for theming.\n *\n * This is an async server component (RSC). It makes one API call at render\n * time, cached via Next.js ISR (default 1 hour revalidation).\n */\nexport async function AgencySiteKitLayout({\n children,\n apiKey,\n apiUrl,\n brandConfig: brandConfigOverride,\n theme,\n className,\n}: AgencySiteKitLayoutProps): Promise<React.ReactElement> {\n // Use override brand config if provided, otherwise fetch from API\n const brandConfig = brandConfigOverride\n ? mergeBrandConfig(brandConfigOverride)\n : await fetchBrandConfig(apiKey, apiUrl);\n\n const cssString = generateBrandCSS(brandConfig);\n\n const resolvedApiUrl =\n apiUrl ??\n process.env.SONOR_API_URL ??\n process.env.UPTRADE_API_URL ??\n 'https://api.sonor.io';\n const preconnectHref = portalApiOrigin(resolvedApiUrl);\n\n return React.createElement(\n React.Fragment,\n null,\n React.createElement('link', {\n rel: 'preconnect',\n href: preconnectHref,\n crossOrigin: 'anonymous',\n }),\n React.createElement('link', {\n rel: 'dns-prefetch',\n href: preconnectHref,\n }),\n React.createElement('style', {\n dangerouslySetInnerHTML: { __html: cssString },\n 'data-agency-site-kit': 'brand',\n }),\n React.createElement(\n 'div',\n {\n 'data-theme': theme || undefined,\n 'data-agency-site-kit': 'root',\n className,\n style: {\n fontFamily: 'var(--sk-font)',\n color: 'var(--sk-text-primary)',\n backgroundColor: 'var(--sk-bg)',\n },\n },\n children,\n ),\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/shared/api.ts"],"names":[],"mappings":";;;AAOA,IAAM,eAAA,GAAkB,sBAAA;AACxB,IAAM,kBAAA,GAAqB,IAAA;AAYpB,SAAS,YAAA,GAAiC;AAC/C,EAAA,MAAM,SACJ,OAAA,CAAQ,GAAA,CAAI,aAAA,IACZ,OAAA,CAAQ,IAAI,eAAA,IACZ,EAAA;AAEF,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN;AAAA,KAEF;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GAAA,CACJ,OAAA,CAAQ,GAAA,CAAI,aAAA,IACZ,OAAA,CAAQ,IAAI,yBAAA,IACZ,eAAA,EACA,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAEpB,EAAA,OAAO,EAAE,QAAQ,MAAA,EAAO;AAC1B;AAqBA,eAAsB,QAAA,CACpB,IAAA,EACA,OAAA,GAA2B,EAAC,EACT;AACnB,EAAA,MAAM;AAAA,IACJ,UAAA,GAAa,kBAAA;AAAA,IACb,OAAA,EAAS,eAAe,EAAC;AAAA,IACzB,MAAA,EAAQ,cAAA;AAAA,IACR,MAAA,EAAQ,cAAA;AAAA,IACR,GAAG;AAAA,GACL,GAAI,OAAA;AAEJ,EAAA,IAAI,SAAS,cAAA,IAAkB,EAAA;AAC/B,EAAA,IAAI,SAAS,cAAA,IAAkB,EAAA;AAE/B,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,MAAA,EAAQ;AACtB,IAAA,MAAM,SAAS,YAAA,EAAa;AAC5B,IAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,IAAA,IAAI,CAAC,MAAA,EAAQ,MAAA,GAAS,MAAA,CAAO,MAAA;AAC7B,IAAA,IAAI,CAAC,MAAA,EAAQ,MAAA,GAAS,MAAA,CAAO,MAAA;AAAA,EAC/B;AAEA,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,MAAM,CAAA,EAAG,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,CAAA,CAAA,EAAI,IAAI,CAAA,CAAE,CAAA,CAAA;AAEhE,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,MAChC,GAAG,YAAA;AAAA,MACH,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,WAAA,EAAa,MAAA;AAAA,QACb,GAAG;AAAA,OACL;AAAA,MACA,IAAA,EAAM,EAAE,UAAA;AAAW,KACL,CAAA;AAEhB,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,mDAAmD,QAAA,CAAS,MAAM,IAAI,QAAA,CAAS,UAAU,WAAM,GAAG,CAAA;AAAA,OACpG;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN,qDAAqD,GAAG,CAAA,CAAA,CAAA;AAAA,MACxD,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,KAC3C;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAUA,eAAsB,MAAA,CACpB,IAAA,EACA,UAAA,EACA,MAAA,EACA,MAAA,EACmB;AACnB,EAAA,OAAO,SAAY,IAAA,EAAM;AAAA,IACvB,MAAA,EAAQ,KAAA;AAAA,IACR,UAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AACH","file":"chunk-QBLWP25X.cjs","sourcesContent":["/**\n * @sonordev/agency-site-kit — API fetch utilities\n *\n * Server-side only. Reads SONOR_API_KEY and SONOR_API_URL from process.env.\n * Uses Next.js ISR caching via `next: { revalidate }` on every fetch.\n */\n\nconst DEFAULT_API_URL = 'https://api.sonor.io';\nconst DEFAULT_REVALIDATE = 3600; // 1 hour\n\ninterface ApiConfig {\n apiKey: string;\n apiUrl: string;\n}\n\n/**\n * Reads API configuration from environment variables.\n * Falls back to UPTRADE_API_KEY for legacy compatibility.\n * Returns null if no API key is found.\n */\nexport function getApiConfig(): ApiConfig | null {\n const apiKey =\n process.env.SONOR_API_KEY ||\n process.env.UPTRADE_API_KEY ||\n '';\n\n if (!apiKey) {\n console.warn(\n '[@sonordev/agency-site-kit] Missing SONOR_API_KEY environment variable. ' +\n 'Portfolio data will not be fetched.',\n );\n return null;\n }\n\n const apiUrl = (\n process.env.SONOR_API_URL ||\n process.env.NEXT_PUBLIC_SONOR_API_URL ||\n DEFAULT_API_URL\n ).replace(/\\/+$/, '');\n\n return { apiKey, apiUrl };\n}\n\ninterface ApiFetchOptions extends Omit<RequestInit, 'headers'> {\n /** ISR revalidation interval in seconds. Defaults to 3600. Set to 0 for no caching. */\n revalidate?: number;\n /** Additional headers to merge with defaults */\n headers?: Record<string, string>;\n /** Override API key (e.g. passed from layout props) */\n apiKey?: string;\n /** Override API URL */\n apiUrl?: string;\n}\n\n/**\n * Fetch wrapper for the public portfolio API.\n *\n * - Injects `x-api-key` header automatically\n * - Parses JSON responses\n * - Uses Next.js ISR caching via `next.revalidate`\n * - Returns null on failure instead of throwing\n */\nexport async function apiFetch<T>(\n path: string,\n options: ApiFetchOptions = {},\n): Promise<T | null> {\n const {\n revalidate = DEFAULT_REVALIDATE,\n headers: extraHeaders = {},\n apiKey: overrideApiKey,\n apiUrl: overrideApiUrl,\n ...fetchOptions\n } = options;\n\n let apiKey = overrideApiKey || '';\n let apiUrl = overrideApiUrl || '';\n\n if (!apiKey || !apiUrl) {\n const config = getApiConfig();\n if (!config) return null;\n if (!apiKey) apiKey = config.apiKey;\n if (!apiUrl) apiUrl = config.apiUrl;\n }\n\n const url = `${apiUrl}${path.startsWith('/') ? path : `/${path}`}`;\n\n try {\n const response = await fetch(url, {\n ...fetchOptions,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n ...extraHeaders,\n },\n next: { revalidate },\n } as RequestInit);\n\n if (!response.ok) {\n console.warn(\n `[@sonordev/agency-site-kit] API request failed: ${response.status} ${response.statusText} — ${url}`,\n );\n return null;\n }\n\n const data = await response.json();\n return data as T;\n } catch (error) {\n console.warn(\n `[@sonordev/agency-site-kit] API request error for ${url}:`,\n error instanceof Error ? error.message : error,\n );\n return null;\n }\n}\n\n/**\n * GET shorthand for apiFetch.\n *\n * @param path API path (e.g. \"/api/public/portfolio/items\")\n * @param revalidate ISR revalidation in seconds (default 3600)\n * @param apiKey Optional override API key\n * @param apiUrl Optional override API URL\n */\nexport async function apiGet<T>(\n path: string,\n revalidate?: number,\n apiKey?: string,\n apiUrl?: string,\n): Promise<T | null> {\n return apiFetch<T>(path, {\n method: 'GET',\n revalidate,\n apiKey,\n apiUrl,\n });\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/shared/brand.ts","../src/layout/AgencySiteKitLayout.tsx"],"names":["apiGet","React"],"mappings":"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","file":"chunk-XM2QD3AK.cjs","sourcesContent":["/**\n * @sonordev/agency-site-kit — Brand customization system\n *\n * Generates CSS custom properties from a BrandConfig object.\n * Supports light/dark mode with automatic fallbacks.\n */\n\nimport type { BrandConfig } from '../types';\n\n// ---------------------------------------------------------------------------\n// Hex to RGB helper\n// ---------------------------------------------------------------------------\n\n/**\n * Converts a hex color string to an \"R, G, B\" string for use in rgb()/rgba().\n * Supports 3-digit (#abc) and 6-digit (#aabbcc) hex values.\n * Returns null for invalid inputs.\n */\nexport function hexToRgb(hex: string): string | null {\n if (!hex || typeof hex !== 'string') return null;\n\n const cleaned = hex.replace(/^#/, '');\n\n let r: number;\n let g: number;\n let b: number;\n\n if (cleaned.length === 3) {\n r = parseInt(cleaned[0] + cleaned[0], 16);\n g = parseInt(cleaned[1] + cleaned[1], 16);\n b = parseInt(cleaned[2] + cleaned[2], 16);\n } else if (cleaned.length === 6) {\n r = parseInt(cleaned.substring(0, 2), 16);\n g = parseInt(cleaned.substring(2, 4), 16);\n b = parseInt(cleaned.substring(4, 6), 16);\n } else {\n return null;\n }\n\n if (isNaN(r) || isNaN(g) || isNaN(b)) return null;\n\n return `${r}, ${g}, ${b}`;\n}\n\n// ---------------------------------------------------------------------------\n// Default theme values\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_BRAND_CONFIG: BrandConfig = {\n primary: '#6366f1',\n secondary: '#8b5cf6',\n background: '#ffffff',\n backgroundElevated: '#f8fafc',\n surface: '#f1f5f9',\n surfaceHover: '#e2e8f0',\n surfaceBorder: '#e2e8f0',\n textPrimary: '#0f172a',\n textSecondary: '#475569',\n textTertiary: '#94a3b8',\n fontHeading: \"'Inter', system-ui, -apple-system, sans-serif\",\n fontBody: \"'Inter', system-ui, -apple-system, sans-serif\",\n radius: {\n sm: '0.375rem',\n md: '0.5rem',\n lg: '0.75rem',\n },\n};\n\nexport const DEFAULT_DARK_MODE: Partial<BrandConfig> = {\n background: '#1a1a1f',\n backgroundElevated: '#222228',\n surface: '#2a2a30',\n surfaceHover: '#333338',\n surfaceBorder: '#334155',\n textPrimary: '#f8fafc',\n textSecondary: '#cbd5e1',\n textTertiary: '#64748b',\n};\n\n// ---------------------------------------------------------------------------\n// CSS variable generation\n// ---------------------------------------------------------------------------\n\nfunction buildVariables(config: BrandConfig): string {\n const primaryRgb = hexToRgb(config.primary) || '99, 102, 241';\n const secondaryRgb = hexToRgb(config.secondary) || '139, 92, 246';\n\n const radius = config.radius || DEFAULT_BRAND_CONFIG.radius!;\n\n return [\n // Primary tokens (--sk-*)\n `--sk-primary: ${config.primary};`,\n `--sk-primary-rgb: ${primaryRgb};`,\n `--sk-secondary: ${config.secondary};`,\n `--sk-secondary-rgb: ${secondaryRgb};`,\n `--sk-bg: ${config.background};`,\n `--sk-bg-elevated: ${config.backgroundElevated};`,\n `--sk-surface: ${config.surface};`,\n `--sk-surface-hover: ${config.surfaceHover};`,\n `--sk-border: ${config.surfaceBorder};`,\n `--sk-text-primary: ${config.textPrimary};`,\n `--sk-text-secondary: ${config.textSecondary};`,\n `--sk-text-tertiary: ${config.textTertiary};`,\n `--sk-radius-sm: ${radius.sm};`,\n `--sk-radius: ${radius.md};`,\n `--sk-radius-lg: ${radius.lg};`,\n `--sk-font-heading: ${config.fontHeading};`,\n `--sk-font: ${config.fontBody};`,\n ].join('\\n ');\n}\n\nfunction buildDarkVariables(base: BrandConfig): string {\n const dark = {\n ...DEFAULT_DARK_MODE,\n ...base.darkMode,\n // Primary/secondary carry over unless explicitly overridden\n primary: base.darkMode?.primary || base.primary,\n secondary: base.darkMode?.secondary || base.secondary,\n };\n\n const primaryRgb = hexToRgb(dark.primary!) || hexToRgb(base.primary) || '99, 102, 241';\n const secondaryRgb = hexToRgb(dark.secondary!) || hexToRgb(base.secondary) || '139, 92, 246';\n\n return [\n // Primary tokens (--sk-*)\n `--sk-primary: ${dark.primary};`,\n `--sk-primary-rgb: ${primaryRgb};`,\n `--sk-secondary: ${dark.secondary};`,\n `--sk-secondary-rgb: ${secondaryRgb};`,\n `--sk-bg: ${dark.background || base.background || DEFAULT_DARK_MODE.background};`,\n `--sk-bg-elevated: ${dark.backgroundElevated || base.backgroundElevated || DEFAULT_DARK_MODE.backgroundElevated};`,\n `--sk-surface: ${dark.surface || base.surface || DEFAULT_DARK_MODE.surface};`,\n `--sk-surface-hover: ${dark.surfaceHover || base.surfaceHover || DEFAULT_DARK_MODE.surfaceHover};`,\n `--sk-border: ${dark.surfaceBorder || base.surfaceBorder || DEFAULT_DARK_MODE.surfaceBorder};`,\n `--sk-text-primary: ${dark.textPrimary || base.textPrimary || DEFAULT_DARK_MODE.textPrimary};`,\n `--sk-text-secondary: ${dark.textSecondary || base.textSecondary || DEFAULT_DARK_MODE.textSecondary};`,\n `--sk-text-tertiary: ${dark.textTertiary || base.textTertiary || DEFAULT_DARK_MODE.textTertiary};`,\n ].join('\\n ');\n}\n\n/**\n * Generates a complete CSS string with all --sk-* custom properties.\n *\n * Produces:\n * - `:root` / `[data-theme=\"light\"]` block with light mode values\n * - `[data-theme=\"dark\"]` block with dark mode values\n * - `@media (prefers-color-scheme: dark)` fallback for auto detection\n *\n * Usage: inject the returned string into a `<style>` tag.\n */\nexport function generateBrandCSS(config: BrandConfig): string {\n const merged: BrandConfig = {\n ...DEFAULT_BRAND_CONFIG,\n ...config,\n radius: {\n ...DEFAULT_BRAND_CONFIG.radius!,\n ...config.radius,\n },\n };\n\n const lightVars = buildVariables(merged);\n const darkVars = buildDarkVariables(merged);\n\n return `\n:root, [data-theme=\"light\"] {\n ${lightVars}\n}\n\n[data-theme=\"dark\"] {\n ${darkVars}\n}\n\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n ${darkVars}\n }\n}\n`.trim();\n}\n\n/**\n * Merges a partial brand config with defaults, returning a complete BrandConfig.\n */\nexport function mergeBrandConfig(partial: Partial<BrandConfig>): BrandConfig {\n return {\n ...DEFAULT_BRAND_CONFIG,\n ...partial,\n radius: {\n ...DEFAULT_BRAND_CONFIG.radius!,\n ...partial.radius,\n },\n };\n}\n","/**\n * @sonordev/agency-site-kit — AgencySiteKitLayout\n *\n * Server component that wraps children with brand CSS variables.\n * Fetches the brand configuration from the Portal API and injects\n * a <style> tag with all --sk-* custom properties (plus --agency-* aliases for backwards compatibility).\n *\n * Usage in app/layout.tsx:\n *\n * ```tsx\n * import { AgencySiteKitLayout } from '@sonordev/agency-site-kit';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html lang=\"en\">\n * <body>\n * <AgencySiteKitLayout>\n * {children}\n * </AgencySiteKitLayout>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\n\nimport React from 'react';\nimport { apiGet } from '../shared/api';\nimport { generateBrandCSS, mergeBrandConfig } from '../shared/brand';\nimport type { BrandConfig, PortfolioConfigResponse } from '../types';\n\nfunction portalApiOrigin(apiUrl: string): string {\n try {\n return new URL(apiUrl.replace(/\\/$/, '')).origin\n } catch {\n return 'https://api.sonor.io'\n }\n}\n\nexport interface AgencySiteKitLayoutProps {\n children: React.ReactNode;\n /** Override the API key (defaults to process.env.SONOR_API_KEY) */\n apiKey?: string;\n /** Override the API URL (defaults to process.env.SONOR_API_URL or https://api.sonor.io) */\n apiUrl?: string;\n /** Override brand config instead of fetching from API */\n brandConfig?: Partial<BrandConfig>;\n /** Force a specific theme: \"light\" | \"dark\". Omit for system preference. */\n theme?: 'light' | 'dark';\n /** Additional class name for the wrapper div */\n className?: string;\n}\n\n/**\n * Fetches brand configuration from the Portal API.\n * Returns a merged BrandConfig with defaults applied.\n * Falls back to defaults if the API call fails.\n */\nasync function fetchBrandConfig(\n apiKey?: string,\n apiUrl?: string,\n): Promise<BrandConfig> {\n const response = await apiGet<PortfolioConfigResponse>(\n '/api/public/portfolio/config',\n 3600,\n apiKey,\n apiUrl,\n );\n\n if (response?.brand) {\n return mergeBrandConfig(response.brand);\n }\n\n return mergeBrandConfig({});\n}\n\n/**\n * AgencySiteKitLayout — Server component wrapper\n *\n * Fetches the agency's brand configuration and injects CSS custom properties\n * so all child components can use --sk-* variables for theming.\n *\n * This is an async server component (RSC). It makes one API call at render\n * time, cached via Next.js ISR (default 1 hour revalidation).\n */\nexport async function AgencySiteKitLayout({\n children,\n apiKey,\n apiUrl,\n brandConfig: brandConfigOverride,\n theme,\n className,\n}: AgencySiteKitLayoutProps): Promise<React.ReactElement> {\n // Use override brand config if provided, otherwise fetch from API\n const brandConfig = brandConfigOverride\n ? mergeBrandConfig(brandConfigOverride)\n : await fetchBrandConfig(apiKey, apiUrl);\n\n const cssString = generateBrandCSS(brandConfig);\n\n const resolvedApiUrl =\n apiUrl ??\n process.env.SONOR_API_URL ??\n process.env.UPTRADE_API_URL ??\n 'https://api.sonor.io';\n const preconnectHref = portalApiOrigin(resolvedApiUrl);\n\n return React.createElement(\n React.Fragment,\n null,\n React.createElement('link', {\n rel: 'preconnect',\n href: preconnectHref,\n crossOrigin: 'anonymous',\n }),\n React.createElement('link', {\n rel: 'dns-prefetch',\n href: preconnectHref,\n }),\n React.createElement('style', {\n dangerouslySetInnerHTML: { __html: cssString },\n 'data-agency-site-kit': 'brand',\n }),\n React.createElement(\n 'div',\n {\n 'data-theme': theme || undefined,\n 'data-agency-site-kit': 'root',\n className,\n style: {\n fontFamily: 'var(--sk-font)',\n color: 'var(--sk-text-primary)',\n backgroundColor: 'var(--sk-bg)',\n },\n },\n children,\n ),\n );\n}\n"]}