@sonordev/agency-site-kit 0.1.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 (131) hide show
  1. package/dist/BeforeAfterSection-6QUJOBO2.js +176 -0
  2. package/dist/BeforeAfterSection-6QUJOBO2.js.map +1 -0
  3. package/dist/BeforeAfterSection-DVAWWE4K.cjs +181 -0
  4. package/dist/BeforeAfterSection-DVAWWE4K.cjs.map +1 -0
  5. package/dist/CTASection-4JKLXEUF.cjs +111 -0
  6. package/dist/CTASection-4JKLXEUF.cjs.map +1 -0
  7. package/dist/CTASection-BJA72XIL.js +106 -0
  8. package/dist/CTASection-BJA72XIL.js.map +1 -0
  9. package/dist/ChallengesSection-GEQGVSJN.js +180 -0
  10. package/dist/ChallengesSection-GEQGVSJN.js.map +1 -0
  11. package/dist/ChallengesSection-IZ3DHECS.cjs +182 -0
  12. package/dist/ChallengesSection-IZ3DHECS.cjs.map +1 -0
  13. package/dist/ConversionFunnelSection-AUUSJ5HQ.cjs +209 -0
  14. package/dist/ConversionFunnelSection-AUUSJ5HQ.cjs.map +1 -0
  15. package/dist/ConversionFunnelSection-D3GE4NKE.js +203 -0
  16. package/dist/ConversionFunnelSection-D3GE4NKE.js.map +1 -0
  17. package/dist/DetailsSection-FB763FS7.js +135 -0
  18. package/dist/DetailsSection-FB763FS7.js.map +1 -0
  19. package/dist/DetailsSection-OACJFGH7.cjs +137 -0
  20. package/dist/DetailsSection-OACJFGH7.cjs.map +1 -0
  21. package/dist/FeatureSpotlightSection-B7P3JGNL.js +205 -0
  22. package/dist/FeatureSpotlightSection-B7P3JGNL.js.map +1 -0
  23. package/dist/FeatureSpotlightSection-WRHXS7TU.cjs +210 -0
  24. package/dist/FeatureSpotlightSection-WRHXS7TU.cjs.map +1 -0
  25. package/dist/GallerySection-VMKORC47.js +218 -0
  26. package/dist/GallerySection-VMKORC47.js.map +1 -0
  27. package/dist/GallerySection-WJ4PQDBI.cjs +219 -0
  28. package/dist/GallerySection-WJ4PQDBI.cjs.map +1 -0
  29. package/dist/MetricsTimelineSection-4L6DUHJ5.cjs +258 -0
  30. package/dist/MetricsTimelineSection-4L6DUHJ5.cjs.map +1 -0
  31. package/dist/MetricsTimelineSection-6BT5GNFV.js +253 -0
  32. package/dist/MetricsTimelineSection-6BT5GNFV.js.map +1 -0
  33. package/dist/ResultsSection-DFUJ5U6M.js +93 -0
  34. package/dist/ResultsSection-DFUJ5U6M.js.map +1 -0
  35. package/dist/ResultsSection-XLGMMQKY.cjs +95 -0
  36. package/dist/ResultsSection-XLGMMQKY.cjs.map +1 -0
  37. package/dist/ServicesSection-D5V3Q4GR.js +118 -0
  38. package/dist/ServicesSection-D5V3Q4GR.js.map +1 -0
  39. package/dist/ServicesSection-WJMGK2MF.cjs +120 -0
  40. package/dist/ServicesSection-WJMGK2MF.cjs.map +1 -0
  41. package/dist/StrategySection-3ED3QW4R.cjs +180 -0
  42. package/dist/StrategySection-3ED3QW4R.cjs.map +1 -0
  43. package/dist/StrategySection-VUWMIYYP.js +175 -0
  44. package/dist/StrategySection-VUWMIYYP.js.map +1 -0
  45. package/dist/TeamSection-DZVSNZE6.cjs +112 -0
  46. package/dist/TeamSection-DZVSNZE6.cjs.map +1 -0
  47. package/dist/TeamSection-HGKFW6PQ.js +107 -0
  48. package/dist/TeamSection-HGKFW6PQ.js.map +1 -0
  49. package/dist/TechStackSection-OCUYG4XT.js +90 -0
  50. package/dist/TechStackSection-OCUYG4XT.js.map +1 -0
  51. package/dist/TechStackSection-VKJK4KQB.cjs +91 -0
  52. package/dist/TechStackSection-VKJK4KQB.cjs.map +1 -0
  53. package/dist/TestimonialSection-6RGSMXQB.js +122 -0
  54. package/dist/TestimonialSection-6RGSMXQB.js.map +1 -0
  55. package/dist/TestimonialSection-XPTFUQIN.cjs +124 -0
  56. package/dist/TestimonialSection-XPTFUQIN.cjs.map +1 -0
  57. package/dist/VideoSection-4A2HC6K6.js +117 -0
  58. package/dist/VideoSection-4A2HC6K6.js.map +1 -0
  59. package/dist/VideoSection-G3DFS7UH.cjs +118 -0
  60. package/dist/VideoSection-G3DFS7UH.cjs.map +1 -0
  61. package/dist/chunk-2VNNFAG6.js +415 -0
  62. package/dist/chunk-2VNNFAG6.js.map +1 -0
  63. package/dist/chunk-2Y4O3LWM.js +53 -0
  64. package/dist/chunk-2Y4O3LWM.js.map +1 -0
  65. package/dist/chunk-5FKOLIV6.cjs +221 -0
  66. package/dist/chunk-5FKOLIV6.cjs.map +1 -0
  67. package/dist/chunk-7CFFAKDM.js +74 -0
  68. package/dist/chunk-7CFFAKDM.js.map +1 -0
  69. package/dist/chunk-A4I4IK7V.js +69 -0
  70. package/dist/chunk-A4I4IK7V.js.map +1 -0
  71. package/dist/chunk-IKBK7HYX.cjs +79 -0
  72. package/dist/chunk-IKBK7HYX.cjs.map +1 -0
  73. package/dist/chunk-KEOHORIH.cjs +79 -0
  74. package/dist/chunk-KEOHORIH.cjs.map +1 -0
  75. package/dist/chunk-NAS4K5UR.cjs +139 -0
  76. package/dist/chunk-NAS4K5UR.cjs.map +1 -0
  77. package/dist/chunk-QBLWP25X.cjs +73 -0
  78. package/dist/chunk-QBLWP25X.cjs.map +1 -0
  79. package/dist/chunk-QIC6JFFD.js +210 -0
  80. package/dist/chunk-QIC6JFFD.js.map +1 -0
  81. package/dist/chunk-TAPNXT7X.cjs +422 -0
  82. package/dist/chunk-TAPNXT7X.cjs.map +1 -0
  83. package/dist/chunk-XCKXHK44.js +15 -0
  84. package/dist/chunk-XCKXHK44.js.map +1 -0
  85. package/dist/chunk-XMC4DN6G.js +131 -0
  86. package/dist/chunk-XMC4DN6G.js.map +1 -0
  87. package/dist/chunk-XONXEFJY.cjs +58 -0
  88. package/dist/chunk-XONXEFJY.cjs.map +1 -0
  89. package/dist/chunk-XQNJED46.cjs +19 -0
  90. package/dist/chunk-XQNJED46.cjs.map +1 -0
  91. package/dist/chunk-YB4B3OMC.js +74 -0
  92. package/dist/chunk-YB4B3OMC.js.map +1 -0
  93. package/dist/index.cjs +271 -0
  94. package/dist/index.cjs.map +1 -0
  95. package/dist/index.d.cts +137 -0
  96. package/dist/index.d.ts +137 -0
  97. package/dist/index.js +197 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/layout/index.cjs +13 -0
  100. package/dist/layout/index.cjs.map +1 -0
  101. package/dist/layout/index.d.cts +54 -0
  102. package/dist/layout/index.d.ts +54 -0
  103. package/dist/layout/index.js +4 -0
  104. package/dist/layout/index.js.map +1 -0
  105. package/dist/portfolio/client.cjs +18 -0
  106. package/dist/portfolio/client.cjs.map +1 -0
  107. package/dist/portfolio/client.d.cts +97 -0
  108. package/dist/portfolio/client.d.ts +97 -0
  109. package/dist/portfolio/client.js +6 -0
  110. package/dist/portfolio/client.js.map +1 -0
  111. package/dist/portfolio/index.cjs +41 -0
  112. package/dist/portfolio/index.cjs.map +1 -0
  113. package/dist/portfolio/index.d.cts +12 -0
  114. package/dist/portfolio/index.d.ts +12 -0
  115. package/dist/portfolio/index.js +8 -0
  116. package/dist/portfolio/index.js.map +1 -0
  117. package/dist/portfolio/sections.cjs +20 -0
  118. package/dist/portfolio/sections.cjs.map +1 -0
  119. package/dist/portfolio/sections.d.cts +42 -0
  120. package/dist/portfolio/sections.d.ts +42 -0
  121. package/dist/portfolio/sections.js +4 -0
  122. package/dist/portfolio/sections.js.map +1 -0
  123. package/dist/portfolio/server.cjs +141 -0
  124. package/dist/portfolio/server.cjs.map +1 -0
  125. package/dist/portfolio/server.d.cts +68 -0
  126. package/dist/portfolio/server.d.ts +68 -0
  127. package/dist/portfolio/server.js +134 -0
  128. package/dist/portfolio/server.js.map +1 -0
  129. package/dist/types-BMUhBhWx.d.cts +346 -0
  130. package/dist/types-BMUhBhWx.d.ts +346 -0
  131. package/package.json +71 -0
