@decocms/apps 1.9.1 → 1.11.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.
@@ -21,3 +21,24 @@ export const formatPrice = (
21
21
  currency = "BRL",
22
22
  locale = "pt-BR",
23
23
  ) => (price != null && Number.isFinite(price) ? formatter(currency, locale).format(price) : null);
24
+
25
+ /**
26
+ * Formats a "min:max" range string (as VTEX/Shopify Intelligent Search
27
+ * facets emit) into a localised price range like "R$ 10,00 - R$ 50,00".
28
+ *
29
+ * Returns the original input untouched if either bound fails to parse,
30
+ * so this never crashes a filter UI on a bad facet value.
31
+ */
32
+ export const formatPriceRange = (
33
+ value: string,
34
+ currency = "BRL",
35
+ locale = "pt-BR",
36
+ separator = " - ",
37
+ ): string => {
38
+ if (typeof value !== "string" || !value.includes(":")) return value;
39
+ const [rawMin, rawMax] = value.split(":");
40
+ const min = formatPrice(Number(rawMin), currency, locale);
41
+ const max = formatPrice(Number(rawMax), currency, locale);
42
+ if (min == null || max == null) return value;
43
+ return `${min}${separator}${max}`;
44
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/apps",
3
- "version": "1.9.1",
3
+ "version": "1.11.0",
4
4
  "type": "module",
5
5
  "description": "Deco commerce apps for TanStack Start - Shopify, VTEX, commerce types, analytics utils",
6
6
  "exports": {
@@ -0,0 +1,146 @@
1
+ /**
2
+ * OneDollarStats — deco's lightweight in-house analytics.
3
+ *
4
+ * Pings the lilstts collector with pageviews (initial load + SPA navigations)
5
+ * and forwards custom DECO events as `stonks.event(name, params)`.
6
+ *
7
+ * Mount in the site's `__root.tsx` as a child of `DecoRootLayout`:
8
+ *
9
+ * ```tsx
10
+ * <DecoRootLayout … >
11
+ * <OneDollarStats />
12
+ * </DecoRootLayout>
13
+ * ```
14
+ *
15
+ * No CMS wiring is needed — the component is env-gated and self-mounting.
16
+ *
17
+ * Behavioral notes vs the Fresh deco-cx/apps OneDollarStats:
18
+ * - The initial pageview fires unconditionally (mirrors the Path B
19
+ * `analytics/loaders/OneDollarScript.ts` script-loader from Fresh).
20
+ * The Fresh component variant relied on a synthesised `{ name: "deco" }`
21
+ * event from `Events.tsx`'s subscribe-replay, which has no equivalent
22
+ * in TanStack — so we do not depend on it.
23
+ * - Flag enrichment continues to work via client-side parsing of the
24
+ * `deco_segment` cookie (same code path as the script-loader version).
25
+ * - `pageId` enrichment is intentionally dropped — no deco-admin dashboard
26
+ * consumes it. Add later if a flag-segmented dashboard needs it.
27
+ */
28
+
29
+ declare global {
30
+ interface Window {
31
+ stonks?: {
32
+ view?: (params: Record<string, string | boolean>) => void;
33
+ event?: (name: string, params: Record<string, string | boolean>) => void;
34
+ };
35
+ }
36
+ }
37
+
38
+ export interface Props {
39
+ /** lilstts collector URL. Defaults to {@link DEFAULT_COLLECTOR_ADDRESS}. */
40
+ collectorAddress?: string;
41
+ /** lilstts static script URL. Defaults to {@link DEFAULT_ANALYTICS_SCRIPT_URL}. */
42
+ staticScriptUrl?: string;
43
+ }
44
+
45
+ export const DEFAULT_COLLECTOR_ADDRESS = "https://d.lilstts.com/events";
46
+ export const DEFAULT_ANALYTICS_SCRIPT_URL = "https://s.lilstts.com/deco.js";
47
+
48
+ /**
49
+ * `false` when `ONEDOLLAR_ENABLED=false` is set on the Worker. Default: enabled.
50
+ * Matches the Fresh-side Deno env contract.
51
+ */
52
+ const ONEDOLLAR_ENABLED = process.env.ONEDOLLAR_ENABLED !== "false";
53
+ const ONEDOLLAR_COLLECTOR = process.env.ONEDOLLAR_COLLECTOR;
54
+ const ONEDOLLAR_STATIC_SCRIPT = process.env.ONEDOLLAR_STATIC_SCRIPT;
55
+
56
+ /**
57
+ * Inline subscriber snippet — kept as a plain string constant (not a
58
+ * `useScript(fn)` serialisation) because `fn.toString()` produces different
59
+ * output in SSR vs client Vite builds under React Compiler, causing
60
+ * hydration mismatches on `dangerouslySetInnerHTML`. See
61
+ * `@decocms/start/sdk/useScript` for the deprecation note.
62
+ *
63
+ * Mirrors the Path B (`analytics/loaders/OneDollarScript.ts`) snippet from
64
+ * deco-cx/apps: parse `deco_segment` cookie for flags, fire first pageview
65
+ * unconditionally, patch `history.pushState` + `popstate` for SPA navs,
66
+ * subscribe to `window.DECO.events` to forward non-`deco` events.
67
+ */
68
+ const ONEDOLLAR_SNIPPET = `(function(){
69
+ function parseCookies(str){
70
+ var out = {};
71
+ str.split(";").forEach(function(c){
72
+ var idx = c.indexOf("=");
73
+ if (idx < 0) return;
74
+ out[c.slice(0, idx).trim()] = c.slice(idx + 1).trim();
75
+ });
76
+ return out;
77
+ }
78
+ function tryOrDefault(fn, d){ try { return fn(); } catch(e) { return d; } }
79
+ function getFlags(cookies){
80
+ var out = [];
81
+ var raw = cookies["deco_segment"];
82
+ var seg = raw ? tryOrDefault(function(){ return JSON.parse(decodeURIComponent(atob(raw))); }, {}) : {};
83
+ (seg.active || []).forEach(function(name){ out.push({ name: name, value: true }); });
84
+ (seg.inactiveDrawn || []).forEach(function(name){ out.push({ name: name, value: false }); });
85
+ return out;
86
+ }
87
+ function truncate(v){ return ("" + v).slice(0, 990); }
88
+ var flagList = getFlags(parseCookies(document.cookie || ""));
89
+ var flags = {};
90
+ flagList.forEach(function(f){ flags[f.name] = f.value; });
91
+ function trackPageview(){
92
+ if (window.stonks && typeof window.stonks.view === "function") {
93
+ window.stonks.view(flags);
94
+ }
95
+ }
96
+ trackPageview();
97
+ var origPush = history.pushState;
98
+ if (origPush) {
99
+ history.pushState = function(){
100
+ origPush.apply(this, arguments);
101
+ trackPageview();
102
+ };
103
+ addEventListener("popstate", trackPageview);
104
+ }
105
+ if (window.DECO && window.DECO.events && typeof window.DECO.events.subscribe === "function") {
106
+ window.DECO.events.subscribe(function(event){
107
+ if (!event || !event.name || event.name === "deco") return;
108
+ var values = {};
109
+ for (var k in flags) values[k] = flags[k];
110
+ var params = event.params || {};
111
+ for (var key in params) {
112
+ var v = params[key];
113
+ if (v !== null && v !== undefined) {
114
+ values[key] = truncate(typeof v !== "object" ? v : JSON.stringify(v));
115
+ }
116
+ }
117
+ if (window.stonks && typeof window.stonks.event === "function") {
118
+ window.stonks.event(event.name, values);
119
+ }
120
+ });
121
+ }
122
+ })();`;
123
+
124
+ function OneDollarStats({ collectorAddress, staticScriptUrl }: Props) {
125
+ if (!ONEDOLLAR_ENABLED) return null;
126
+
127
+ const collector = collectorAddress ?? ONEDOLLAR_COLLECTOR ?? DEFAULT_COLLECTOR_ADDRESS;
128
+ const staticScript = staticScriptUrl ?? ONEDOLLAR_STATIC_SCRIPT ?? DEFAULT_ANALYTICS_SCRIPT_URL;
129
+
130
+ return (
131
+ <>
132
+ <link rel="dns-prefetch" href={collector} />
133
+ <link rel="preconnect" href={collector} crossOrigin="anonymous" />
134
+ <script
135
+ id="tracker"
136
+ data-autocollect="false"
137
+ data-hash-routing="true"
138
+ data-url={collector}
139
+ src={staticScript}
140
+ />
141
+ <script defer dangerouslySetInnerHTML={{ __html: ONEDOLLAR_SNIPPET }} />
142
+ </>
143
+ );
144
+ }
145
+
146
+ export default OneDollarStats;