fragment-headless-sdk 2.1.2 → 2.1.4

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.
@@ -33,29 +33,29 @@ export default function CountdownTimer({ content, }) {
33
33
  const blockClass = mergeSlotClasses("flex flex-col items-center", styling, "countdownBlock");
34
34
  const blockStyle = mergeSlotStyles(undefined, styling, "countdownBlock");
35
35
  const blockAttributes = mergeSlotAttributes(styling, "countdownBlock");
36
- const digitClass = mergeSlotClasses("w-5 h-6 rounded bg-black text-white text-base font-bold flex items-center justify-center", styling, "countdownDigit");
37
- const digitBgColor = resolveToken(styling, "counterBgColor", "#000000") || "#000000";
36
+ const digitClass = mergeSlotClasses("text-white text-xl font-bold flex items-center justify-center leading-none", styling, "countdownDigit");
38
37
  const digitTextColor = resolveToken(styling, "counterDigitColor", "#FFFFFF") || "#FFFFFF";
39
38
  const digitStyle = mergeSlotStyles({
40
- backgroundColor: digitBgColor,
41
39
  color: digitTextColor,
42
40
  }, styling, "countdownDigit");
43
41
  const digitAttributes = mergeSlotAttributes(styling, "countdownDigit");
44
- const labelClass = mergeSlotClasses("mt-0.5 text-[10px]", styling, "countdownLabel");
45
- const labelStyle = mergeSlotStyles(undefined, styling, "countdownLabel");
42
+ const labelClass = mergeSlotClasses("mt-0.5 text-[10px] leading-tight", styling, "countdownLabel");
43
+ const labelStyle = mergeSlotStyles({
44
+ color: digitTextColor,
45
+ }, styling, "countdownLabel");
46
46
  const labelAttributes = mergeSlotAttributes(styling, "countdownLabel");
47
47
  const separatorClass = mergeSlotClasses("text-xl font-semibold -mt-4", styling, "countdownSeparator");
48
48
  const separatorStyle = mergeSlotStyles(undefined, styling, "countdownSeparator");
49
49
  const separatorAttributes = mergeSlotAttributes(styling, "countdownSeparator");
50
50
  const renderBlock = (value, label) => (React.createElement("div", { className: blockClass, style: blockStyle, ...(blockAttributes ?? {}) },
51
- React.createElement("div", { className: "flex gap-1" }, value.split("").map((digit, i) => (React.createElement("span", { key: i, className: digitClass, style: digitStyle, ...(digitAttributes ?? {}) }, digit)))),
51
+ React.createElement("div", { className: "flex" }, value.split("").map((digit, i) => (React.createElement("span", { key: i, className: digitClass, style: digitStyle, ...(digitAttributes ?? {}) }, digit)))),
52
52
  React.createElement("span", { className: labelClass, style: labelStyle, ...(labelAttributes ?? {}) }, label)));
53
53
  return (React.createElement("div", { className: containerClass, style: containerStyle, ...(containerAttributes ?? {}) },
54
54
  renderBlock(timeLeft.days, "Days"),
55
55
  React.createElement("span", { className: separatorClass, style: separatorStyle, ...(separatorAttributes ?? {}) }, ":"),
56
56
  renderBlock(timeLeft.hours, "Hours"),
57
57
  React.createElement("span", { className: separatorClass, style: separatorStyle, ...(separatorAttributes ?? {}) }, ":"),
58
- renderBlock(timeLeft.minutes, "Minutes"),
58
+ renderBlock(timeLeft.minutes, "Mins"),
59
59
  React.createElement("span", { className: separatorClass, style: separatorStyle, ...(separatorAttributes ?? {}) }, ":"),
60
- renderBlock(timeLeft.seconds, "Seconds")));
60
+ renderBlock(timeLeft.seconds, "Secs")));
61
61
  }
@@ -1,3 +1,4 @@
1
+ import { XMarkIcon } from "@heroicons/react/24/outline";
1
2
  import React, { useEffect, useRef } from "react";
2
3
  import { AnnouncementType, ButtonType } from "../../constants";
3
4
  import { buildClickUrl, fireImpressionWhenVisible, getThemeClasses, mergeSlotAttributes, mergeSlotClasses, mergeSlotStyles, resolveToken, resolveTokenByCategory, } from "../../utils";
