@uptrademedia/site-kit 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-CWtoFJCO.d.mts +137 -0
- package/dist/api-CWtoFJCO.d.ts +137 -0
- package/dist/{api-QUIPJJCX.js → api-UBHLAPUG.js} +20 -20
- package/dist/api-UBHLAPUG.js.map +1 -0
- package/dist/{api-V3BA5PMX.mjs → api-XNF6Q5HO.mjs} +3 -3
- package/dist/api-XNF6Q5HO.mjs.map +1 -0
- package/dist/{chunk-QQB4FO4Q.js → chunk-AWAJEIZS.js} +11 -8
- package/dist/chunk-AWAJEIZS.js.map +1 -0
- package/dist/{chunk-MB3WR5KJ.mjs → chunk-CDJL2YGL.mjs} +61 -443
- package/dist/chunk-CDJL2YGL.mjs.map +1 -0
- package/dist/{chunk-TDK7DLCH.js → chunk-FLAA4ZJO.js} +59 -448
- package/dist/chunk-FLAA4ZJO.js.map +1 -0
- package/dist/{chunk-JGQPAXTL.mjs → chunk-H5AGHERY.mjs} +8 -5
- package/dist/chunk-H5AGHERY.mjs.map +1 -0
- package/dist/{chunk-VDI7KYME.js → chunk-IYVJGUYX.js} +8 -4
- package/dist/chunk-IYVJGUYX.js.map +1 -0
- package/dist/{chunk-FQVGK746.mjs → chunk-SKHOW2CI.mjs} +8 -4
- package/dist/chunk-SKHOW2CI.mjs.map +1 -0
- package/dist/cli/index.js +32 -25
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +32 -25
- package/dist/cli/index.mjs.map +1 -1
- package/dist/images/index.d.mts +49 -126
- package/dist/images/index.d.ts +49 -126
- package/dist/images/index.js +12 -12
- package/dist/images/index.mjs +1 -5
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +510 -106
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +440 -13
- package/dist/index.mjs.map +1 -1
- package/dist/{routing-CIOFpFCB.d.mts → routing-BT0RrBLk.d.mts} +14 -1
- package/dist/{routing-CF91y6NO.d.ts → routing-wmNSxSvP.d.ts} +14 -1
- package/dist/seo/index.d.mts +37 -4
- package/dist/seo/index.d.ts +37 -4
- package/dist/seo/index.js +49 -19
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/index.mjs +35 -6
- package/dist/seo/index.mjs.map +1 -1
- package/dist/seo/server.d.mts +15 -4
- package/dist/seo/server.d.ts +15 -4
- package/dist/seo/server.js +16 -16
- package/dist/seo/server.mjs +2 -2
- package/dist/{types-j8X4vUhB.d.mts → types-wf4dwNMO.d.mts} +5 -0
- package/dist/{types-j8X4vUhB.d.ts → types-wf4dwNMO.d.ts} +5 -0
- package/package.json +1 -1
- package/dist/api-QUIPJJCX.js.map +0 -1
- package/dist/api-V3BA5PMX.mjs.map +0 -1
- package/dist/chunk-FQVGK746.mjs.map +0 -1
- package/dist/chunk-JGQPAXTL.mjs.map +0 -1
- package/dist/chunk-MB3WR5KJ.mjs.map +0 -1
- package/dist/chunk-QQB4FO4Q.js.map +0 -1
- package/dist/chunk-TDK7DLCH.js.map +0 -1
- package/dist/chunk-VDI7KYME.js.map +0 -1
|
@@ -1,442 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { EngageWidget } from './chunk-DYM5ML2V.mjs';
|
|
4
|
-
import { configureFormsApi } from './chunk-SMUFNQLM.mjs';
|
|
5
|
-
import { createContext, useContext, useState, useRef, useCallback, useEffect, useMemo, Suspense } from 'react';
|
|
6
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
function useSignal() {
|
|
10
|
-
const context = useContext(SignalContext);
|
|
11
|
-
if (!context) {
|
|
12
|
-
throw new Error("useSignal must be used within a SignalBridge");
|
|
13
|
-
}
|
|
14
|
-
return context;
|
|
15
|
-
}
|
|
16
|
-
function getApiConfig() {
|
|
17
|
-
const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
|
|
18
|
-
const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
|
|
19
|
-
return { apiUrl, apiKey };
|
|
20
|
-
}
|
|
21
|
-
function getVisitorId() {
|
|
22
|
-
if (typeof window === "undefined") return "";
|
|
23
|
-
const key = "_uptrade_vid";
|
|
24
|
-
let visitorId = localStorage.getItem(key);
|
|
25
|
-
if (!visitorId) {
|
|
26
|
-
visitorId = crypto.randomUUID();
|
|
27
|
-
localStorage.setItem(key, visitorId);
|
|
28
|
-
}
|
|
29
|
-
return visitorId;
|
|
30
|
-
}
|
|
31
|
-
function getSessionId() {
|
|
32
|
-
if (typeof window === "undefined") return "";
|
|
33
|
-
const key = "_uptrade_sid";
|
|
34
|
-
let sessionId = sessionStorage.getItem(key);
|
|
35
|
-
if (!sessionId) {
|
|
36
|
-
sessionId = crypto.randomUUID();
|
|
37
|
-
sessionStorage.setItem(key, sessionId);
|
|
38
|
-
}
|
|
39
|
-
return sessionId;
|
|
40
|
-
}
|
|
41
|
-
function getDeviceType() {
|
|
42
|
-
if (typeof window === "undefined") return "desktop";
|
|
43
|
-
const ua = navigator.userAgent;
|
|
44
|
-
if (/tablet|ipad|playbook|silk/i.test(ua)) return "tablet";
|
|
45
|
-
if (/mobile|iphone|ipod|android|blackberry|opera mini|iemobile/i.test(ua)) return "mobile";
|
|
46
|
-
return "desktop";
|
|
47
|
-
}
|
|
48
|
-
function getBrowser() {
|
|
49
|
-
if (typeof window === "undefined") return "unknown";
|
|
50
|
-
const ua = navigator.userAgent;
|
|
51
|
-
if (ua.includes("Firefox")) return "Firefox";
|
|
52
|
-
if (ua.includes("Edg")) return "Edge";
|
|
53
|
-
if (ua.includes("Chrome")) return "Chrome";
|
|
54
|
-
if (ua.includes("Safari")) return "Safari";
|
|
55
|
-
return "Other";
|
|
56
|
-
}
|
|
57
|
-
function getOS() {
|
|
58
|
-
if (typeof window === "undefined") return "unknown";
|
|
59
|
-
const ua = navigator.userAgent;
|
|
60
|
-
if (ua.includes("Windows")) return "Windows";
|
|
61
|
-
if (ua.includes("Mac")) return "macOS";
|
|
62
|
-
if (ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
|
|
63
|
-
if (ua.includes("Android")) return "Android";
|
|
64
|
-
if (ua.includes("Linux")) return "Linux";
|
|
65
|
-
return "Other";
|
|
66
|
-
}
|
|
67
|
-
function SignalBridge({
|
|
68
|
-
enabled = true,
|
|
69
|
-
realtime = true,
|
|
70
|
-
experiments = true,
|
|
71
|
-
behaviorTracking = true,
|
|
72
|
-
children
|
|
73
|
-
}) {
|
|
74
|
-
const [config, setConfig] = useState(null);
|
|
75
|
-
const [loading, setLoading] = useState(true);
|
|
76
|
-
const [error, setError] = useState(null);
|
|
77
|
-
const eventSourceRef = useRef(null);
|
|
78
|
-
const eventQueueRef = useRef([]);
|
|
79
|
-
const flushTimeoutRef = useRef(null);
|
|
80
|
-
const assignmentsRef = useRef(/* @__PURE__ */ new Map());
|
|
81
|
-
const pageLoadTimeRef = useRef(Date.now());
|
|
82
|
-
const scrollDepthRef = useRef(0);
|
|
83
|
-
const clickCountRef = useRef(0);
|
|
84
|
-
const { apiUrl, apiKey } = getApiConfig();
|
|
85
|
-
const fetchConfig = useCallback(async () => {
|
|
86
|
-
if (!apiKey || !enabled) {
|
|
87
|
-
setLoading(false);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
try {
|
|
91
|
-
const response = await fetch(`${apiUrl}/api/public/signal/config`, {
|
|
92
|
-
headers: {
|
|
93
|
-
"x-api-key": apiKey,
|
|
94
|
-
"x-visitor-id": getVisitorId()
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
if (!response.ok) {
|
|
98
|
-
throw new Error(`Failed to fetch Signal config: ${response.statusText}`);
|
|
99
|
-
}
|
|
100
|
-
const data = await response.json();
|
|
101
|
-
setConfig(data.config);
|
|
102
|
-
setError(null);
|
|
103
|
-
if (experiments && data.config?.experiments) {
|
|
104
|
-
for (const exp of data.config.experiments) {
|
|
105
|
-
if (exp.status === "running") {
|
|
106
|
-
await loadExperimentAssignment(exp.id);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
} catch (err) {
|
|
111
|
-
console.error("[Signal] Config fetch error:", err);
|
|
112
|
-
setError(err);
|
|
113
|
-
} finally {
|
|
114
|
-
setLoading(false);
|
|
115
|
-
}
|
|
116
|
-
}, [apiUrl, apiKey, enabled, experiments]);
|
|
117
|
-
const connectSSE = useCallback(() => {
|
|
118
|
-
if (!apiKey || !enabled || !realtime) return;
|
|
119
|
-
if (eventSourceRef.current) {
|
|
120
|
-
eventSourceRef.current.close();
|
|
121
|
-
}
|
|
122
|
-
const url = `${apiUrl}/api/public/signal/stream?key=${apiKey}`;
|
|
123
|
-
const eventSource = new EventSource(url);
|
|
124
|
-
eventSource.addEventListener("config_update", (e) => {
|
|
125
|
-
try {
|
|
126
|
-
const { config: newConfig, version } = JSON.parse(e.data);
|
|
127
|
-
setConfig((prev) => {
|
|
128
|
-
if (prev?.version !== version) {
|
|
129
|
-
console.log("[Signal] Config updated to version:", version);
|
|
130
|
-
return newConfig;
|
|
131
|
-
}
|
|
132
|
-
return prev;
|
|
133
|
-
});
|
|
134
|
-
} catch (err) {
|
|
135
|
-
console.error("[Signal] SSE parse error:", err);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
eventSource.addEventListener("experiment_update", (e) => {
|
|
139
|
-
try {
|
|
140
|
-
const { experiment_id, action } = JSON.parse(e.data);
|
|
141
|
-
if (action === "started" || action === "updated") {
|
|
142
|
-
loadExperimentAssignment(experiment_id);
|
|
143
|
-
} else if (action === "stopped") {
|
|
144
|
-
assignmentsRef.current.delete(experiment_id);
|
|
145
|
-
}
|
|
146
|
-
} catch (err) {
|
|
147
|
-
console.error("[Signal] Experiment update error:", err);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
eventSource.onerror = () => {
|
|
151
|
-
console.warn("[Signal] SSE connection error, reconnecting...");
|
|
152
|
-
eventSource.close();
|
|
153
|
-
setTimeout(connectSSE, 5e3);
|
|
154
|
-
};
|
|
155
|
-
eventSourceRef.current = eventSource;
|
|
156
|
-
}, [apiUrl, apiKey, enabled, realtime]);
|
|
157
|
-
const loadExperimentAssignment = useCallback(async (experimentId) => {
|
|
158
|
-
const storageKey = `_signal_exp_${experimentId}`;
|
|
159
|
-
const stored = localStorage.getItem(storageKey);
|
|
160
|
-
if (stored) {
|
|
161
|
-
try {
|
|
162
|
-
const assignment = JSON.parse(stored);
|
|
163
|
-
if (assignment.expires > Date.now()) {
|
|
164
|
-
assignmentsRef.current.set(experimentId, assignment);
|
|
165
|
-
return assignment;
|
|
166
|
-
}
|
|
167
|
-
} catch {
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
try {
|
|
171
|
-
const response = await fetch(`${apiUrl}/api/public/signal/experiment/${experimentId}`, {
|
|
172
|
-
headers: {
|
|
173
|
-
"x-api-key": apiKey,
|
|
174
|
-
"x-visitor-id": getVisitorId()
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
if (!response.ok) return null;
|
|
178
|
-
const assignment = await response.json();
|
|
179
|
-
localStorage.setItem(storageKey, JSON.stringify(assignment));
|
|
180
|
-
assignmentsRef.current.set(experimentId, assignment);
|
|
181
|
-
return assignment;
|
|
182
|
-
} catch (err) {
|
|
183
|
-
console.error("[Signal] Experiment assignment error:", err);
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
}, [apiUrl, apiKey]);
|
|
187
|
-
const getExperiment = useCallback((experimentId) => {
|
|
188
|
-
return assignmentsRef.current.get(experimentId) || null;
|
|
189
|
-
}, []);
|
|
190
|
-
const flushEvents = useCallback(async () => {
|
|
191
|
-
if (eventQueueRef.current.length === 0) return;
|
|
192
|
-
const events = [...eventQueueRef.current];
|
|
193
|
-
eventQueueRef.current = [];
|
|
194
|
-
try {
|
|
195
|
-
await fetch(`${apiUrl}/api/public/signal/events`, {
|
|
196
|
-
method: "POST",
|
|
197
|
-
headers: {
|
|
198
|
-
"Content-Type": "application/json",
|
|
199
|
-
"x-api-key": apiKey
|
|
200
|
-
},
|
|
201
|
-
body: JSON.stringify({
|
|
202
|
-
visitor_id: getVisitorId(),
|
|
203
|
-
session_id: getSessionId(),
|
|
204
|
-
events
|
|
205
|
-
})
|
|
206
|
-
});
|
|
207
|
-
} catch (err) {
|
|
208
|
-
eventQueueRef.current = [...events, ...eventQueueRef.current];
|
|
209
|
-
console.error("[Signal] Event flush error:", err);
|
|
210
|
-
}
|
|
211
|
-
}, [apiUrl, apiKey]);
|
|
212
|
-
const trackEvent = useCallback((event) => {
|
|
213
|
-
if (!enabled || !apiKey) return;
|
|
214
|
-
const enrichedEvent = {
|
|
215
|
-
...event,
|
|
216
|
-
page_url: window.location.href,
|
|
217
|
-
page_path: window.location.pathname,
|
|
218
|
-
referrer: document.referrer,
|
|
219
|
-
device_type: getDeviceType(),
|
|
220
|
-
browser: getBrowser(),
|
|
221
|
-
os: getOS(),
|
|
222
|
-
viewport_width: window.innerWidth,
|
|
223
|
-
viewport_height: window.innerHeight,
|
|
224
|
-
time_on_page: Date.now() - pageLoadTimeRef.current,
|
|
225
|
-
scroll_depth: scrollDepthRef.current,
|
|
226
|
-
click_count: clickCountRef.current,
|
|
227
|
-
experiments: Array.from(assignmentsRef.current.values()).map((a) => ({
|
|
228
|
-
experiment_id: a.experiment_id,
|
|
229
|
-
variant_key: a.variant_key
|
|
230
|
-
})),
|
|
231
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
232
|
-
};
|
|
233
|
-
eventQueueRef.current.push(enrichedEvent);
|
|
234
|
-
if (flushTimeoutRef.current) {
|
|
235
|
-
clearTimeout(flushTimeoutRef.current);
|
|
236
|
-
}
|
|
237
|
-
flushTimeoutRef.current = setTimeout(flushEvents, 1e3);
|
|
238
|
-
}, [enabled, apiKey, flushEvents]);
|
|
239
|
-
const trackOutcome = useCallback(async (outcome) => {
|
|
240
|
-
if (!enabled || !apiKey) return;
|
|
241
|
-
try {
|
|
242
|
-
await fetch(`${apiUrl}/api/public/signal/outcome`, {
|
|
243
|
-
method: "POST",
|
|
244
|
-
headers: {
|
|
245
|
-
"Content-Type": "application/json",
|
|
246
|
-
"x-api-key": apiKey
|
|
247
|
-
},
|
|
248
|
-
body: JSON.stringify({
|
|
249
|
-
...outcome,
|
|
250
|
-
visitor_id: getVisitorId(),
|
|
251
|
-
session_id: getSessionId(),
|
|
252
|
-
experiments: Array.from(assignmentsRef.current.keys()),
|
|
253
|
-
page_url: window.location.href,
|
|
254
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
255
|
-
})
|
|
256
|
-
});
|
|
257
|
-
} catch (err) {
|
|
258
|
-
console.error("[Signal] Outcome tracking error:", err);
|
|
259
|
-
}
|
|
260
|
-
}, [apiUrl, apiKey, enabled]);
|
|
261
|
-
useEffect(() => {
|
|
262
|
-
if (!behaviorTracking || typeof window === "undefined") return;
|
|
263
|
-
const handleScroll = () => {
|
|
264
|
-
const scrollTop = window.scrollY;
|
|
265
|
-
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
|
266
|
-
const depth = docHeight > 0 ? Math.round(scrollTop / docHeight * 100) : 0;
|
|
267
|
-
scrollDepthRef.current = Math.max(scrollDepthRef.current, depth);
|
|
268
|
-
};
|
|
269
|
-
const handleClick = () => {
|
|
270
|
-
clickCountRef.current++;
|
|
271
|
-
};
|
|
272
|
-
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
273
|
-
window.addEventListener("click", handleClick);
|
|
274
|
-
const handleVisibilityChange = () => {
|
|
275
|
-
if (document.visibilityState === "hidden") {
|
|
276
|
-
flushEvents();
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
280
|
-
window.addEventListener("beforeunload", flushEvents);
|
|
281
|
-
return () => {
|
|
282
|
-
window.removeEventListener("scroll", handleScroll);
|
|
283
|
-
window.removeEventListener("click", handleClick);
|
|
284
|
-
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
285
|
-
window.removeEventListener("beforeunload", flushEvents);
|
|
286
|
-
};
|
|
287
|
-
}, [behaviorTracking, flushEvents]);
|
|
288
|
-
useEffect(() => {
|
|
289
|
-
fetchConfig();
|
|
290
|
-
}, [fetchConfig]);
|
|
291
|
-
useEffect(() => {
|
|
292
|
-
if (config && realtime) {
|
|
293
|
-
connectSSE();
|
|
294
|
-
}
|
|
295
|
-
return () => {
|
|
296
|
-
if (eventSourceRef.current) {
|
|
297
|
-
eventSourceRef.current.close();
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
}, [config, realtime, connectSSE]);
|
|
301
|
-
const contextValue = useMemo(() => ({
|
|
302
|
-
config,
|
|
303
|
-
loading,
|
|
304
|
-
error,
|
|
305
|
-
trackEvent,
|
|
306
|
-
trackOutcome,
|
|
307
|
-
getExperiment,
|
|
308
|
-
refreshConfig: fetchConfig
|
|
309
|
-
}), [config, loading, error, trackEvent, trackOutcome, getExperiment, fetchConfig]);
|
|
310
|
-
return /* @__PURE__ */ jsx(SignalContext.Provider, { value: contextValue, children });
|
|
311
|
-
}
|
|
312
|
-
function useSignalConfig() {
|
|
313
|
-
const { config } = useSignal();
|
|
314
|
-
return config;
|
|
315
|
-
}
|
|
316
|
-
function useSignalEvent() {
|
|
317
|
-
const { trackEvent } = useSignal();
|
|
318
|
-
return trackEvent;
|
|
319
|
-
}
|
|
320
|
-
function useSignalOutcome() {
|
|
321
|
-
const { trackOutcome } = useSignal();
|
|
322
|
-
return { trackOutcome };
|
|
323
|
-
}
|
|
324
|
-
function useSignalExperiment(experimentId) {
|
|
325
|
-
const { getExperiment, config } = useSignal();
|
|
326
|
-
const assignment = getExperiment(experimentId);
|
|
327
|
-
const experiment = config?.experiments?.find((e) => e.id === experimentId);
|
|
328
|
-
const isRunning = experiment?.status === "running";
|
|
329
|
-
return {
|
|
330
|
-
assignment: isRunning ? assignment : null,
|
|
331
|
-
variant: isRunning && assignment ? assignment.variant_key : null,
|
|
332
|
-
isControl: !assignment || assignment.variant_key === "control"
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
var SiteKitContext = createContext(null);
|
|
336
|
-
function useSiteKit() {
|
|
337
|
-
const context = useContext(SiteKitContext);
|
|
338
|
-
if (!context) {
|
|
339
|
-
throw new Error("useSiteKit must be used within a SiteKitProvider");
|
|
340
|
-
}
|
|
341
|
-
return context;
|
|
342
|
-
}
|
|
343
|
-
function SiteKitProvider({
|
|
344
|
-
children,
|
|
345
|
-
apiKey,
|
|
346
|
-
apiUrl,
|
|
347
|
-
signalUrl,
|
|
348
|
-
analytics,
|
|
349
|
-
engage,
|
|
350
|
-
forms,
|
|
351
|
-
signal,
|
|
352
|
-
debug = false
|
|
353
|
-
}) {
|
|
354
|
-
const finalApiUrl = apiUrl || "https://api.uptrademedia.com";
|
|
355
|
-
const finalSignalUrl = signalUrl || "https://signal.uptrademedia.com";
|
|
356
|
-
if (!apiKey) {
|
|
357
|
-
console.error("@uptrade/site-kit: No API key provided. Set NEXT_PUBLIC_UPTRADE_API_KEY environment variable.");
|
|
358
|
-
}
|
|
359
|
-
useEffect(() => {
|
|
360
|
-
if (typeof window !== "undefined") {
|
|
361
|
-
window.__SITE_KIT_API_URL__ = finalApiUrl;
|
|
362
|
-
window.__SITE_KIT_SIGNAL_URL__ = finalSignalUrl;
|
|
363
|
-
window.__SITE_KIT_API_KEY__ = apiKey;
|
|
364
|
-
window.__SITE_KIT_DEBUG__ = debug;
|
|
365
|
-
}
|
|
366
|
-
if (apiKey) {
|
|
367
|
-
configureFormsApi({
|
|
368
|
-
baseUrl: finalApiUrl,
|
|
369
|
-
apiKey
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
}, [finalApiUrl, finalSignalUrl, apiKey, debug]);
|
|
373
|
-
const contextValue = useMemo(
|
|
374
|
-
() => ({
|
|
375
|
-
apiUrl: finalApiUrl,
|
|
376
|
-
signalUrl: finalSignalUrl,
|
|
377
|
-
apiKey,
|
|
378
|
-
analytics,
|
|
379
|
-
engage,
|
|
380
|
-
forms,
|
|
381
|
-
signal,
|
|
382
|
-
debug,
|
|
383
|
-
isReady: true
|
|
384
|
-
}),
|
|
385
|
-
[finalApiUrl, finalSignalUrl, apiKey, analytics, engage, forms, signal, debug]
|
|
386
|
-
);
|
|
387
|
-
let content = /* @__PURE__ */ jsx(Fragment, { children });
|
|
388
|
-
if (signal?.enabled) {
|
|
389
|
-
content = /* @__PURE__ */ jsx(
|
|
390
|
-
SignalBridge,
|
|
391
|
-
{
|
|
392
|
-
enabled: signal.enabled,
|
|
393
|
-
realtime: signal.realtime !== false,
|
|
394
|
-
experiments: signal.experiments !== false,
|
|
395
|
-
behaviorTracking: signal.behaviorTracking !== false,
|
|
396
|
-
children: content
|
|
397
|
-
}
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
if (analytics?.enabled) {
|
|
401
|
-
content = /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
|
|
402
|
-
AnalyticsProvider,
|
|
403
|
-
{
|
|
404
|
-
apiUrl: finalApiUrl,
|
|
405
|
-
apiKey,
|
|
406
|
-
trackPageViews: analytics.trackPageViews !== false,
|
|
407
|
-
trackWebVitals: analytics.trackWebVitals !== false,
|
|
408
|
-
trackScrollDepth: analytics.trackScrollDepth !== false,
|
|
409
|
-
trackClicks: analytics.trackClicks !== false,
|
|
410
|
-
debug,
|
|
411
|
-
children: content
|
|
412
|
-
}
|
|
413
|
-
) });
|
|
414
|
-
}
|
|
415
|
-
if (engage?.enabled) {
|
|
416
|
-
content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
417
|
-
content,
|
|
418
|
-
/* @__PURE__ */ jsx(
|
|
419
|
-
EngageWidget,
|
|
420
|
-
{
|
|
421
|
-
apiUrl: finalApiUrl,
|
|
422
|
-
apiKey,
|
|
423
|
-
position: engage.position || "bottom-right",
|
|
424
|
-
chatEnabled: engage.chatEnabled !== false
|
|
425
|
-
}
|
|
426
|
-
)
|
|
427
|
-
] });
|
|
428
|
-
}
|
|
429
|
-
content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
430
|
-
content,
|
|
431
|
-
/* @__PURE__ */ jsx(SitemapSync, { debug })
|
|
432
|
-
] });
|
|
433
|
-
return /* @__PURE__ */ jsx(SiteKitContext.Provider, { value: contextValue, children: content });
|
|
434
|
-
}
|
|
4
|
+
// src/images/ManagedImage.tsx
|
|
435
5
|
var isDevMode = () => {
|
|
436
6
|
if (typeof window === "undefined") return false;
|
|
437
7
|
return process.env.NODE_ENV === "development" || window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.search.includes("uptrade_dev=true");
|
|
438
8
|
};
|
|
439
9
|
function ManagedImage({
|
|
10
|
+
apiKey = process.env.NEXT_PUBLIC_UPTRADE_API_KEY,
|
|
11
|
+
apiUrl = process.env.NEXT_PUBLIC_UPTRADE_API_URL || "https://api.uptrademedia.com",
|
|
440
12
|
slotId,
|
|
441
13
|
pagePath,
|
|
442
14
|
alt,
|
|
@@ -452,7 +24,6 @@ function ManagedImage({
|
|
|
452
24
|
style,
|
|
453
25
|
forceDevMode
|
|
454
26
|
}) {
|
|
455
|
-
const context = useSiteKit();
|
|
456
27
|
const [imageData, setImageData] = useState(null);
|
|
457
28
|
const [loading, setLoading] = useState(true);
|
|
458
29
|
const [error, setError] = useState(null);
|
|
@@ -460,18 +31,18 @@ function ManagedImage({
|
|
|
460
31
|
const [devMode] = useState(() => forceDevMode || isDevMode());
|
|
461
32
|
const currentPath = pagePath ?? (typeof window !== "undefined" ? window.location.pathname : "/");
|
|
462
33
|
const fetchImage = useCallback(async () => {
|
|
463
|
-
if (!
|
|
34
|
+
if (!apiKey || !apiUrl) {
|
|
464
35
|
setLoading(false);
|
|
465
36
|
return;
|
|
466
37
|
}
|
|
467
38
|
try {
|
|
468
39
|
const params = new URLSearchParams({ page_path: currentPath });
|
|
469
40
|
const res = await fetch(
|
|
470
|
-
`${
|
|
41
|
+
`${apiUrl}/public/images/slot/${encodeURIComponent(slotId)}?${params}`,
|
|
471
42
|
{
|
|
472
43
|
headers: {
|
|
473
44
|
"Content-Type": "application/json",
|
|
474
|
-
"x-api-key":
|
|
45
|
+
"x-api-key": apiKey
|
|
475
46
|
}
|
|
476
47
|
}
|
|
477
48
|
);
|
|
@@ -488,7 +59,7 @@ function ManagedImage({
|
|
|
488
59
|
} finally {
|
|
489
60
|
setLoading(false);
|
|
490
61
|
}
|
|
491
|
-
}, [
|
|
62
|
+
}, [apiKey, apiUrl, slotId, currentPath, onError]);
|
|
492
63
|
useEffect(() => {
|
|
493
64
|
fetchImage();
|
|
494
65
|
}, [fetchImage]);
|
|
@@ -562,7 +133,7 @@ function ManagedImage({
|
|
|
562
133
|
{
|
|
563
134
|
slotId,
|
|
564
135
|
pagePath: currentPath,
|
|
565
|
-
config:
|
|
136
|
+
config: { apiKey, apiUrl },
|
|
566
137
|
onClose: () => setShowPicker(false),
|
|
567
138
|
onSelect: () => {
|
|
568
139
|
setShowPicker(false);
|
|
@@ -611,7 +182,7 @@ function ManagedImage({
|
|
|
611
182
|
{
|
|
612
183
|
slotId,
|
|
613
184
|
pagePath: currentPath,
|
|
614
|
-
config:
|
|
185
|
+
config: { apiKey, apiUrl },
|
|
615
186
|
currentImage: imageData,
|
|
616
187
|
onClose: () => setShowPicker(false),
|
|
617
188
|
onSelect: () => {
|
|
@@ -866,6 +437,53 @@ function ImagePickerModal({
|
|
|
866
437
|
}
|
|
867
438
|
);
|
|
868
439
|
}
|
|
440
|
+
async function fetchFaviconData(apiUrl, apiKey) {
|
|
441
|
+
try {
|
|
442
|
+
const res = await fetch(`${apiUrl}/public/images/slot/favicon`, {
|
|
443
|
+
headers: {
|
|
444
|
+
"Content-Type": "application/json",
|
|
445
|
+
"x-api-key": apiKey
|
|
446
|
+
},
|
|
447
|
+
cache: "no-store"
|
|
448
|
+
// Always fetch fresh
|
|
449
|
+
});
|
|
450
|
+
if (!res.ok) {
|
|
451
|
+
console.warn("[ManagedFavicon] Failed to fetch:", res.status);
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
const data = await res.json();
|
|
455
|
+
if (data.image && !data.is_placeholder) {
|
|
456
|
+
return {
|
|
457
|
+
public_url: data.image.public_url || data.image.external_url,
|
|
458
|
+
mime_type: data.image.file?.mime_type,
|
|
459
|
+
is_placeholder: false
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
return null;
|
|
463
|
+
} catch (err) {
|
|
464
|
+
console.error("[ManagedFavicon] Error fetching favicon:", err);
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async function ManagedFavicon({
|
|
469
|
+
apiKey = process.env.NEXT_PUBLIC_UPTRADE_API_KEY,
|
|
470
|
+
apiUrl = process.env.NEXT_PUBLIC_UPTRADE_API_URL || "https://api.uptrademedia.com",
|
|
471
|
+
fallback = "/favicon.ico",
|
|
472
|
+
themeColor = "#4bbf39"
|
|
473
|
+
}) {
|
|
474
|
+
const faviconData = apiKey && apiUrl ? await fetchFaviconData(apiUrl, apiKey) : null;
|
|
475
|
+
const faviconUrl = faviconData?.public_url || fallback;
|
|
476
|
+
const mimeType = faviconData?.mime_type || "image/x-icon";
|
|
477
|
+
const isSvg = mimeType === "image/svg+xml" || faviconUrl.endsWith(".svg");
|
|
478
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
479
|
+
isSvg ? /* @__PURE__ */ jsx("link", { rel: "icon", type: "image/svg+xml", href: faviconUrl }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
480
|
+
/* @__PURE__ */ jsx("link", { rel: "icon", type: "image/png", sizes: "32x32", href: faviconUrl }),
|
|
481
|
+
/* @__PURE__ */ jsx("link", { rel: "icon", type: "image/png", sizes: "16x16", href: faviconUrl })
|
|
482
|
+
] }),
|
|
483
|
+
/* @__PURE__ */ jsx("link", { rel: "apple-touch-icon", sizes: "180x180", href: faviconUrl }),
|
|
484
|
+
/* @__PURE__ */ jsx("meta", { name: "theme-color", content: themeColor })
|
|
485
|
+
] });
|
|
486
|
+
}
|
|
869
487
|
|
|
870
488
|
// src/images/api.ts
|
|
871
489
|
async function fetchManagedImage(config, slotId, pagePath) {
|
|
@@ -986,6 +604,6 @@ async function clearImageSlot(config, slotId, pagePath) {
|
|
|
986
604
|
return res.json();
|
|
987
605
|
}
|
|
988
606
|
|
|
989
|
-
export {
|
|
990
|
-
//# sourceMappingURL=chunk-
|
|
991
|
-
//# sourceMappingURL=chunk-
|
|
607
|
+
export { ManagedFavicon, ManagedImage, assignImageToSlot, clearImageSlot, fetchManagedImage, fetchManagedImages, listImageFiles, uploadImage };
|
|
608
|
+
//# sourceMappingURL=chunk-CDJL2YGL.mjs.map
|
|
609
|
+
//# sourceMappingURL=chunk-CDJL2YGL.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/images/ManagedImage.tsx","../src/images/ManagedFavicon.tsx","../src/images/api.ts"],"names":["jsxs","Fragment","jsx"],"mappings":"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","file":"chunk-CDJL2YGL.mjs","sourcesContent":["/**\n * ManagedImage - Portal-managed image component\n * \n * Features:\n * - Fetches images from Portal API via API key (never direct Supabase)\n * - Dev mode: Click to open image picker modal\n * - Supports responsive variants\n * - Automatic focal point handling\n * - Placeholder state for empty slots\n * \n * @example\n * ```tsx\n * <ManagedImage \n * slotId=\"hero-background\"\n * alt=\"Hero background image\"\n * className=\"w-full h-96 object-cover\"\n * />\n * ```\n */\n\n'use client'\n\nimport React, { useState, useEffect, useCallback } from 'react'\n\nexport interface ManagedImageData {\n id: string\n slot_id: string\n page_path: string | null\n file_id: string | null\n external_url: string | null\n alt_text: string | null\n title: string | null\n caption: string | null\n focal_point_x: number\n focal_point_y: number\n aspect_ratio: string | null\n public_url?: string\n is_placeholder: boolean\n}\n\nexport interface ImageFile {\n id: string\n filename: string\n storage_path: string\n mime_type: string\n file_size: number\n folder_path: string | null\n public_url?: string\n}\n\nexport interface ManagedImageProps {\n /** API key for Portal API */\n apiKey?: string\n /** API URL (defaults to https://api.uptrademedia.com) */\n apiUrl?: string\n /** Unique slot identifier (e.g., 'hero-background', 'about-team-1') */\n slotId: string\n /** Page path for page-specific slots (defaults to current path) */\n pagePath?: string\n /** Fallback alt text if not set in Portal */\n alt?: string\n /** CSS class names */\n className?: string\n /** Image width */\n width?: number | string\n /** Image height */\n height?: number | string\n /** CSS object-fit property */\n objectFit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'\n /** Fallback image URL when no image assigned */\n fallback?: string\n /** Custom placeholder component */\n placeholder?: React.ReactNode\n /** Called when image loads */\n onLoad?: () => void\n /** Called on error */\n onError?: (error: Error) => void\n /** Priority loading (Next.js Image optimization) */\n priority?: boolean\n /** Additional styles */\n style?: React.CSSProperties\n /** Enable dev picker even outside dev mode */\n forceDevMode?: boolean\n}\n\n// Check if we're in dev mode\nconst isDevMode = (): boolean => {\n if (typeof window === 'undefined') return false\n return (\n process.env.NODE_ENV === 'development' ||\n window.location.hostname === 'localhost' ||\n window.location.hostname === '127.0.0.1' ||\n window.location.search.includes('uptrade_dev=true')\n )\n}\n\nexport function ManagedImage({\n apiKey = process.env.NEXT_PUBLIC_UPTRADE_API_KEY,\n apiUrl = process.env.NEXT_PUBLIC_UPTRADE_API_URL || 'https://api.uptrademedia.com',\n slotId,\n pagePath,\n alt,\n className = '',\n width,\n height,\n objectFit = 'cover',\n fallback,\n placeholder,\n onLoad,\n onError,\n priority,\n style,\n forceDevMode,\n}: ManagedImageProps) {\n const [imageData, setImageData] = useState<ManagedImageData | null>(null)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n const [showPicker, setShowPicker] = useState(false)\n const [devMode] = useState(() => forceDevMode || isDevMode())\n\n // Get current page path if not provided\n const currentPath = pagePath ?? (typeof window !== 'undefined' ? window.location.pathname : '/')\n\n // Fetch image data from Portal API\n const fetchImage = useCallback(async () => {\n if (!apiKey || !apiUrl) {\n setLoading(false)\n return\n }\n\n try {\n const params = new URLSearchParams({ page_path: currentPath })\n const res = await fetch(\n `${apiUrl}/public/images/slot/${encodeURIComponent(slotId)}?${params}`,\n {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n }\n )\n\n if (!res.ok) {\n throw new Error(`Failed to fetch image: ${res.status}`)\n }\n\n const data = await res.json()\n setImageData(data.image)\n setError(null)\n } catch (err) {\n console.error('[ManagedImage] Fetch error:', err)\n setError(err instanceof Error ? err : new Error(String(err)))\n onError?.(err instanceof Error ? err : new Error(String(err)))\n } finally {\n setLoading(false)\n }\n }, [apiKey, apiUrl, slotId, currentPath, onError])\n\n useEffect(() => {\n fetchImage()\n }, [fetchImage])\n\n // Calculate object-position from focal point\n const objectPosition = imageData\n ? `${imageData.focal_point_x}% ${imageData.focal_point_y}%`\n : '50% 50%'\n\n // Get the image URL\n const imageUrl = imageData?.public_url || imageData?.external_url || fallback\n\n // Handle click in dev mode\n const handleClick = (e: React.MouseEvent) => {\n if (devMode) {\n e.preventDefault()\n e.stopPropagation()\n setShowPicker(true)\n }\n }\n\n // Render placeholder state\n if (loading) {\n return (\n <div\n className={`bg-gray-200 animate-pulse ${className}`}\n style={{\n width: width ?? '100%',\n height: height ?? 200,\n ...style,\n }}\n />\n )\n }\n\n // No image assigned - show placeholder or dev picker hint\n if (!imageUrl) {\n if (placeholder) {\n return <>{placeholder}</>\n }\n\n return (\n <div\n onClick={handleClick}\n className={`\n bg-gray-100 border-2 border-dashed border-gray-300 \n flex items-center justify-center text-gray-400\n ${devMode ? 'cursor-pointer hover:border-blue-400 hover:text-blue-500 hover:bg-blue-50' : ''}\n ${className}\n `}\n style={{\n width: width ?? '100%',\n height: height ?? 200,\n ...style,\n }}\n title={devMode ? `Click to add image for slot: ${slotId}` : undefined}\n >\n <div className=\"text-center p-4\">\n <svg\n className=\"w-12 h-12 mx-auto mb-2\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"\n />\n </svg>\n {devMode && (\n <p className=\"text-sm font-medium\">Click to add image</p>\n )}\n <p className=\"text-xs mt-1\">{slotId}</p>\n </div>\n\n {showPicker && (\n <ImagePickerModal\n slotId={slotId}\n pagePath={currentPath}\n config={{ apiKey, apiUrl }}\n onClose={() => setShowPicker(false)}\n onSelect={() => {\n setShowPicker(false)\n fetchImage()\n }}\n />\n )}\n </div>\n )\n }\n\n // Render the image\n return (\n <div className=\"relative\" style={{ width, height }}>\n <img\n src={imageUrl}\n alt={imageData?.alt_text || alt || ''}\n title={imageData?.title || undefined}\n data-managed-image=\"true\"\n data-slot-id={slotId}\n data-page-path={currentPath}\n className={className}\n style={{\n objectFit,\n objectPosition,\n width: width ?? '100%',\n height: height ?? 'auto',\n ...style,\n }}\n onLoad={onLoad}\n onError={() => onError?.(new Error('Image failed to load'))}\n loading={priority ? 'eager' : 'lazy'}\n onClick={handleClick}\n />\n\n {/* Dev mode overlay */}\n {devMode && (\n <div\n className=\"absolute inset-0 bg-black/0 hover:bg-black/30 transition-colors cursor-pointer flex items-center justify-center opacity-0 hover:opacity-100\"\n onClick={handleClick}\n >\n <div className=\"bg-white/90 px-3 py-1.5 rounded-lg shadow-lg text-sm font-medium text-gray-700\">\n Edit Image\n </div>\n </div>\n )}\n\n {showPicker && (\n <ImagePickerModal\n slotId={slotId}\n pagePath={currentPath}\n config={{ apiKey, apiUrl }}\n currentImage={imageData}\n onClose={() => setShowPicker(false)}\n onSelect={() => {\n setShowPicker(false)\n fetchImage()\n }}\n />\n )}\n </div>\n )\n}\n\n// ============================================================================\n// IMAGE PICKER MODAL\n// ============================================================================\n\ninterface ImagePickerModalProps {\n slotId: string\n pagePath: string\n config: any\n currentImage?: ManagedImageData | null\n onClose: () => void\n onSelect: () => void\n}\n\nfunction ImagePickerModal({\n slotId,\n pagePath,\n config,\n currentImage,\n onClose,\n onSelect,\n}: ImagePickerModalProps) {\n const [files, setFiles] = useState<ImageFile[]>([])\n const [folders, setFolders] = useState<string[]>([])\n const [currentFolder, setCurrentFolder] = useState<string>('')\n const [search, setSearch] = useState('')\n const [loading, setLoading] = useState(true)\n const [uploading, setUploading] = useState(false)\n const [altText, setAltText] = useState(currentImage?.alt_text || '')\n\n // Fetch available files\n const fetchFiles = useCallback(async () => {\n if (!config?.apiKey || !config?.apiUrl) return\n\n setLoading(true)\n try {\n const params = new URLSearchParams()\n if (currentFolder) params.set('folder', currentFolder)\n if (search) params.set('search', search)\n\n const res = await fetch(\n `${config.apiUrl}/public/images/files?${params}`,\n {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': config.apiKey,\n },\n }\n )\n\n if (res.ok) {\n const data = await res.json()\n setFiles(data.files || [])\n setFolders(data.folders || [])\n }\n } catch (err) {\n console.error('[ImagePicker] Fetch error:', err)\n } finally {\n setLoading(false)\n }\n }, [config?.apiKey, config?.apiUrl, currentFolder, search])\n\n useEffect(() => {\n fetchFiles()\n }, [fetchFiles])\n\n // Select an existing file\n const handleSelectFile = async (file: ImageFile) => {\n if (!config?.apiKey || !config?.apiUrl) return\n\n try {\n const res = await fetch(\n `${config.apiUrl}/public/images/slot/${encodeURIComponent(slotId)}`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': config.apiKey,\n },\n body: JSON.stringify({\n page_path: pagePath,\n file_id: file.id,\n alt_text: altText || file.filename,\n }),\n }\n )\n\n if (res.ok) {\n onSelect()\n }\n } catch (err) {\n console.error('[ImagePicker] Select error:', err)\n }\n }\n\n // Upload new file\n const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0]\n if (!file || !config?.apiKey || !config?.apiUrl) return\n\n setUploading(true)\n try {\n const formData = new FormData()\n formData.append('file', file)\n formData.append('slot_id', slotId)\n formData.append('page_path', pagePath)\n formData.append('folder', currentFolder || 'Website/Images')\n if (altText) formData.append('alt_text', altText)\n\n const res = await fetch(`${config.apiUrl}/public/images/upload`, {\n method: 'POST',\n headers: {\n 'x-api-key': config.apiKey,\n },\n body: formData,\n })\n\n if (res.ok) {\n onSelect()\n }\n } catch (err) {\n console.error('[ImagePicker] Upload error:', err)\n } finally {\n setUploading(false)\n }\n }\n\n // Clear the slot\n const handleClear = async () => {\n if (!config?.apiKey || !config?.apiUrl) return\n\n try {\n const params = new URLSearchParams({ page_path: pagePath })\n await fetch(\n `${config.apiUrl}/public/images/slot/${encodeURIComponent(slotId)}?${params}`,\n {\n method: 'DELETE',\n headers: {\n 'x-api-key': config.apiKey,\n },\n }\n )\n onSelect()\n } catch (err) {\n console.error('[ImagePicker] Clear error:', err)\n }\n }\n\n return (\n <div\n className=\"fixed inset-0 z-[99999] bg-black/50 flex items-center justify-center p-4\"\n onClick={onClose}\n >\n <div\n className=\"bg-white rounded-xl shadow-2xl max-w-4xl w-full max-h-[90vh] overflow-hidden\"\n onClick={(e) => e.stopPropagation()}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between p-4 border-b\">\n <div>\n <h2 className=\"text-lg font-semibold\">Select Image</h2>\n <p className=\"text-sm text-gray-500\">Slot: {slotId}</p>\n </div>\n <button\n onClick={onClose}\n className=\"text-gray-400 hover:text-gray-600 p-2\"\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Body */}\n <div className=\"p-4 overflow-y-auto\" style={{ maxHeight: 'calc(90vh - 180px)' }}>\n {/* Upload & Search */}\n <div className=\"flex gap-4 mb-4\">\n <div className=\"flex-1\">\n <input\n type=\"text\"\n placeholder=\"Search images...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"w-full px-3 py-2 border rounded-lg text-sm\"\n />\n </div>\n <label className=\"px-4 py-2 bg-blue-600 text-white rounded-lg cursor-pointer hover:bg-blue-700 text-sm font-medium flex items-center gap-2\">\n {uploading ? 'Uploading...' : 'Upload New'}\n <input\n type=\"file\"\n accept=\"image/*\"\n onChange={handleUpload}\n disabled={uploading}\n className=\"hidden\"\n />\n </label>\n {currentImage?.file_id && (\n <button\n onClick={handleClear}\n className=\"px-4 py-2 border border-red-200 text-red-600 rounded-lg hover:bg-red-50 text-sm\"\n >\n Remove\n </button>\n )}\n </div>\n\n {/* Alt text input */}\n <div className=\"mb-4\">\n <input\n type=\"text\"\n placeholder=\"Alt text for image...\"\n value={altText}\n onChange={(e) => setAltText(e.target.value)}\n className=\"w-full px-3 py-2 border rounded-lg text-sm\"\n />\n </div>\n\n {/* Folder navigation */}\n {folders.length > 0 && (\n <div className=\"flex gap-2 mb-4 flex-wrap\">\n <button\n onClick={() => setCurrentFolder('')}\n className={`px-3 py-1 text-sm rounded-full ${\n !currentFolder ? 'bg-blue-100 text-blue-700' : 'bg-gray-100 hover:bg-gray-200'\n }`}\n >\n All\n </button>\n {folders.map((folder) => (\n <button\n key={folder}\n onClick={() => setCurrentFolder(folder)}\n className={`px-3 py-1 text-sm rounded-full ${\n currentFolder === folder ? 'bg-blue-100 text-blue-700' : 'bg-gray-100 hover:bg-gray-200'\n }`}\n >\n {folder}\n </button>\n ))}\n </div>\n )}\n\n {/* File grid */}\n {loading ? (\n <div className=\"grid grid-cols-4 gap-4\">\n {[...Array(8)].map((_, i) => (\n <div key={i} className=\"aspect-square bg-gray-200 rounded-lg animate-pulse\" />\n ))}\n </div>\n ) : files.length === 0 ? (\n <div className=\"text-center py-12 text-gray-500\">\n <p>No images found</p>\n <p className=\"text-sm mt-1\">Upload a new image to get started</p>\n </div>\n ) : (\n <div className=\"grid grid-cols-4 gap-4\">\n {files.map((file) => (\n <button\n key={file.id}\n onClick={() => handleSelectFile(file)}\n className={`\n aspect-square rounded-lg overflow-hidden border-2 transition-all\n hover:border-blue-400 hover:shadow-lg\n ${currentImage?.file_id === file.id ? 'border-blue-500 ring-2 ring-blue-200' : 'border-gray-200'}\n `}\n >\n <img\n src={file.public_url}\n alt={file.filename}\n className=\"w-full h-full object-cover\"\n />\n </button>\n ))}\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"p-4 border-t bg-gray-50 flex justify-end\">\n <button\n onClick={onClose}\n className=\"px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg text-sm\"\n >\n Cancel\n </button>\n </div>\n </div>\n </div>\n )\n}\n\nexport default ManagedImage\n","/**\n * ManagedFavicon Component\n * \n * Server component that renders favicon link tags using the project's logo.\n * When a logo is uploaded in Project Settings, it's automatically synced\n * to the 'favicon' slot in site_managed_images.\n * \n * Usage:\n * ```tsx\n * import { ManagedFavicon } from '@uptrade/site-kit/images'\n * \n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <head>\n * <ManagedFavicon />\n * </head>\n * <body>{children}</body>\n * </html>\n * )\n * }\n * ```\n * \n * Supports:\n * - SVG favicons (best for modern browsers, scales perfectly)\n * - PNG favicons with multiple sizes\n * - Apple touch icons\n * - Theme color for mobile browsers\n */\n\nimport React from 'react'\n\nexport interface ManagedFaviconProps {\n /**\n * API key for Portal API authentication\n * Defaults to NEXT_PUBLIC_UPTRADE_API_KEY env var\n */\n apiKey?: string\n \n /**\n * API URL (defaults to https://api.uptrademedia.com)\n */\n apiUrl?: string\n \n /**\n * Fallback favicon URL if no managed favicon is set\n */\n fallback?: string\n \n /**\n * Theme color for mobile browser chrome\n * Defaults to #4bbf39 (Uptrade brand primary)\n */\n themeColor?: string\n}\n\ninterface FaviconData {\n public_url?: string\n mime_type?: string\n is_placeholder?: boolean\n}\n\n/**\n * Server-side fetch of favicon data\n */\nasync function fetchFaviconData(apiUrl: string, apiKey: string): Promise<FaviconData | null> {\n try {\n const res = await fetch(`${apiUrl}/public/images/slot/favicon`, {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n cache: 'no-store', // Always fetch fresh\n })\n\n if (!res.ok) {\n console.warn('[ManagedFavicon] Failed to fetch:', res.status)\n return null\n }\n\n const data = await res.json()\n if (data.image && !data.is_placeholder) {\n return {\n public_url: data.image.public_url || data.image.external_url,\n mime_type: data.image.file?.mime_type,\n is_placeholder: false,\n }\n }\n return null\n } catch (err) {\n console.error('[ManagedFavicon] Error fetching favicon:', err)\n return null\n }\n}\n\nexport async function ManagedFavicon({\n apiKey = process.env.NEXT_PUBLIC_UPTRADE_API_KEY,\n apiUrl = process.env.NEXT_PUBLIC_UPTRADE_API_URL || 'https://api.uptrademedia.com',\n fallback = '/favicon.ico',\n themeColor = '#4bbf39',\n}: ManagedFaviconProps) {\n // Fetch favicon data during SSR\n const faviconData = apiKey && apiUrl ? await fetchFaviconData(apiUrl, apiKey) : null\n \n const faviconUrl = faviconData?.public_url || fallback\n const mimeType = faviconData?.mime_type || 'image/x-icon'\n const isSvg = mimeType === 'image/svg+xml' || faviconUrl.endsWith('.svg')\n\n return (\n <>\n {/* Primary favicon */}\n {isSvg ? (\n <link rel=\"icon\" type=\"image/svg+xml\" href={faviconUrl} />\n ) : (\n <>\n <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href={faviconUrl} />\n <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href={faviconUrl} />\n </>\n )}\n \n {/* Apple touch icon (for iOS home screen) */}\n <link rel=\"apple-touch-icon\" sizes=\"180x180\" href={faviconUrl} />\n \n {/* Theme color for mobile browsers */}\n <meta name=\"theme-color\" content={themeColor} />\n </>\n )\n}","/**\n * Images API functions\n * \n * All functions use Portal API with API key authentication.\n * Never makes direct Supabase calls.\n */\n\nimport type { ManagedImageData, ImageFile } from './ManagedImage'\n\nexport interface ImageApiConfig {\n apiUrl: string\n apiKey: string\n}\n\n/**\n * Fetch a managed image for a specific slot\n */\nexport async function fetchManagedImage(\n config: ImageApiConfig,\n slotId: string,\n pagePath?: string,\n): Promise<{ image: ManagedImageData | null; is_placeholder: boolean }> {\n const params = new URLSearchParams()\n if (pagePath) params.set('page_path', pagePath)\n\n const res = await fetch(\n `${config.apiUrl}/public/images/slot/${encodeURIComponent(slotId)}?${params}`,\n {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': config.apiKey,\n },\n }\n )\n\n if (!res.ok) {\n throw new Error(`Failed to fetch image: ${res.status}`)\n }\n\n return res.json()\n}\n\n/**\n * Fetch all managed images for the project\n */\nexport async function fetchManagedImages(\n config: ImageApiConfig,\n options?: {\n pagePath?: string\n category?: string\n includePlaceholders?: boolean\n },\n): Promise<{ images: ManagedImageData[] }> {\n const params = new URLSearchParams()\n if (options?.pagePath) params.set('page_path', options.pagePath)\n if (options?.category) params.set('category', options.category)\n if (options?.includePlaceholders) params.set('include_placeholders', 'true')\n\n const res = await fetch(\n `${config.apiUrl}/public/images?${params}`,\n {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': config.apiKey,\n },\n }\n )\n\n if (!res.ok) {\n throw new Error(`Failed to fetch images: ${res.status}`)\n }\n\n return res.json()\n}\n\n/**\n * List available image files in the project\n */\nexport async function listImageFiles(\n config: ImageApiConfig,\n options?: {\n folder?: string\n search?: string\n },\n): Promise<{ files: ImageFile[]; folders: string[] }> {\n const params = new URLSearchParams()\n if (options?.folder) params.set('folder', options.folder)\n if (options?.search) params.set('search', options.search)\n\n const res = await fetch(\n `${config.apiUrl}/public/images/files?${params}`,\n {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': config.apiKey,\n },\n }\n )\n\n if (!res.ok) {\n throw new Error(`Failed to list files: ${res.status}`)\n }\n\n return res.json()\n}\n\n/**\n * Upload a new image\n */\nexport async function uploadImage(\n config: ImageApiConfig,\n file: File,\n options?: {\n slotId?: string\n pagePath?: string\n folder?: string\n altText?: string\n },\n): Promise<{ file: ImageFile; image?: ManagedImageData }> {\n const formData = new FormData()\n formData.append('file', file)\n if (options?.slotId) formData.append('slot_id', options.slotId)\n if (options?.pagePath) formData.append('page_path', options.pagePath)\n if (options?.folder) formData.append('folder', options.folder)\n if (options?.altText) formData.append('alt_text', options.altText)\n\n const res = await fetch(`${config.apiUrl}/public/images/upload`, {\n method: 'POST',\n headers: {\n 'x-api-key': config.apiKey,\n },\n body: formData,\n })\n\n if (!res.ok) {\n throw new Error(`Failed to upload image: ${res.status}`)\n }\n\n return res.json()\n}\n\n/**\n * Assign an existing file to an image slot\n */\nexport async function assignImageToSlot(\n config: ImageApiConfig,\n slotId: string,\n options: {\n fileId?: string\n externalUrl?: string\n pagePath?: string\n altText?: string\n title?: string\n caption?: string\n focalPointX?: number\n focalPointY?: number\n aspectRatio?: string\n },\n): Promise<{ image: ManagedImageData }> {\n const res = await fetch(\n `${config.apiUrl}/public/images/slot/${encodeURIComponent(slotId)}`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': config.apiKey,\n },\n body: JSON.stringify({\n page_path: options.pagePath,\n file_id: options.fileId,\n external_url: options.externalUrl,\n alt_text: options.altText,\n title: options.title,\n caption: options.caption,\n focal_point_x: options.focalPointX,\n focal_point_y: options.focalPointY,\n aspect_ratio: options.aspectRatio,\n }),\n }\n )\n\n if (!res.ok) {\n throw new Error(`Failed to assign image: ${res.status}`)\n }\n\n return res.json()\n}\n\n/**\n * Clear an image from a slot (keeps the file)\n */\nexport async function clearImageSlot(\n config: ImageApiConfig,\n slotId: string,\n pagePath?: string,\n): Promise<{ success: boolean }> {\n const params = new URLSearchParams()\n if (pagePath) params.set('page_path', pagePath)\n\n const res = await fetch(\n `${config.apiUrl}/public/images/slot/${encodeURIComponent(slotId)}?${params}`,\n {\n method: 'DELETE',\n headers: {\n 'x-api-key': config.apiKey,\n },\n }\n )\n\n if (!res.ok) {\n throw new Error(`Failed to clear slot: ${res.status}`)\n }\n\n return res.json()\n}\n"]}
|