@@ -0,0 +1,68 @@
1
+ import { B as BrandConfig, P as PortfolioItemFull, a as PortfolioListResponse } from '../types-BMUhBhWx.js';
2
+
3
+ /**
4
+ * @sonordev/agency-site-kit — Portfolio server-side data fetching
5
+ *
6
+ * RSC (React Server Component) data layer for portfolio pages.
7
+ * All functions are async and designed for use in Next.js App Router
8
+ * server components, generateMetadata(), and generateStaticParams().
9
+ *
10
+ * This file is server-only — no 'use client' directive.
11
+ */
12
+
13
+ /**
14
+ * Fetches published portfolio items from the public API.
15
+ * Cached via Next.js ISR (revalidate: 3600).
16
+ */
17
+ declare function getPortfolioItems(options?: {
18
+ category?: string;
19
+ featured?: boolean;
20
+ limit?: number;
21
+ offset?: number;
22
+ }): Promise<PortfolioListResponse>;
23
+ /**
24
+ * Fetches a single portfolio item by slug, including full Sanity sections
25
+ * and live metrics delta. This is the main data function for portfolio detail pages.
26
+ *
27
+ * Returns null when the item is not found or the API call fails.
28
+ */
29
+ declare function getPortfolioItem(slug: string): Promise<PortfolioItemFull | null>;
30
+ /**
31
+ * Fetches available portfolio categories.
32
+ * Returns an empty array on failure.
33
+ */
34
+ declare function getPortfolioCategories(): Promise<string[]>;
35
+ /**
36
+ * Fetches brand configuration for the agency (colors, fonts, logo).
37
+ * Returns sensible defaults when the API call fails.
38
+ */
39
+ declare function getPortfolioBrandConfig(): Promise<BrandConfig>;
40
+ /**
41
+ * Generates Next.js Metadata for a portfolio item page.
42
+ * For use in generateMetadata() in the App Router.
43
+ *
44
+ * Returns an empty object when the item cannot be found, which is safe
45
+ * to spread into the metadata return value.
46
+ */
47
+ declare function generatePortfolioMetadata(slug: string): Promise<{
48
+ title?: string;
49
+ description?: string;
50
+ keywords?: string[];
51
+ openGraph?: {
52
+ title?: string;
53
+ description?: string;
54
+ images?: string[];
55
+ type?: string;
56
+ };
57
+ }>;
58
+ /**
59
+ * Generates static params for portfolio pages.
60
+ * For use in generateStaticParams() in the App Router.
61
+ *
62
+ * Fetches all published portfolio items and returns their slugs.
63
+ */
64
+ declare function generatePortfolioStaticParams(): Promise<Array<{
65
+ slug: string;
66
+ }>>;
67
+
68
+ export { generatePortfolioMetadata, generatePortfolioStaticParams, getPortfolioBrandConfig, getPortfolioCategories, getPortfolioItem, getPortfolioItems };
@@ -0,0 +1,134 @@
1
+ import { apiGet } from '../chunk-A4I4IK7V.js';
2
+
3
+ // src/portfolio/server.ts
4
+ var LOG_PREFIX = "[@sonordev/agency-site-kit/portfolio]";
5
+ function buildQuery(params) {
6
+ const entries = Object.entries(params).filter(
7
+ (entry) => entry[1] != null
8
+ );
9
+ if (entries.length === 0) return "";
10
+ const qs = new URLSearchParams(
11
+ entries.map(([k, v]) => [k, String(v)])
12
+ ).toString();
13
+ return `?${qs}`;
14
+ }
15
+ async function getPortfolioItems(options) {
16
+ const fallback = { items: [], total: 0, limit: 0, offset: 0 };
17
+ const query = buildQuery({
18
+ category: options?.category,
19
+ featured: options?.featured,
20
+ limit: options?.limit,
21
+ offset: options?.offset
22
+ });
23
+ const data = await apiGet(
24
+ `/api/public/portfolio/items${query}`
25
+ );
26
+ if (!data) {
27
+ console.warn(`${LOG_PREFIX} Failed to fetch portfolio items.`);
28
+ return fallback;
29
+ }
30
+ return data;
31
+ }
32
+ async function getPortfolioItem(slug) {
33
+ if (!slug) {
34
+ console.warn(`${LOG_PREFIX} getPortfolioItem called with empty slug.`);
35
+ return null;
36
+ }
37
+ const data = await apiGet(
38
+ `/api/public/portfolio/items/${encodeURIComponent(slug)}`
39
+ );
40
+ if (!data || !data.found || !data.item) {
41
+ console.warn(`${LOG_PREFIX} Portfolio item not found for slug: "${slug}".`);
42
+ return null;
43
+ }
44
+ const { item } = data;
45
+ return {
46
+ id: item.id,
47
+ slug: item.slug,
48
+ title: item.title,
49
+ subtitle: item.subtitle,
50
+ category: item.category,
51
+ services: item.services,
52
+ description: item.description,
53
+ hero_image: item.hero_image,
54
+ hero_image_alt: item.hero_image_alt,
55
+ live_url: item.live_url,
56
+ kpis: item.kpis,
57
+ details: item.details,
58
+ seo: item.seo,
59
+ featured: item.featured,
60
+ order: item.order,
61
+ published_at: item.published_at,
62
+ hero_screenshots: item.hero_screenshots,
63
+ baseline_metrics: item.baseline_metrics,
64
+ current_metrics: item.current_metrics,
65
+ metrics_last_refreshed_at: item.metrics_last_refreshed_at,
66
+ sections: item.sections ?? [],
67
+ metricsDelta: item.metricsDelta ?? []
68
+ };
69
+ }
70
+ async function getPortfolioCategories() {
71
+ const data = await apiGet("/api/public/portfolio/categories");
72
+ if (!data) {
73
+ console.warn(`${LOG_PREFIX} Failed to fetch portfolio categories.`);
74
+ return [];
75
+ }
76
+ return data;
77
+ }
78
+ async function getPortfolioBrandConfig() {
79
+ const fallback = {
80
+ primary: "#6366f1",
81
+ secondary: "#8b5cf6",
82
+ background: "#0a0a0f",
83
+ backgroundElevated: "#12121a",
84
+ surface: "#1a1a2e",
85
+ surfaceHover: "#22223a",
86
+ surfaceBorder: "#2a2a3e",
87
+ textPrimary: "#f0f0f5",
88
+ textSecondary: "#a0a0b5",
89
+ textTertiary: "#6b6b80",
90
+ fontHeading: "Inter, system-ui, sans-serif",
91
+ fontBody: "Inter, system-ui, sans-serif"
92
+ };
93
+ const data = await apiGet(
94
+ "/api/public/portfolio/config"
95
+ );
96
+ if (!data || !data.brand) {
97
+ console.warn(`${LOG_PREFIX} Failed to fetch brand config, using defaults.`);
98
+ return fallback;
99
+ }
100
+ return data.brand;
101
+ }
102
+ async function generatePortfolioMetadata(slug) {
103
+ const item = await getPortfolioItem(slug);
104
+ if (!item) {
105
+ return {};
106
+ }
107
+ const seo = item.seo;
108
+ const title = seo?.metaTitle || item.title;
109
+ const description = seo?.metaDescription || item.description;
110
+ const keywords = seo?.keywords ?? [];
111
+ const ogImages = [];
112
+ if (item.hero_image) {
113
+ ogImages.push(item.hero_image);
114
+ }
115
+ return {
116
+ title,
117
+ description,
118
+ keywords: keywords.length > 0 ? keywords : void 0,
119
+ openGraph: {
120
+ title,
121
+ description,
122
+ images: ogImages.length > 0 ? ogImages : void 0,
123
+ type: "article"
124
+ }
125
+ };
126
+ }
127
+ async function generatePortfolioStaticParams() {
128
+ const data = await getPortfolioItems({ limit: 500, offset: 0 });
129
+ return data.items.map((item) => ({ slug: item.slug }));
130
+ }
131
+
132
+ export { generatePortfolioMetadata, generatePortfolioStaticParams, getPortfolioBrandConfig, getPortfolioCategories, getPortfolioItem, getPortfolioItems };
133
+ //# sourceMappingURL=server.js.map
134
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/portfolio/server.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["/**\n * @sonordev/agency-site-kit — Portfolio server-side data fetching\n *\n * RSC (React Server Component) data layer for portfolio pages.\n * All functions are async and designed for use in Next.js App Router\n * server components, generateMetadata(), and generateStaticParams().\n *\n * This file is server-only — no 'use client' directive.\n */\n\nimport { apiGet } from '../shared/api';\nimport type {\n BrandConfig,\n MetricsDelta,\n PortfolioConfigResponse,\n PortfolioItemFull,\n PortfolioListResponse,\n PortfolioSeoData,\n PortfolioSection,\n} from '../types';\n\nconst LOG_PREFIX = '[@sonordev/agency-site-kit/portfolio]';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a query string from an object, omitting undefined/null values.\n */\nfunction buildQuery(params: Record<string, string | number | boolean | undefined | null>): string {\n const entries = Object.entries(params).filter(\n (entry): entry is [string, string | number | boolean] => entry[1] != null,\n );\n if (entries.length === 0) return '';\n const qs = new URLSearchParams(\n entries.map(([k, v]) => [k, String(v)]),\n ).toString();\n return `?${qs}`;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — List items\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches published portfolio items from the public API.\n * Cached via Next.js ISR (revalidate: 3600).\n */\nexport async function getPortfolioItems(options?: {\n category?: string;\n featured?: boolean;\n limit?: number;\n offset?: number;\n}): Promise<PortfolioListResponse> {\n const fallback: PortfolioListResponse = { items: [], total: 0, limit: 0, offset: 0 };\n\n const query = buildQuery({\n category: options?.category,\n featured: options?.featured,\n limit: options?.limit,\n offset: options?.offset,\n });\n\n const data = await apiGet<PortfolioListResponse>(\n `/api/public/portfolio/items${query}`,\n );\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio items.`);\n return fallback;\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Single item\n// ---------------------------------------------------------------------------\n\n/** Shape returned by the single-item API endpoint. */\ninterface PortfolioItemApiResponse {\n found: boolean;\n item: {\n id: string;\n slug: string;\n title: string;\n subtitle: string;\n category: string;\n services: string[];\n description: string;\n hero_image: string;\n hero_image_alt: string;\n live_url: string | null;\n kpis: PortfolioItemFull['kpis'];\n details: PortfolioItemFull['details'];\n seo: PortfolioItemFull['seo'];\n featured: boolean;\n order?: number;\n published_at: string | null;\n hero_screenshots: PortfolioItemFull['hero_screenshots'];\n baseline_metrics: PortfolioItemFull['baseline_metrics'];\n current_metrics: PortfolioItemFull['current_metrics'];\n metrics_last_refreshed_at: string | null;\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n };\n}\n\n/**\n * Fetches a single portfolio item by slug, including full Sanity sections\n * and live metrics delta. This is the main data function for portfolio detail pages.\n *\n * Returns null when the item is not found or the API call fails.\n */\nexport async function getPortfolioItem(slug: string): Promise<PortfolioItemFull | null> {\n if (!slug) {\n console.warn(`${LOG_PREFIX} getPortfolioItem called with empty slug.`);\n return null;\n }\n\n const data = await apiGet<PortfolioItemApiResponse>(\n `/api/public/portfolio/items/${encodeURIComponent(slug)}`,\n );\n\n if (!data || !data.found || !data.item) {\n console.warn(`${LOG_PREFIX} Portfolio item not found for slug: \"${slug}\".`);\n return null;\n }\n\n // Map the API response to the PortfolioItemFull type\n const { item } = data;\n return {\n id: item.id,\n slug: item.slug,\n title: item.title,\n subtitle: item.subtitle,\n category: item.category,\n services: item.services,\n description: item.description,\n hero_image: item.hero_image,\n hero_image_alt: item.hero_image_alt,\n live_url: item.live_url,\n kpis: item.kpis,\n details: item.details,\n seo: item.seo,\n featured: item.featured,\n order: item.order,\n published_at: item.published_at,\n hero_screenshots: item.hero_screenshots,\n baseline_metrics: item.baseline_metrics,\n current_metrics: item.current_metrics,\n metrics_last_refreshed_at: item.metrics_last_refreshed_at,\n sections: item.sections ?? [],\n metricsDelta: item.metricsDelta ?? [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Categories\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches available portfolio categories.\n * Returns an empty array on failure.\n */\nexport async function getPortfolioCategories(): Promise<string[]> {\n const data = await apiGet<string[]>('/api/public/portfolio/categories');\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio categories.`);\n return [];\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Brand config\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches brand configuration for the agency (colors, fonts, logo).\n * Returns sensible defaults when the API call fails.\n */\nexport async function getPortfolioBrandConfig(): Promise<BrandConfig> {\n const fallback: BrandConfig = {\n primary: '#6366f1',\n secondary: '#8b5cf6',\n background: '#0a0a0f',\n backgroundElevated: '#12121a',\n surface: '#1a1a2e',\n surfaceHover: '#22223a',\n surfaceBorder: '#2a2a3e',\n textPrimary: '#f0f0f5',\n textSecondary: '#a0a0b5',\n textTertiary: '#6b6b80',\n fontHeading: 'Inter, system-ui, sans-serif',\n fontBody: 'Inter, system-ui, sans-serif',\n };\n\n const data = await apiGet<PortfolioConfigResponse>(\n '/api/public/portfolio/config',\n );\n\n if (!data || !data.brand) {\n console.warn(`${LOG_PREFIX} Failed to fetch brand config, using defaults.`);\n return fallback;\n }\n\n return data.brand;\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Metadata helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates Next.js Metadata for a portfolio item page.\n * For use in generateMetadata() in the App Router.\n *\n * Returns an empty object when the item cannot be found, which is safe\n * to spread into the metadata return value.\n */\nexport async function generatePortfolioMetadata(slug: string): Promise<{\n title?: string;\n description?: string;\n keywords?: string[];\n openGraph?: {\n title?: string;\n description?: string;\n images?: string[];\n type?: string;\n };\n}> {\n const item = await getPortfolioItem(slug);\n\n if (!item) {\n return {};\n }\n\n // Prefer dedicated SEO data if available, fall back to item fields\n const seo: PortfolioSeoData | null = item.seo;\n const title = seo?.metaTitle || item.title;\n const description = seo?.metaDescription || item.description;\n const keywords = seo?.keywords ?? [];\n\n // Collect OG images: prefer SEO og image, then hero image\n const ogImages: string[] = [];\n if (item.hero_image) {\n ogImages.push(item.hero_image);\n }\n\n return {\n title,\n description,\n keywords: keywords.length > 0 ? keywords : undefined,\n openGraph: {\n title,\n description,\n images: ogImages.length > 0 ? ogImages : undefined,\n type: 'article',\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Static Params helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates static params for portfolio pages.\n * For use in generateStaticParams() in the App Router.\n *\n * Fetches all published portfolio items and returns their slugs.\n */\nexport async function generatePortfolioStaticParams(): Promise<Array<{ slug: string }>> {\n // Fetch a large batch to cover all published items\n const data = await getPortfolioItems({ limit: 500, offset: 0 });\n\n return data.items.map((item) => ({ slug: item.slug }));\n}\n"]}
@@ -0,0 +1,346 @@
1
+ /**
2
+ * @sonordev/agency-site-kit — Type definitions
3
+ *
4
+ * These types mirror the Portal API's cms/types.ts definitions for portfolio
5
+ * content. Keep them in sync when modifying the API response shapes.
6
+ */
7
+ interface BrandConfig {
8
+ /** Primary brand color (hex) */
9
+ primary: string;
10
+ /** Secondary/accent color (hex) */
11
+ secondary: string;
12
+ /** Background color (hex) */
13
+ background: string;
14
+ /** Elevated background — cards, modals (hex) */
15
+ backgroundElevated: string;
16
+ /** Surface color — interactive containers (hex) */
17
+ surface: string;
18
+ /** Surface hover state (hex) */
19
+ surfaceHover: string;
20
+ /** Surface border color (hex) */
21
+ surfaceBorder: string;
22
+ /** Primary text color (hex) */
23
+ textPrimary: string;
24
+ /** Secondary text color (hex) */
25
+ textSecondary: string;
26
+ /** Tertiary/muted text color (hex) */
27
+ textTertiary: string;
28
+ /** Heading font family */
29
+ fontHeading: string;
30
+ /** Body font family */
31
+ fontBody: string;
32
+ /** Logo URL (absolute or relative) */
33
+ logoUrl?: string;
34
+ /** Logo alt text */
35
+ logoAlt?: string;
36
+ /** Dark mode overrides — partial, merged with base values */
37
+ darkMode?: Partial<Omit<BrandConfig, 'fontHeading' | 'fontBody' | 'logoUrl' | 'logoAlt' | 'darkMode'>>;
38
+ /** Border radius tokens */
39
+ radius?: {
40
+ sm: string;
41
+ md: string;
42
+ lg: string;
43
+ };
44
+ }
45
+ interface SanityImageRef {
46
+ _type: 'image';
47
+ asset: {
48
+ _type: 'reference';
49
+ _ref: string;
50
+ };
51
+ hotspot?: {
52
+ x: number;
53
+ y: number;
54
+ height: number;
55
+ width: number;
56
+ };
57
+ crop?: {
58
+ top: number;
59
+ bottom: number;
60
+ left: number;
61
+ right: number;
62
+ };
63
+ alt?: string;
64
+ }
65
+ type MetricSource = 'measured' | 'estimated';
66
+ interface PortfolioKPI {
67
+ label: string;
68
+ value: number;
69
+ suffix: string;
70
+ prefix?: string;
71
+ description: string;
72
+ source: MetricSource;
73
+ }
74
+ interface MetricsDelta {
75
+ metric: string;
76
+ baseline: number;
77
+ current: number;
78
+ delta: number;
79
+ deltaPercent: number;
80
+ direction: 'up' | 'down' | 'flat';
81
+ timespan: string;
82
+ }
83
+ interface MetricsSnapshot {
84
+ seo: {
85
+ clicks_28d?: number;
86
+ impressions_28d?: number;
87
+ avg_position?: number;
88
+ ctr?: number;
89
+ };
90
+ cwv: {
91
+ lcp_ms?: number;
92
+ inp_ms?: number;
93
+ cls?: number;
94
+ performance_score?: number;
95
+ };
96
+ analytics: {
97
+ sessions_28d?: number;
98
+ page_views_28d?: number;
99
+ conversion_rate?: number;
100
+ bounce_rate?: number;
101
+ };
102
+ pages: {
103
+ total_indexed?: number;
104
+ total_crawled?: number;
105
+ total_pages?: number;
106
+ };
107
+ }
108
+ interface PortfolioHeroData {
109
+ headline: string;
110
+ subheadline: string;
111
+ description: string;
112
+ category: string;
113
+ services: string[];
114
+ liveUrl?: string;
115
+ screenshots: {
116
+ desktop?: string;
117
+ tablet?: string;
118
+ mobile?: string;
119
+ };
120
+ kpis: PortfolioKPI[];
121
+ }
122
+ interface PortfolioChallengeItem {
123
+ title: string;
124
+ description: string;
125
+ solution: string;
126
+ result?: string;
127
+ icon?: string;
128
+ }
129
+ interface PortfolioChallengesData {
130
+ items: PortfolioChallengeItem[];
131
+ }
132
+ interface PortfolioStrategyPhase {
133
+ number: number;
134
+ title: string;
135
+ description: string;
136
+ deliverables?: string[];
137
+ icon?: string;
138
+ timeline?: string;
139
+ }
140
+ interface PortfolioStrategyData {
141
+ phases: PortfolioStrategyPhase[];
142
+ }
143
+ interface PortfolioResultItem {
144
+ title: string;
145
+ description: string;
146
+ metric?: {
147
+ value: number;
148
+ suffix: string;
149
+ prefix?: string;
150
+ };
151
+ source: MetricSource;
152
+ icon?: string;
153
+ }
154
+ interface PortfolioResultsData {
155
+ items: PortfolioResultItem[];
156
+ }
157
+ interface PortfolioTechItem {
158
+ name: string;
159
+ category: string;
160
+ icon?: string;
161
+ description?: string;
162
+ }
163
+ interface PortfolioTechStackData {
164
+ technologies: PortfolioTechItem[];
165
+ }
166
+ interface PortfolioServiceItem {
167
+ title: string;
168
+ description: string;
169
+ features: string[];
170
+ icon?: string;
171
+ }
172
+ interface PortfolioServicesData {
173
+ items: PortfolioServiceItem[];
174
+ }
175
+ interface PortfolioTestimonialData {
176
+ quote: string;
177
+ author: string;
178
+ title: string;
179
+ company: string;
180
+ avatar?: SanityImageRef;
181
+ rating?: number;
182
+ }
183
+ interface PortfolioGalleryImage {
184
+ image: SanityImageRef;
185
+ caption?: string;
186
+ type?: 'screenshot' | 'before' | 'after' | 'design';
187
+ }
188
+ interface PortfolioGalleryData {
189
+ images: PortfolioGalleryImage[];
190
+ layout?: 'grid' | 'masonry';
191
+ }
192
+ interface PortfolioVideoData {
193
+ url?: string;
194
+ embedUrl?: string;
195
+ thumbnail: SanityImageRef;
196
+ title: string;
197
+ duration?: string;
198
+ platform?: 'youtube' | 'vimeo' | 'custom';
199
+ }
200
+ interface PortfolioTeamMember {
201
+ name: string;
202
+ role: string;
203
+ avatar?: SanityImageRef;
204
+ }
205
+ interface PortfolioTeamData {
206
+ members: PortfolioTeamMember[];
207
+ }
208
+ interface PortfolioAnnotation {
209
+ x: number;
210
+ y: number;
211
+ label: string;
212
+ description: string;
213
+ icon?: string;
214
+ }
215
+ interface PortfolioFeatureSpotlightData {
216
+ image: SanityImageRef;
217
+ title: string;
218
+ description: string;
219
+ layout: 'left' | 'right' | 'full';
220
+ annotations: PortfolioAnnotation[];
221
+ }
222
+ interface PortfolioBeforeAfterData {
223
+ before: SanityImageRef;
224
+ after: SanityImageRef;
225
+ title?: string;
226
+ description?: string;
227
+ defaultPosition?: number;
228
+ }
229
+ interface PortfolioMetricsDataPoint {
230
+ month: string;
231
+ label: string;
232
+ metrics: {
233
+ traffic?: number;
234
+ conversions?: number;
235
+ revenue?: number;
236
+ rankings?: number;
237
+ };
238
+ }
239
+ interface PortfolioMetricsTimelineData {
240
+ dataPoints: PortfolioMetricsDataPoint[];
241
+ baselineDate: string;
242
+ title?: string;
243
+ description?: string;
244
+ }
245
+ interface PortfolioFunnelStage {
246
+ label: string;
247
+ value: number;
248
+ icon?: string;
249
+ color?: string;
250
+ }
251
+ interface PortfolioConversionFunnelData {
252
+ stages: PortfolioFunnelStage[];
253
+ title?: string;
254
+ description?: string;
255
+ }
256
+ interface PortfolioDetailsData {
257
+ industry: string;
258
+ location?: string;
259
+ website?: string;
260
+ timeline?: string;
261
+ launchDate?: string;
262
+ budgetRange?: string;
263
+ }
264
+ interface PortfolioSeoData {
265
+ metaTitle: string;
266
+ metaDescription: string;
267
+ keywords: string[];
268
+ ogImage?: SanityImageRef;
269
+ }
270
+ interface PortfolioCTAData {
271
+ headline: string;
272
+ description: string;
273
+ buttonText: string;
274
+ buttonUrl: string;
275
+ style?: 'primary' | 'glass';
276
+ }
277
+ declare const PORTFOLIO_SECTION_TYPES: readonly ["portfolioHero", "portfolioChallenges", "portfolioStrategy", "portfolioResults", "portfolioTechStack", "portfolioServices", "portfolioTestimonial", "portfolioGallery", "portfolioVideo", "portfolioTeam", "portfolioFeatureSpotlight", "portfolioBeforeAfter", "portfolioMetricsTimeline", "portfolioConversionFunnel", "portfolioDetails", "portfolioSeo", "portfolioCTA"];
278
+ type PortfolioSectionType = (typeof PORTFOLIO_SECTION_TYPES)[number];
279
+ type PortfolioSectionData = PortfolioHeroData | PortfolioChallengesData | PortfolioStrategyData | PortfolioResultsData | PortfolioTechStackData | PortfolioServicesData | PortfolioTestimonialData | PortfolioGalleryData | PortfolioVideoData | PortfolioTeamData | PortfolioFeatureSpotlightData | PortfolioBeforeAfterData | PortfolioMetricsTimelineData | PortfolioConversionFunnelData | PortfolioDetailsData | PortfolioSeoData | PortfolioCTAData;
280
+ interface PortfolioSectionDataMap {
281
+ portfolioHero: PortfolioHeroData;
282
+ portfolioChallenges: PortfolioChallengesData;
283
+ portfolioStrategy: PortfolioStrategyData;
284
+ portfolioResults: PortfolioResultsData;
285
+ portfolioTechStack: PortfolioTechStackData;
286
+ portfolioServices: PortfolioServicesData;
287
+ portfolioTestimonial: PortfolioTestimonialData;
288
+ portfolioGallery: PortfolioGalleryData;
289
+ portfolioVideo: PortfolioVideoData;
290
+ portfolioTeam: PortfolioTeamData;
291
+ portfolioFeatureSpotlight: PortfolioFeatureSpotlightData;
292
+ portfolioBeforeAfter: PortfolioBeforeAfterData;
293
+ portfolioMetricsTimeline: PortfolioMetricsTimelineData;
294
+ portfolioConversionFunnel: PortfolioConversionFunnelData;
295
+ portfolioDetails: PortfolioDetailsData;
296
+ portfolioSeo: PortfolioSeoData;
297
+ portfolioCTA: PortfolioCTAData;
298
+ }
299
+ interface PortfolioSection<T extends PortfolioSectionType = PortfolioSectionType> {
300
+ sectionType: T;
301
+ displayName: string;
302
+ data: T extends keyof PortfolioSectionDataMap ? PortfolioSectionDataMap[T] : PortfolioSectionData;
303
+ }
304
+ interface PortfolioItem {
305
+ id: string;
306
+ slug: string;
307
+ title: string;
308
+ subtitle: string;
309
+ category: string;
310
+ services: string[];
311
+ description: string;
312
+ hero_image: string;
313
+ hero_image_alt: string;
314
+ live_url: string | null;
315
+ kpis: PortfolioKPI[];
316
+ details: PortfolioDetailsData | null;
317
+ seo: PortfolioSeoData | null;
318
+ featured: boolean;
319
+ order?: number;
320
+ published_at: string | null;
321
+ hero_screenshots: {
322
+ desktop?: string;
323
+ tablet?: string;
324
+ mobile?: string;
325
+ } | null;
326
+ baseline_metrics: MetricsSnapshot | null;
327
+ current_metrics: MetricsSnapshot | null;
328
+ metrics_last_refreshed_at: string | null;
329
+ }
330
+ interface PortfolioItemFull extends PortfolioItem {
331
+ sections: PortfolioSection[];
332
+ metricsDelta: MetricsDelta[];
333
+ }
334
+ interface PortfolioListResponse {
335
+ items: PortfolioItem[];
336
+ total: number;
337
+ limit: number;
338
+ offset: number;
339
+ }
340
+ interface PortfolioConfigResponse {
341
+ brand: BrandConfig;
342
+ orgName: string;
343
+ orgSlug: string;
344
+ }
345
+
346
+ export { type PortfolioSection as A, type BrandConfig as B, type PortfolioSeoData as C, type PortfolioServiceItem as D, type PortfolioServicesData as E, type PortfolioStrategyData as F, type PortfolioStrategyPhase as G, type PortfolioTeamData as H, type PortfolioTeamMember as I, type PortfolioTechItem as J, type PortfolioTechStackData as K, type PortfolioTestimonialData as L, type MetricSource as M, type PortfolioVideoData as N, type PortfolioItemFull as P, type SanityImageRef as S, type PortfolioListResponse as a, type PortfolioSectionType as b, type PortfolioSectionDataMap as c, type PortfolioSectionData as d, type MetricsDelta as e, type MetricsSnapshot as f, PORTFOLIO_SECTION_TYPES as g, type PortfolioAnnotation as h, type PortfolioBeforeAfterData as i, type PortfolioCTAData as j, type PortfolioChallengeItem as k, type PortfolioChallengesData as l, type PortfolioConfigResponse as m, type PortfolioConversionFunnelData as n, type PortfolioDetailsData as o, type PortfolioFeatureSpotlightData as p, type PortfolioFunnelStage as q, type PortfolioGalleryData as r, type PortfolioGalleryImage as s, type PortfolioHeroData as t, type PortfolioItem as u, type PortfolioKPI as v, type PortfolioMetricsDataPoint as w, type PortfolioMetricsTimelineData as x, type PortfolioResultItem as y, type PortfolioResultsData as z };