@@ -50,7 +51,7 @@ export default function Announcement({ content, type, handleClose, }) {
50
51
  const announcementTextClass = mergeSlotClasses("max-w-none text-base font-semibold", styling, "announcementText");
51
52
  const announcementTextStyle = mergeSlotStyles(undefined, styling, "announcementText");
52
53
  const announcementTextAttributes = mergeSlotAttributes(styling, "announcementText");
53
- const closeButtonClass = mergeSlotClasses("absolute right-4 top-1/2 -translate-y-1/2 text-3xl leading-none cursor-pointer", styling, "closeButton");
54
+ const closeButtonClass = mergeSlotClasses("absolute right-4 top-1/2 -translate-y-1/2 cursor-pointer", styling, "closeButton");
54
55
  const closeButtonColor = resolveTokenByCategory(styling, "colors", "closeButton") ||
55
56
  (resolveToken(styling, "closeButtonColor", textColor ?? "#000") ?? "#000");
56
57
  const closeButtonStyle = mergeSlotStyles({ color: closeButtonColor }, styling, "closeButton");
@@ -69,7 +70,8 @@ export default function Announcement({ content, type, handleClose, }) {
69
70
  React.createElement("div", { dangerouslySetInnerHTML: {
70
71
  __html: content.announcementHtml || "",
71
72
  } })),
72
- type === AnnouncementType.Countdown ? (React.createElement(CountdownTimer, { content: content })) : (content.buttonText &&
73
- content.buttonType !== ButtonType.None && (React.createElement(AnnouncementButton, { content: content, buttonHref: buttonHref, clickHref: clickTrackingHref }))))),
74
- React.createElement("div", { onClick: handleClose, className: closeButtonClass, style: closeButtonStyle, ...(closeButtonAttributes ?? {}) }, "\u00D7"))));
73
+ type === AnnouncementType.Countdown && (React.createElement(CountdownTimer, { content: content })),
74
+ content.buttonText && content.buttonType !== ButtonType.None && (React.createElement(AnnouncementButton, { content: content, buttonHref: buttonHref, clickHref: clickTrackingHref })))),
75
+ React.createElement("div", { onClick: handleClose, className: closeButtonClass, style: closeButtonStyle, ...(closeButtonAttributes ?? {}) },
76
+ React.createElement(XMarkIcon, { className: "w-5 h-5" })))));
75
77
  }
@@ -1,7 +1,4 @@
1
- export declare enum ResourceType {
2
- HeroBanners = "hero-banners",
3
- Announcements = "announcements"
4
- }
1
+ export declare const ENABLE_REQUEST_DEDUPLICATION = true;
5
2
  export type ListParams = {
6
3
  status?: "enabled" | "disabled";
7
4
  page?: number;
@@ -9,10 +6,14 @@ export type ListParams = {
9
6
  search?: string;
10
7
  pageFilter?: string;
11
8
  };
9
+ export declare enum ResourceType {
10
+ HeroBanners = "hero-banners",
11
+ Announcements = "announcements"
12
+ }
12
13
  export type CacheOptions = {
13
- /** Request cache mode (default: 'no-store' for fresh data) */
14
+ /** Request cache mode (default: 'force-cache' with 60s revalidation) */
14
15
  cache?: RequestCache;
15
- /** Next.js revalidation time in seconds (default: 0 for always fresh) */
16
+ /** Next.js revalidation time in seconds (default: 60) */
16
17
  revalidate?: number | false;
17
18
  /** Next.js cache tags for selective invalidation */
18
19
  tags?: string[];
@@ -23,7 +24,7 @@ type FetchResourceParams = {
23
24
  type: ResourceType;
24
25
  params?: ListParams;
25
26
  fetchImpl?: typeof fetch;
26
- /** Cache configuration (defaults to no caching for fresh data) */
27
+ /** Cache configuration (defaults to force-cache with 60s revalidation) */
27
28
  cacheOptions?: CacheOptions;
28
29
  };
29
30
  /** Lists resources with optional filters (parity with client.list) */
@@ -1,8 +1,30 @@
1
+ // Simple anti-spam: prevent identical concurrent requests
2
+ export const ENABLE_REQUEST_DEDUPLICATION = true;
1
3
  export var ResourceType;
2
4
  (function (ResourceType) {
3
5
  ResourceType["HeroBanners"] = "hero-banners";
4
6
  ResourceType["Announcements"] = "announcements";
5
7
  })(ResourceType || (ResourceType = {}));
8
+ // Simple request deduplication to prevent identical concurrent requests
9
+ const pendingRequests = new Map();
10
+ /**
11
+ * Generates a cache key for request deduplication
12
+ * Normalizes params to include default status for consistent key generation
13
+ */
14
+ function generateRequestKey(baseUrl, type, params) {
15
+ // Normalize params with defaults (same as performRequest does)
16
+ const normalizedParams = {
17
+ ...params,
18
+ status: params.status ?? "enabled", // Include default status
19
+ };
20
+ const sortedParams = Object.keys(normalizedParams)
21
+ .sort()
22
+ .reduce((acc, key) => {
23
+ acc[key] = normalizedParams[key];
24
+ return acc;
25
+ }, {});
26
+ return `${baseUrl}:${type}:${JSON.stringify(sortedParams)}`;
27
+ }
6
28
  /**
7
29
  * Detects if running in Next.js environment
8
30
  */
@@ -20,10 +42,10 @@ function getDefaultCacheConfig(type) {
20
42
  const isNextJS = isNextJSEnvironment();
21
43
  const isDev = process?.env?.NODE_ENV === "development";
22
44
  if (isNextJS && !isDev) {
23
- // In Next.js production, default to no caching for fresh data
45
+ // In Next.js production, default to caching with 60 second revalidation
24
46
  return {
25
- cache: "no-store",
26
- revalidate: 0,
47
+ cache: "force-cache",
48
+ revalidate: 60,
27
49
  tags: [`fragment-${type}`],
28
50
  };
29
51
  }
@@ -38,6 +60,50 @@ export async function fetchResource({ baseUrl, apiKey, type, params = {}, fetchI
38
60
  console.warn("❌ Missing EXTERNAL_API_URL or FRAGMENT_API_KEY");
39
61
  return [];
40
62
  }
63
+ // Generate key for request deduplication
64
+ const requestKey = generateRequestKey(baseUrl, type, params);
65
+ // Request deduplication - check if identical request is already in flight
66
+ if (ENABLE_REQUEST_DEDUPLICATION) {
67
+ const existingRequest = pendingRequests.get(requestKey);
68
+ if (existingRequest) {
69
+ console.log(`🔄 Deduplicating request for ${type}`);
70
+ try {
71
+ return await existingRequest;
72
+ }
73
+ catch (err) {
74
+ // If the existing request failed, we'll try again below
75
+ pendingRequests.delete(requestKey);
76
+ }
77
+ }
78
+ }
79
+ // Create the actual request promise
80
+ const requestPromise = performRequest({
81
+ baseUrl,
82
+ apiKey,
83
+ type,
84
+ params,
85
+ fetchImpl,
86
+ cacheOptions,
87
+ });
88
+ // Store the promise for deduplication
89
+ if (ENABLE_REQUEST_DEDUPLICATION) {
90
+ pendingRequests.set(requestKey, requestPromise);
91
+ }
92
+ try {
93
+ const result = await requestPromise;
94
+ return result;
95
+ }
96
+ finally {
97
+ // Clean up the pending request
98
+ if (ENABLE_REQUEST_DEDUPLICATION) {
99
+ pendingRequests.delete(requestKey);
100
+ }
101
+ }
102
+ }
103
+ /**
104
+ * Performs the actual HTTP request (separated for cleaner code organization)
105
+ */
106
+ async function performRequest({ baseUrl, apiKey, type, params = {}, fetchImpl, cacheOptions, }) {
41
107
  try {
42
108
  const f = fetchImpl ?? fetch;
43
109
  const base = baseUrl.replace(/\/+$/, "");
@@ -47,8 +113,8 @@ export async function fetchResource({ baseUrl, apiKey, type, params = {}, fetchI
47
113
  const limit = Math.min(100, Math.max(1, params.limit ?? 25));
48
114
  url.searchParams.set("pageNum", String(page));
49
115
  url.searchParams.set("limit", String(limit));
50
- if (params.status)
51
- url.searchParams.set("status", params.status);
116
+ // Default to "enabled" status if not specified
117
+ url.searchParams.set("status", params.status ?? "enabled");
52
118
  if (params.pageFilter)
53
119
  url.searchParams.set("page", params.pageFilter);
54
120
  if (params.search)
@@ -64,9 +130,6 @@ export async function fetchResource({ baseUrl, apiKey, type, params = {}, fetchI
64
130
  headers: {
65
131
  Authorization: `Bearer ${apiKey}`,
66
132
  "Content-Type": "application/json",
67
- // Add cache-busting headers for better cache control
68
- "Cache-Control": "no-cache, no-store, must-revalidate",
69
- Pragma: "no-cache",
70
133
  },
71
134
  cache: finalCacheOptions.cache,
72
135
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fragment-headless-sdk",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,6 +18,7 @@
18
18
  "build": "tsc"
19
19
  },
20
20
  "dependencies": {
21
+ "@heroicons/react": "^2.2.0",
21
22
  "react": "^19.1.0"
22
23
  },
23
24
  "devDependencies": {
package/readme.md CHANGED
@@ -2,7 +2,19 @@
2
2
 
3
3
  The official SDK for integrating with Fragment-Shopify CMS. Provides React components, TypeScript types, and utilities for rendering published sections in headless Shopify storefronts.
4
4
 
5
- ## ✨ What's New in v2.1.2
5
+ ## ✨ What's New in v2.1.4
6
+
7
+ 🎨 **Countdown Timer Enhancements** - Improved countdown timer visual design with cleaner appearance
8
+ ⚡ **Enhanced Announcement Layouts** - Countdown timers can now display alongside buttons
9
+
10
+ ### Previous Release (v2.1.3)
11
+
12
+ ⚡ **Request Deduplication** - Intelligent request deduplication prevents identical concurrent API calls
13
+ 🚀 **Enhanced Caching** - Improved caching strategy with 60-second revalidation for better performance
14
+ 🔧 **Optimized Fetching** - Consistent parameter handling and smarter cache key generation
15
+ 🛡️ **Anti-Spam Protection** - Built-in protection against redundant API requests
16
+
17
+ ### Previous Release (v2.1.2)
6
18
 
7
19
  📚 **Enhanced Documentation** - Comprehensive documentation updates highlighting new click tracking features
8
20
  📖 **Better Examples** - Improved usage examples and feature descriptions
@@ -76,6 +88,15 @@ Fragment-Shopify App (CMS) → API Endpoint → fragment-headless-sdk (Consumer)
76
88
  - 📝 **Type Safety**: Enhanced TypeScript interfaces for all styling options
77
89
  - 🔄 **Backward Compatible**: Works seamlessly with existing Hero implementations
78
90
 
91
+ ### Request Deduplication & Performance System (v2.1.3+)
92
+
93
+ - ⚡ **Smart Deduplication**: Prevents identical concurrent API requests automatically
94
+ - 🚀 **Optimized Caching**: 60-second cache revalidation for optimal performance vs freshness
95
+ - 🔧 **Consistent Parameters**: Normalized parameter handling ensures reliable cache keys
96
+ - 🛡️ **Anti-Spam Protection**: Built-in protection against redundant API calls
97
+ - 📊 **Performance Monitoring**: Console logging for deduplication events and debugging
98
+ - 🔄 **Graceful Fallback**: Failed requests retry automatically without affecting user experience
99
+
79
100
  ### Enhanced Click Tracking System (v2.1.1+)
80
101
 
81
102
  - 🎯 **Separated Concerns**: Button destinations and click tracking handled independently
@@ -183,11 +204,20 @@ const heroBanners = await fetchResource({
183
204
  type: ResourceType.HeroBanners,
184
205
  });
185
206
 
186
- // Fetch announcements
207
+ // Fetch announcements with optional parameters
187
208
  const announcements = await fetchResource({
188
209
  baseUrl: process.env.EXTERNAL_API_URL,
189
210
  apiKey: process.env.FRAGMENT_API_KEY,
190
211
  type: ResourceType.Announcements,
212
+ params: {
213
+ status: "enabled", // Only fetch enabled announcements
214
+ limit: 10, // Limit to 10 items
215
+ search: "sale", // Search for announcements containing "sale"
216
+ },
217
+ cacheOptions: {
218
+ revalidate: 30, // Custom 30-second cache revalidation
219
+ tags: ["announcements"], // Custom cache tags
220
+ },
191
221
  });
192
222
  ```
193
223
 
@@ -650,13 +680,30 @@ const advancedHeroContent = {
650
680
 
651
681
  ### `fetchResource<T>(params)`
652
682
 
653
- Fetches sections from your Fragment-Shopify app.
683
+ Fetches sections from your Fragment-Shopify app with intelligent request deduplication and caching.
654
684
 
655
685
  **Parameters:**
656
686
 
657
687
  - `baseUrl: string` - URL of your Fragment-Shopify app
658
688
  - `apiKey: string` - Fragment API key (format: `keyId:secret`)
659
689
  - `type: ResourceType` - Type of resource to fetch
690
+ - `params?: ListParams` - Optional filtering and pagination parameters
691
+ - `fetchImpl?: typeof fetch` - Optional custom fetch implementation
692
+ - `cacheOptions?: CacheOptions` - Optional cache configuration
693
+
694
+ **ListParams Options:**
695
+
696
+ - `status?: "enabled" | "disabled"` - Filter by status (defaults to "enabled")
697
+ - `page?: number` - Page number for pagination (defaults to 1)
698
+ - `limit?: number` - Items per page (defaults to 25, max 100)
699
+ - `search?: string` - Search query for filtering
700
+ - `pageFilter?: string` - Filter by page path (e.g., "/collections/sale")
701
+
702
+ **CacheOptions:**
703
+
704
+ - `cache?: RequestCache` - Request cache mode (defaults to "force-cache")
705
+ - `revalidate?: number | false` - Next.js revalidation time in seconds (defaults to 60)
706
+ - `tags?: string[]` - Next.js cache tags for selective invalidation
660
707
 
661
708
  **ResourceType Options:**
662
709
 
@@ -665,6 +712,13 @@ Fetches sections from your Fragment-Shopify app.
665
712
 
666
713
  **Returns:** `Promise<T[]>` - Array of fetched resources
667
714
 
715
+ **Performance Features:**
716
+
717
+ - **Request Deduplication**: Identical concurrent requests are automatically deduplicated
718
+ - **Smart Caching**: 60-second cache revalidation balances performance and freshness
719
+ - **Parameter Normalization**: Consistent cache key generation for reliable deduplication
720
+ - **Error Handling**: Graceful fallbacks with automatic retry on failed requests
721
+
668
722
  ---
669
723
 
670
724
  ## 🧩 Components
package/CHANGELOG.md DELETED
@@ -1,436 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [2.1.2] - 2025-10-30
9
-
10
- ### 📚 Documentation Updates
11
-
12
- - **Enhanced README** – Updated documentation to highlight new click tracking features
13
- - **Feature Highlights** – Added comprehensive documentation for the enhanced click tracking system
14
- - **Usage Examples** – Improved examples showing the separation of button destinations and tracking
15
-
16
- ## [2.1.1] - 2025-10-30
17
-
18
- ### 🎯 Enhanced Click Tracking System
19
-
20
- - **Improved Click Tracking Architecture** – Separated button destinations from click tracking for better user experience
21
- - `buttonHref` now contains the actual destination URL (no redirect)
22
- - `clickHref` contains the tracking URL for metrics collection
23
- - Users go directly to intended destinations instead of through redirects
24
- - **New `fireClickMetric()` Function** – Advanced click tracking without relying on redirects
25
- - Uses `fetch()` with `no-cors` mode and `keepalive` for reliable tracking
26
- - Falls back to Image pixel tracking for maximum compatibility
27
- - Handles server-side rendering gracefully
28
- - **Removed Automatic New Tab Behavior** – Links no longer force `target="_blank"`
29
- - Provides more natural user experience
30
- - Allows developers to control link behavior explicitly
31
- - Maintains accessibility with proper ARIA labels
32
-
33
- ### 🛠 Technical Improvements
34
-
35
- - **Enhanced Component Props** – Both Hero and Announcement components now accept separate tracking parameters
36
- - `buttonHref` for the actual destination
37
- - `clickHref` for tracking metrics
38
- - **Better Error Handling** – Click tracking fails gracefully without affecting user experience
39
- - **Performance Optimized** – Non-blocking click tracking that doesn't delay navigation
40
- - **Cross-Browser Compatible** – Works across all modern browsers with appropriate fallbacks
41
-
42
- ### 📝 Usage Examples
43
-
44
- ```typescript
45
- // The SDK automatically handles the separation of concerns
46
- const heroContent = {
47
- title: "Shop Now",
48
- buttonText: "Get Started",
49
- buttonLink: "https://example.com/products", // Direct destination
50
- clickUrlBase: "https://tracking.example.com/click", // Tracking base
51
- // SDK automatically creates:
52
- // - buttonHref: "https://example.com/products" (direct link)
53
- // - clickHref: "https://tracking.example.com/click?u=..." (tracking)
54
- };
55
-
56
- <Hero content={heroContent} />;
57
- ```
58
-
59
- ### ⚡ Performance Benefits
60
-
61
- - **Faster Navigation** – Users go directly to destinations without redirect delays
62
- - **Reliable Tracking** – Click metrics are captured even if users navigate away quickly
63
- - **Better SEO** – Direct links improve search engine crawling and indexing
64
- - **Enhanced UX** – More predictable link behavior for better user experience
65
-
66
- ### 🔄 Backward Compatibility
67
-
68
- - **No Breaking Changes** – All existing implementations continue to work
69
- - **Automatic Upgrade** – New tracking system activates automatically when `clickUrlBase` is present
70
- - **Legacy Support** – Existing tracking URLs continue to function as before
71
-
72
- ## [2.1.0] - 2025-10-27
73
-
74
- ### 🎨 Enhanced Hero Styling System
75
-
76
- - **New Hero Resolvers Utility** – Comprehensive utility system for advanced Hero component customization
77
- - `resolveHeroColors()` – Intelligent color resolution with fallback handling
78
- - `resolveHeroTypography()` – Typography settings with font family, size, and line height control
79
- - `resolveContentWidthClass()` – Dynamic content width management
80
- - `resolvePosition()` – Content positioning (left, center, right alignment)
81
- - `resolveHeight()` – Flexible height configuration
82
- - `renderText()` – Unified text rendering with typography and styling support
83
-
84
- ### ✨ Advanced Typography Features
85
-
86
- - **Font Family Support** – Built-in support for popular font families:
87
- - Roboto, Open Sans, Lato, Montserrat, Poppins, Inter, Nunito Sans, Source Sans Pro
88
- - Custom font family support through `FontKey` type system
89
- - **Responsive Typography** – Granular control over font sizes and line heights
90
- - Separate title and description typography settings
91
- - Tailwind CSS class integration for responsive design
92
- - **Typography Tokens** – New styling tokens for enhanced typography control:
93
- - `titleFontSize`, `titleLineHeight`, `titleFont`
94
- - `descriptionFontSize`, `descriptionLineHeight`, `descriptionFont`
95
-
96
- ### 🏗️ Layout & Positioning Enhancements
97
-
98
- - **Content Positioning** – New positioning system for Hero content alignment
99
- - Left, center, and right alignment options
100
- - Responsive positioning with proper text alignment
101
- - **Content Width Control** – Dynamic content width management
102
- - Configurable content container widths
103
- - Responsive design integration
104
- - **Height Management** – Flexible height configuration system
105
- - Custom height classes support
106
- - Default height fallbacks
107
-
108
- ### 🎯 Developer Experience Improvements
109
-
110
- - **Type Safety** – Enhanced TypeScript interfaces for all new features
111
- - `HeroResolvedColors` interface for color resolution
112
- - `HeroTypographySettings` interface for typography configuration
113
- - `FontKey` type for font family validation
114
- - **Utility Functions** – New helper functions for common operations
115
- - `joinClassNames()` – Safe CSS class concatenation
116
- - `fallbackColor()` – Color value validation with fallbacks
117
- - **Better Defaults** – Comprehensive default values for all styling options
118
- - `DEFAULT_COLORS` for color fallbacks
119
- - `DEFAULT_TYPOGRAPHY` for typography defaults
120
- - `FONT_FAMILY_MAP` for font family mappings
121
-
122
- ### 🔄 Backward Compatibility
123
-
124
- - **Seamless Migration** – All existing Hero components continue to work without changes
125
- - **Progressive Enhancement** – New features are opt-in and don't affect existing implementations
126
- - **Legacy Support** – Existing styling approaches remain fully supported
127
-
128
- ### 📝 Usage Examples
129
-
130
- ```typescript
131
- // Enhanced Hero with new typography and positioning
132
- const heroContent = {
133
- title: "Welcome to Our Store",
134
- description: "Discover amazing products",
135
- buttonText: "Shop Now",
136
- buttonLink: "/products",
137
- imageUrl: "https://example.com/hero.jpg",
138
-
139
- styling: {
140
- tokens: {
141
- colors: {
142
- title: "#ffffff",
143
- text: "#f0f0f0",
144
- button: "#007bff",
145
- buttonText: "#ffffff",
146
- background: "#1a1a1a",
147
- },
148
- typography: {
149
- titleFont: "montserrat",
150
- titleFontSize: "text-6xl",
151
- titleLineHeight: "leading-tight",
152
- descriptionFont: "inter",
153
- descriptionFontSize: "text-xl",
154
- descriptionLineHeight: "leading-relaxed",
155
- },
156
- layout: {
157
- contentWidth: "max-w-4xl",
158
- position: "center",
159
- height: "min-h-screen",
160
- },
161
- },
162
- },
163
- };
164
- ```
165
-
166
- ### 🛠 Technical Improvements
167
-
168
- - **Performance Optimized** – Efficient color and typography resolution
169
- - **Memory Efficient** – Optimized utility functions with minimal overhead
170
- - **Tree Shakeable** – Individual utility functions can be imported separately
171
- - **CSS-in-JS Ready** – Full compatibility with styled-components and emotion
172
-
173
- ## [1.0.6] - 2025-10-16
174
-
175
- ### 🚀 Next.js Caching Fix
176
-
177
- - **Fixed Vercel/Next.js Caching Issues** – Resolved aggressive caching that prevented fresh data from appearing in production deployments
178
- - Added `cache: 'no-store'` by default for all `fetchResource()` calls
179
- - Added Next.js-specific `revalidate: 0` configuration
180
- - Added cache-busting headers (`Cache-Control`, `Pragma`) to prevent CDN caching
181
- - Smart environment detection for Next.js vs other frameworks
182
-
183
- ### ✨ New Cache Management Features
184
-
185
- - **Cache Configuration Options** – Added optional `cacheOptions` parameter to `fetchResource()`
186
- - `cache`: Control request cache mode (default: 'no-store' for fresh data)
187
- - `revalidate`: Next.js revalidation time in seconds (default: 0)
188
- - `tags`: Next.js cache tags for selective invalidation
189
- - **Cache Invalidation Utilities** – New helper functions for cache management
190
- - `revalidateFragmentCache()` – Invalidate all or specific Fragment caches
191
- - `revalidateResourceType()` – Invalidate cache for specific resource type
192
- - `revalidateAllFragmentCaches()` – Clear all Fragment-related caches
193
- - `createCacheTag()` / `createCacheTags()` – Generate cache tags
194
-
195
- ### 🛠 Technical Improvements
196
-
197
- - **Environment Detection** – Automatic Next.js environment detection for optimal cache settings
198
- - **Backward Compatibility** – All existing code continues to work without changes
199
- - **TypeScript Support** – Full type definitions for new cache options
200
-
201
- ### 📝 Usage Examples
202
-
203
- ```typescript
204
- // Default behavior - always fresh data (recommended)
205
- const announcements = await fetchResource({
206
- baseUrl: process.env.FRAGMENT_BASE_URL,
207
- apiKey: process.env.FRAGMENT_API_KEY,
208
- type: ResourceType.Announcements,
209
- });
210
-
211
- // Optional: Enable caching for performance
212
- const cachedHeroes = await fetchResource({
213
- baseUrl: process.env.FRAGMENT_BASE_URL,
214
- apiKey: process.env.FRAGMENT_API_KEY,
215
- type: ResourceType.HeroBanners,
216
- cacheOptions: {
217
- cache: "default",
218
- revalidate: 300, // 5 minutes
219
- },
220
- });
221
-
222
- // Cache invalidation (server-side only)
223
- import { revalidateResourceType } from "fragment-headless-sdk";
224
- await revalidateResourceType(ResourceType.Announcements);
225
- ```
226
-
227
- ### ⚠️ Migration Notes
228
-
229
- - **No breaking changes** – Existing code works without modification
230
- - **Fresh data by default** – Your database updates will now appear immediately in production
231
- - **Opt-in caching** – Use `cacheOptions` if you want to enable caching for performance
232
-
233
- ## [1.0.5] - 2025-09-28
234
-
235
- ### ✨ New Features
236
-
237
- - **Metrics Tracking** – Added built-in view and click tracking for both **Hero** and **Announcement** sections.
238
- - Each resource’s `content` object now includes two server-generated fields:
239
- - `impressionUrl` – 1×1 pixel URL automatically fired when the component enters the viewport.
240
- - `clickUrlBase` – base redirect URL used to record button clicks before sending the user to the final destination.
241
- - The SDK’s `<Hero>` and `<Announcement>` components now automatically:
242
- - trigger a view pixel when visible, and
243
- - wrap their CTA buttons with a signed click-tracking redirect.
244
-
245
- ### 🛠 Technical Notes
246
-
247
- - The `makeSignedMetricUrls` helper was refactored to attach `impressionUrl` and `clickUrlBase` **inside the `content` object** for each item returned by the API.
248
- - New client-side utilities exported from `utils`:
249
- - `buildClickUrl()` – safely appends the final destination (`&u=...`) to a signed `clickUrlBase`.
250
- - `fireImpressionWhenVisible()` – fires a pixel only once when an element is at least 30 % visible.
251
-
252
- ### ⚠️ Migration Notes
253
-
254
- - **No breaking changes.**
255
- Existing components continue to work; the new tracking is automatic when you upgrade to v1.0.5.
256
- - If you build custom CTAs outside the provided components, use the new helpers to track clicks and views manually.
257
-
258
- ## [1.0.4] - 2025-09-27
259
-
260
- - **Types:** `IHero` now includes `views_count: number` and `clicks_count: number`.
261
-
262
- ### 📝 Notes
263
-
264
- - No breaking changes..
265
-
266
- ## [1.0.3] - 2025-09-21
267
-
268
- ### 🎨 UI/UX Improvements
269
-
270
- - **Announcement Type Rename** - Changed `AnnouncementType.Announcement` to `AnnouncementType.Static` for better clarity
271
- - **Countdown Timer Styling** - Removed white background from countdown timer for cleaner appearance
272
- - **Layout Optimization** - Improved announcement banner layout with:
273
- - Removed top/bottom padding (`py-3`) for more compact design
274
- - Added 50px minimum height for consistent banner sizing
275
- - Enhanced vertical centering of all content elements
276
- - **Timer Digit Sizing** - Made countdown timer digits smaller and more compact:
277
- - Reduced digit size from 24×28px to 20×24px
278
- - Changed font size from `text-xl` to `text-base`
279
-
280
- ### 🔧 Technical Changes
281
-
282
- - Updated `announcementTypes` array to reflect new "Static" label
283
- - Improved flexbox layout for better vertical alignment
284
- - Maintained responsive design across all screen sizes
285
-
286
- ## [1.0.2] - 2025-09-20
287
-
288
- ### 🔄 Breaking Changes
289
-
290
- - **Component Naming** - Renamed `Banner` component and all related types to `Announcement`
291
-
292
- - `Banner` → `Announcement`
293
- - `IBannerContent` → `IAnnouncementContent`
294
- - `IBanner` → `IAnnouncement`
295
- - `BannerType` → `AnnouncementType`
296
- - `BannerStatus` → `AnnouncementStatus`
297
- - `BannerButton` → `AnnouncementButton`
298
- - `BannerStyles` → `AnnouncementStyles`
299
- - `bannerHtml` property → `announcementHtml`
300
-
301
- - **Resource Type Updates** - Updated resource type enums for consistency
302
- - `ResourceType.HeroSections` → `ResourceType.HeroBanners`
303
- - `ResourceType.Banners` → `ResourceType.Announcements`
304
-
305
- ### 🔗 API Endpoint Changes
306
-
307
- - Updated API endpoints to match new naming:
308
- - `/api/v1/hero-sections` → `/api/v1/hero-banners`
309
- - `/api/v1/banners` → `/api/v1/announcements`
310
-
311
- ### 📚 Documentation
312
-
313
- - Updated all documentation to reflect new component and type names
314
- - Updated README.md examples with new ResourceType values
315
- - Updated code examples throughout
316
-
317
- ### 🛠️ Migration Guide
318
-
319
- To update your existing code:
320
-
321
- ```typescript
322
- // Before (v1.0.1)
323
- import {
324
- Banner,
325
- BannerType,
326
- IBannerContent,
327
- ResourceType,
328
- } from "fragment-headless-sdk";
329
-
330
- const banners = await fetchResource({
331
- type: ResourceType.Banners,
332
- });
333
-
334
- <Banner content={bannerContent} type={BannerType.Standard} />;
335
-
336
- // After (v1.0.2)
337
- import {
338
- Announcement,
339
- AnnouncementType,
340
- IAnnouncementContent,
341
- ResourceType,
342
- } from "fragment-headless-sdk";
343
-
344
- const announcements = await fetchResource({
345
- type: ResourceType.Announcements,
346
- });
347
-
348
- <Announcement
349
- content={announcementContent}
350
- type={AnnouncementType.Announcement}
351
- />;
352
- ```
353
-
354
- ## [1.0.1] - 2025-09-07
355
-
356
- ### 🎉 Initial Release
357
-
358
- The official SDK for integrating with fragment-shopify CMS. Production-ready with full API key authentication support.
359
-
360
- ### Features
361
-
362
- - **Complete TypeScript Support** - Full type definitions for all components and API responses
363
- - **React Components** - Pre-built Hero and Announcement components with responsive design
364
- - **API Integration** - Built-in utilities for fetching sections from fragment-shopify app
365
- - **Production Ready** - Full API key authentication with v1 endpoints
366
- - **Tailwind CSS** - Styled components with customizable design system
367
-
368
- ### Components
369
-
370
- - **Hero Component** - Responsive hero sections with desktop/mobile variants
371
- - Support for images, videos, and call-to-action buttons
372
- - Customizable content and styling
373
- - **Announcement Component** - Flexible announcement bars with multiple display types
374
- - Standard, marquee, and countdown announcement variants
375
- - `AnnouncementButton` and `CountdownTimer` sub-components
376
-
377
- ### API Integration
378
-
379
- - **`fetchResource()` Function** - Simple API for fetching sections
380
- - **API Key Authentication** - Secure authentication using `keyId:secret` format
381
- - **v1 Endpoints** - Production endpoints (`/api/v1/announcements`, `/api/v1/hero-banners`)
382
- - **Error Handling** - Comprehensive error handling and logging
383
- - **Type Safety** - Full TypeScript support for all API responses
384
-
385
- ### Usage
386
-
387
- ```tsx
388
- import {
389
- fetchResource,
390
- ResourceType,
391
- Hero,
392
- Announcement,
393
- } from "fragment-headless-sdk";
394
-
395
- // Fetch data
396
- const heroes = await fetchResource({
397
- baseUrl: process.env.EXTERNAL_API_URL,
398
- apiKey: process.env.FRAGMENT_API_KEY,
399
- type: ResourceType.HeroBanners,
400
- });
401
-
402
- // Render components
403
- <Hero content={heroes[0]?.content} />;
404
- ```
405
-
406
- ### Environment Variables
407
-
408
- ```bash
409
- EXTERNAL_API_URL=https://your-fragment-app.vercel.app
410
- FRAGMENT_API_KEY=bh_a1b2c3d4e5f6:your-64-char-secret
411
- ```
412
-
413
- ---
414
-
415
- ## Upcoming Changes
416
-
417
- ### v1.1.0 (Planned)
418
-
419
- - Enhanced error handling with specific error types
420
- - Support for additional section types
421
- - Caching and performance optimizations
422
-
423
- ### v1.2.0 (Planned)
424
-
425
- - Real-time updates via webhooks
426
- - Advanced filtering and sorting options
427
- - Batch operations support
428
- - TypeScript strict mode compatibility
429
-
430
- ---
431
-
432
- ## Support
433
-
434
- - **Documentation**: [README.md](./README.md)
435
- - **NPM Package**: https://www.npmjs.com/package/fragment-shopify-sdk
436
- - **Issues**: Please report issues in the GitHub repository