@progus/connector 0.2.0 → 0.4.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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @progus/connector
2
2
 
3
3
  Headless connector for Progus partner/affiliate tracking, partner ID assignment,
4
- and cross-sell offers. No UI code, no embedded secrets.
4
+ and cross-sell offers.
5
5
 
6
6
  ## Installation
7
7
 
@@ -18,7 +18,7 @@ const connector = createProgusConnector({
18
18
  appKey: "progus-store-locator",
19
19
  apiBaseUrl: process.env.PARTNERS_API_URL!,
20
20
  signingSecret: process.env.PARTNERS_SECRET_KEY!,
21
- apiKey: process.env.PARTNERS_API_KEY, // optional
21
+ apiKey: process.env.PARTNERS_API_KEY,
22
22
  });
23
23
 
24
24
  await connector.trackInstall({
@@ -51,14 +51,16 @@ if (result.success) {
51
51
  }
52
52
  ```
53
53
 
54
- ## Cross-sell offers
54
+ ## Cross-sell offers (remote catalog)
55
55
 
56
56
  ```ts
57
- import { getCrossSellOffers } from "@progus/connector";
57
+ import { getCrossSellOffersFromApi } from "@progus/connector";
58
58
 
59
- const offers = getCrossSellOffers({
59
+ const offers = await getCrossSellOffersFromApi({
60
60
  currentAppKey: "progus-store-locator",
61
61
  installedAppKeys: ["progus_cod"],
62
+ appsCatalogUrl: "https://appsdata.progus.workers.dev/recommendations",
63
+ limit: 3,
62
64
  });
63
65
  ```
64
66
 
@@ -70,6 +72,12 @@ import { signPayload } from "@progus/connector";
70
72
  const signature = signPayload(JSON.stringify({ test: true }), process.env.PARTNERS_SECRET_KEY!);
71
73
  ```
72
74
 
75
+ ## UI components
76
+
77
+ ```tsx
78
+ import { Recommendations, PartnerIdCard } from "@progus/connector";
79
+ ```
80
+
73
81
  ## Environment variables
74
82
 
75
83
  - `PARTNERS_API_URL` - base URL for partner API (e.g. `https://partners.example.com`)
package/dist/index.cjs CHANGED
@@ -20,9 +20,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- appsCatalog: () => appsCatalog,
23
+ PartnerIdCard: () => PartnerIdCard,
24
+ Recommendations: () => Recommendations,
24
25
  createProgusConnector: () => createProgusConnector,
26
+ fetchAppsCatalog: () => fetchAppsCatalog,
25
27
  getCrossSellOffers: () => getCrossSellOffers,
28
+ getCrossSellOffersFromApi: () => getCrossSellOffersFromApi,
26
29
  normalizePartnerId: () => normalizePartnerId,
27
30
  signPayload: () => signPayload
28
31
  });
@@ -209,52 +212,228 @@ function createProgusConnector(config) {
209
212
  };
210
213
  }
211
214
 
212
- // src/appsCatalog.json
213
- var appsCatalog_default = [
214
- {
215
- key: "progus_cod",
216
- type: "app",
217
- title: "Progus COD",
218
- company: "Progus",
219
- companyUrl: "https://progus.com",
220
- desc: "Automate COD Fees & Hide/Show Cash on Delivery by Rules",
221
- url: "https://apps.shopify.com/progus-cod",
222
- icon: "https://cdn.shopify.com/app-store/listing_images/bc537219cc3ed2bd4e7e3e683fe6b74a/icon/CMi_6dTEkIoDEAE=.png",
223
- priority: 100,
224
- enabled: true
225
- },
226
- {
227
- key: "progus_trust_badges",
228
- type: "app",
229
- title: "Progus Trust Badges",
230
- company: "Progus",
231
- companyUrl: "https://progus.com",
232
- desc: "Add Trust Badges to your store to build trust and credibility.",
233
- url: "https://apps.shopify.com/progus-trust-badges-1",
234
- icon: "https://cdn.shopify.com/app-store/listing_images/f9d0009e237f27d2db35b41ef99be858/icon/CJ3y1qDn1JEDEAE=.png",
235
- priority: 90,
236
- enabled: true
237
- }
238
- ];
239
-
240
215
  // src/crossSell.ts
241
- var catalog = appsCatalog_default;
242
216
  function getCrossSellOffers(options = {}) {
243
- const appsCatalog2 = options.appsCatalog ?? catalog;
217
+ const appsCatalog = options.appsCatalog ?? [];
244
218
  const installedKeys = new Set(
245
219
  [options.currentAppKey, ...options.installedAppKeys ?? []].filter(
246
220
  (key) => Boolean(key)
247
221
  )
248
222
  );
249
223
  const locale = options.locale;
250
- return appsCatalog2.filter((app) => app.enabled !== false).filter((app) => locale ? !app.locales || app.locales.includes(locale) : true).filter((app) => !installedKeys.has(app.key)).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
224
+ return appsCatalog.filter((app) => app.enabled !== false).filter((app) => locale ? !app.locales || app.locales.includes(locale) : true).filter((app) => !installedKeys.has(app.key)).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
225
+ }
226
+ var DEFAULT_APPS_CATALOG_URL = "https://appsdata.progus.workers.dev/recommendations";
227
+ async function fetchAppsCatalog(options = {}) {
228
+ const fetchImpl = options.fetch ?? globalThis.fetch;
229
+ const logger = options.logger ?? console;
230
+ const appName = options.appName ?? options.currentAppKey;
231
+ const baseUrl = stripTrailingSlash(options.appsCatalogUrl ?? DEFAULT_APPS_CATALOG_URL);
232
+ const limit = typeof options.limit === "number" ? options.limit : 3;
233
+ const params = new URLSearchParams();
234
+ if (appName) params.set("appName", appName);
235
+ if (limit > 0) params.set("limit", String(limit));
236
+ const url = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl;
237
+ if (!fetchImpl) {
238
+ throw new Error("Fetch implementation is required");
239
+ }
240
+ try {
241
+ const response = await fetchImpl(url);
242
+ const text = await response.text();
243
+ const parsed = safeJsonParse(text);
244
+ if (!response.ok || !parsed) {
245
+ logger?.error?.("Failed to fetch apps catalog", { status: response.status });
246
+ return [];
247
+ }
248
+ return parsed;
249
+ } catch (error) {
250
+ logger?.error?.("Failed to fetch apps catalog", {
251
+ error: error instanceof Error ? error.message : String(error)
252
+ });
253
+ return [];
254
+ }
255
+ }
256
+ async function getCrossSellOffersFromApi(options = {}) {
257
+ const appsCatalog = options.appsCatalog ?? await fetchAppsCatalog(options);
258
+ return getCrossSellOffers({ ...options, appsCatalog });
259
+ }
260
+
261
+ // src/ui/PartnerIdCard.tsx
262
+ var import_polaris = require("@shopify/polaris");
263
+ var import_jsx_runtime = require("react/jsx-runtime");
264
+ function PartnerIdCard({
265
+ partnerId,
266
+ onPartnerIdChange,
267
+ onSave,
268
+ saveLabel,
269
+ loading,
270
+ saving,
271
+ locked,
272
+ error,
273
+ label,
274
+ placeholder,
275
+ helpText,
276
+ saveDisabled
277
+ }) {
278
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
279
+ import_polaris.LegacyCard,
280
+ {
281
+ sectioned: true,
282
+ primaryFooterAction: {
283
+ content: saveLabel,
284
+ loading: Boolean(saving),
285
+ onAction: onSave,
286
+ disabled: Boolean(saveDisabled)
287
+ },
288
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_polaris.SkeletonBodyText, { lines: 1 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_polaris.FormLayout, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
289
+ import_polaris.TextField,
290
+ {
291
+ error,
292
+ label,
293
+ value: partnerId,
294
+ onChange: onPartnerIdChange,
295
+ placeholder,
296
+ helpText,
297
+ autoComplete: "off",
298
+ disabled: Boolean(locked || loading)
299
+ }
300
+ ) })
301
+ }
302
+ );
303
+ }
304
+
305
+ // src/ui/Recommendations.tsx
306
+ var import_polaris2 = require("@shopify/polaris");
307
+ var import_polaris_icons = require("@shopify/polaris-icons");
308
+ var import_react = require("react");
309
+ var import_jsx_runtime2 = require("react/jsx-runtime");
310
+ function Recommendations({
311
+ recommendations,
312
+ loading,
313
+ title,
314
+ dismissLabel,
315
+ installLabel,
316
+ freePlanLabel,
317
+ newBadgeLabel,
318
+ onDismiss,
319
+ openUrl,
320
+ getDescription
321
+ }) {
322
+ const [menuActive, setMenuActive] = (0, import_react.useState)(false);
323
+ if (!recommendations || recommendations.length === 0) {
324
+ return null;
325
+ }
326
+ const handleOpenUrl = (url) => {
327
+ if (openUrl) return openUrl(url);
328
+ if (typeof window !== "undefined") {
329
+ window.open(url, "_blank", "noopener,noreferrer");
330
+ }
331
+ };
332
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
333
+ import_polaris2.LegacyCard,
334
+ {
335
+ sectioned: true,
336
+ title: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
337
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_polaris2.Text, { variant: "headingSm", as: "h3", children: title }),
338
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
339
+ import_polaris2.Popover,
340
+ {
341
+ active: menuActive,
342
+ activator: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
343
+ import_polaris2.Button,
344
+ {
345
+ onClick: () => setMenuActive(!menuActive),
346
+ icon: import_polaris_icons.MenuHorizontalIcon,
347
+ variant: "plain",
348
+ accessibilityLabel: "Menu"
349
+ }
350
+ ),
351
+ onClose: () => setMenuActive(false),
352
+ preferredAlignment: "right",
353
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
354
+ import_polaris2.ActionList,
355
+ {
356
+ actionRole: "menuitem",
357
+ items: [
358
+ {
359
+ content: dismissLabel,
360
+ onAction: () => {
361
+ setMenuActive(false);
362
+ onDismiss();
363
+ }
364
+ }
365
+ ]
366
+ }
367
+ )
368
+ }
369
+ ) })
370
+ ] }),
371
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_polaris2.Layout, { children: recommendations.map((rec, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_polaris2.Layout.Section, { variant: "oneThird", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_polaris2.LegacyCard, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_polaris2.LegacyCard.Section, { children: [
372
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: "16px", position: "relative" }, children: [
373
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
374
+ "div",
375
+ {
376
+ style: {
377
+ flexShrink: 0,
378
+ width: "48px",
379
+ height: "48px",
380
+ borderRadius: "12px",
381
+ overflow: "hidden",
382
+ backgroundColor: "#f6f6f7",
383
+ display: "flex",
384
+ alignItems: "center",
385
+ justifyContent: "center"
386
+ },
387
+ children: rec.icon ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
388
+ "img",
389
+ {
390
+ src: rec.icon,
391
+ alt: rec.title,
392
+ style: { width: "48px", height: "48px", objectFit: "cover" }
393
+ }
394
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
395
+ "div",
396
+ {
397
+ style: {
398
+ width: "48px",
399
+ height: "48px",
400
+ backgroundColor: "#e1e1e1",
401
+ borderRadius: "12px"
402
+ }
403
+ }
404
+ )
405
+ }
406
+ ),
407
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative" }, children: [
408
+ rec.newBadge && newBadgeLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { position: "absolute", right: "-8px", top: "-8px" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_polaris2.Badge, { tone: "info", children: newBadgeLabel }) }),
409
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_polaris2.Text, { variant: "headingSm", as: "h3", truncate: true, children: rec.title }),
410
+ freePlanLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: "6px" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_polaris2.Badge, { tone: "info", children: freePlanLabel }) })
411
+ ] }) })
412
+ ] }),
413
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: "12px" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_polaris2.Text, { variant: "bodySm", tone: "subdued", as: "p", children: getDescription ? getDescription(rec) : rec.desc }) }),
414
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: "12px" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
415
+ import_polaris2.Button,
416
+ {
417
+ disabled: loading,
418
+ size: "micro",
419
+ variant: "plain",
420
+ onClick: () => handleOpenUrl(rec.url),
421
+ icon: import_polaris_icons.ExternalIcon,
422
+ children: installLabel
423
+ }
424
+ ) })
425
+ ] }) }) }, `${rec.key}-${index}`)) })
426
+ }
427
+ );
251
428
  }
252
- var appsCatalog = catalog;
253
429
  // Annotate the CommonJS export names for ESM import in node:
254
430
  0 && (module.exports = {
255
- appsCatalog,
431
+ PartnerIdCard,
432
+ Recommendations,
256
433
  createProgusConnector,
434
+ fetchAppsCatalog,
257
435
  getCrossSellOffers,
436
+ getCrossSellOffersFromApi,
258
437
  normalizePartnerId,
259
438
  signPayload
260
439
  });
package/dist/index.d.cts CHANGED
@@ -1,3 +1,6 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { TextFieldProps } from '@shopify/polaris';
3
+
1
4
  type FetchLike = typeof fetch;
2
5
  type Logger = {
3
6
  info?: (message: string, meta?: Record<string, unknown>) => void;
@@ -24,6 +27,13 @@ type CrossSellOptions = {
24
27
  shopPlan?: string;
25
28
  appsCatalog?: AppsCatalogEntry[];
26
29
  };
30
+ type CrossSellFetchOptions = CrossSellOptions & {
31
+ appName?: string;
32
+ appsCatalogUrl?: string;
33
+ limit?: number;
34
+ fetch?: FetchLike;
35
+ logger?: Logger;
36
+ };
27
37
  type ConnectorConfig = {
28
38
  appKey: string;
29
39
  apiBaseUrl: string;
@@ -97,10 +107,44 @@ type Connector = {
97
107
  declare function createProgusConnector(config: ConnectorConfig): Connector;
98
108
 
99
109
  declare function getCrossSellOffers(options?: CrossSellOptions): AppsCatalogEntry[];
100
- declare const appsCatalog: AppsCatalogEntry[];
110
+ declare function fetchAppsCatalog(options?: CrossSellFetchOptions): Promise<AppsCatalogEntry[]>;
111
+ declare function getCrossSellOffersFromApi(options?: CrossSellFetchOptions): Promise<AppsCatalogEntry[]>;
101
112
 
102
113
  declare function signPayload(body: string, secret: string): string;
103
114
 
104
115
  declare function normalizePartnerId(value?: string | null): string | null;
105
116
 
106
- export { type AppsCatalogEntry, type AssignPartnerIdInput, type CheckPartnerIdResult, type ConnectorConfig, type CrossSellOptions, type Logger, type SubscriptionEventData, type TrackEventName, type TrackEventParams, type TrackResult, appsCatalog, createProgusConnector, getCrossSellOffers, normalizePartnerId, signPayload };
117
+ type PartnerIdCardProps = {
118
+ partnerId: string;
119
+ onPartnerIdChange: (value: string) => void;
120
+ onSave: () => void;
121
+ saveLabel: string;
122
+ loading?: boolean;
123
+ saving?: boolean;
124
+ locked?: boolean;
125
+ error?: string | TextFieldProps["error"];
126
+ label: string;
127
+ placeholder?: string;
128
+ helpText?: string;
129
+ saveDisabled?: boolean;
130
+ };
131
+ declare function PartnerIdCard({ partnerId, onPartnerIdChange, onSave, saveLabel, loading, saving, locked, error, label, placeholder, helpText, saveDisabled, }: PartnerIdCardProps): react_jsx_runtime.JSX.Element;
132
+
133
+ type RecommendationItem = AppsCatalogEntry & {
134
+ newBadge?: boolean;
135
+ };
136
+ type RecommendationsProps = {
137
+ recommendations: RecommendationItem[];
138
+ loading?: boolean;
139
+ title: string;
140
+ dismissLabel: string;
141
+ installLabel: string;
142
+ freePlanLabel?: string;
143
+ newBadgeLabel?: string;
144
+ onDismiss: () => void;
145
+ openUrl?: (url: string) => void;
146
+ getDescription?: (item: RecommendationItem) => string;
147
+ };
148
+ declare function Recommendations({ recommendations, loading, title, dismissLabel, installLabel, freePlanLabel, newBadgeLabel, onDismiss, openUrl, getDescription, }: RecommendationsProps): react_jsx_runtime.JSX.Element | null;
149
+
150
+ export { type AppsCatalogEntry, type AssignPartnerIdInput, type CheckPartnerIdResult, type ConnectorConfig, type CrossSellFetchOptions, type CrossSellOptions, type Logger, PartnerIdCard, type PartnerIdCardProps, type RecommendationItem, Recommendations, type RecommendationsProps, type SubscriptionEventData, type TrackEventName, type TrackEventParams, type TrackResult, createProgusConnector, fetchAppsCatalog, getCrossSellOffers, getCrossSellOffersFromApi, normalizePartnerId, signPayload };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { TextFieldProps } from '@shopify/polaris';
3
+
1
4
  type FetchLike = typeof fetch;
2
5
  type Logger = {
3
6
  info?: (message: string, meta?: Record<string, unknown>) => void;
@@ -24,6 +27,13 @@ type CrossSellOptions = {
24
27
  shopPlan?: string;
25
28
  appsCatalog?: AppsCatalogEntry[];
26
29
  };
30
+ type CrossSellFetchOptions = CrossSellOptions & {
31
+ appName?: string;
32
+ appsCatalogUrl?: string;
33
+ limit?: number;
34
+ fetch?: FetchLike;
35
+ logger?: Logger;
36
+ };
27
37
  type ConnectorConfig = {
28
38
  appKey: string;
29
39
  apiBaseUrl: string;
@@ -97,10 +107,44 @@ type Connector = {
97
107
  declare function createProgusConnector(config: ConnectorConfig): Connector;
98
108
 
99
109
  declare function getCrossSellOffers(options?: CrossSellOptions): AppsCatalogEntry[];
100
- declare const appsCatalog: AppsCatalogEntry[];
110
+ declare function fetchAppsCatalog(options?: CrossSellFetchOptions): Promise<AppsCatalogEntry[]>;
111
+ declare function getCrossSellOffersFromApi(options?: CrossSellFetchOptions): Promise<AppsCatalogEntry[]>;
101
112
 
102
113
  declare function signPayload(body: string, secret: string): string;
103
114
 
104
115
  declare function normalizePartnerId(value?: string | null): string | null;
105
116
 
106
- export { type AppsCatalogEntry, type AssignPartnerIdInput, type CheckPartnerIdResult, type ConnectorConfig, type CrossSellOptions, type Logger, type SubscriptionEventData, type TrackEventName, type TrackEventParams, type TrackResult, appsCatalog, createProgusConnector, getCrossSellOffers, normalizePartnerId, signPayload };
117
+ type PartnerIdCardProps = {
118
+ partnerId: string;
119
+ onPartnerIdChange: (value: string) => void;
120
+ onSave: () => void;
121
+ saveLabel: string;
122
+ loading?: boolean;
123
+ saving?: boolean;
124
+ locked?: boolean;
125
+ error?: string | TextFieldProps["error"];
126
+ label: string;
127
+ placeholder?: string;
128
+ helpText?: string;
129
+ saveDisabled?: boolean;
130
+ };
131
+ declare function PartnerIdCard({ partnerId, onPartnerIdChange, onSave, saveLabel, loading, saving, locked, error, label, placeholder, helpText, saveDisabled, }: PartnerIdCardProps): react_jsx_runtime.JSX.Element;
132
+
133
+ type RecommendationItem = AppsCatalogEntry & {
134
+ newBadge?: boolean;
135
+ };
136
+ type RecommendationsProps = {
137
+ recommendations: RecommendationItem[];
138
+ loading?: boolean;
139
+ title: string;
140
+ dismissLabel: string;
141
+ installLabel: string;
142
+ freePlanLabel?: string;
143
+ newBadgeLabel?: string;
144
+ onDismiss: () => void;
145
+ openUrl?: (url: string) => void;
146
+ getDescription?: (item: RecommendationItem) => string;
147
+ };
148
+ declare function Recommendations({ recommendations, loading, title, dismissLabel, installLabel, freePlanLabel, newBadgeLabel, onDismiss, openUrl, getDescription, }: RecommendationsProps): react_jsx_runtime.JSX.Element | null;
149
+
150
+ export { type AppsCatalogEntry, type AssignPartnerIdInput, type CheckPartnerIdResult, type ConnectorConfig, type CrossSellFetchOptions, type CrossSellOptions, type Logger, PartnerIdCard, type PartnerIdCardProps, type RecommendationItem, Recommendations, type RecommendationsProps, type SubscriptionEventData, type TrackEventName, type TrackEventParams, type TrackResult, createProgusConnector, fetchAppsCatalog, getCrossSellOffers, getCrossSellOffersFromApi, normalizePartnerId, signPayload };
package/dist/index.js CHANGED
@@ -179,51 +179,227 @@ function createProgusConnector(config) {
179
179
  };
180
180
  }
181
181
 
182
- // src/appsCatalog.json
183
- var appsCatalog_default = [
184
- {
185
- key: "progus_cod",
186
- type: "app",
187
- title: "Progus COD",
188
- company: "Progus",
189
- companyUrl: "https://progus.com",
190
- desc: "Automate COD Fees & Hide/Show Cash on Delivery by Rules",
191
- url: "https://apps.shopify.com/progus-cod",
192
- icon: "https://cdn.shopify.com/app-store/listing_images/bc537219cc3ed2bd4e7e3e683fe6b74a/icon/CMi_6dTEkIoDEAE=.png",
193
- priority: 100,
194
- enabled: true
195
- },
196
- {
197
- key: "progus_trust_badges",
198
- type: "app",
199
- title: "Progus Trust Badges",
200
- company: "Progus",
201
- companyUrl: "https://progus.com",
202
- desc: "Add Trust Badges to your store to build trust and credibility.",
203
- url: "https://apps.shopify.com/progus-trust-badges-1",
204
- icon: "https://cdn.shopify.com/app-store/listing_images/f9d0009e237f27d2db35b41ef99be858/icon/CJ3y1qDn1JEDEAE=.png",
205
- priority: 90,
206
- enabled: true
207
- }
208
- ];
209
-
210
182
  // src/crossSell.ts
211
- var catalog = appsCatalog_default;
212
183
  function getCrossSellOffers(options = {}) {
213
- const appsCatalog2 = options.appsCatalog ?? catalog;
184
+ const appsCatalog = options.appsCatalog ?? [];
214
185
  const installedKeys = new Set(
215
186
  [options.currentAppKey, ...options.installedAppKeys ?? []].filter(
216
187
  (key) => Boolean(key)
217
188
  )
218
189
  );
219
190
  const locale = options.locale;
220
- return appsCatalog2.filter((app) => app.enabled !== false).filter((app) => locale ? !app.locales || app.locales.includes(locale) : true).filter((app) => !installedKeys.has(app.key)).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
191
+ return appsCatalog.filter((app) => app.enabled !== false).filter((app) => locale ? !app.locales || app.locales.includes(locale) : true).filter((app) => !installedKeys.has(app.key)).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
192
+ }
193
+ var DEFAULT_APPS_CATALOG_URL = "https://appsdata.progus.workers.dev/recommendations";
194
+ async function fetchAppsCatalog(options = {}) {
195
+ const fetchImpl = options.fetch ?? globalThis.fetch;
196
+ const logger = options.logger ?? console;
197
+ const appName = options.appName ?? options.currentAppKey;
198
+ const baseUrl = stripTrailingSlash(options.appsCatalogUrl ?? DEFAULT_APPS_CATALOG_URL);
199
+ const limit = typeof options.limit === "number" ? options.limit : 3;
200
+ const params = new URLSearchParams();
201
+ if (appName) params.set("appName", appName);
202
+ if (limit > 0) params.set("limit", String(limit));
203
+ const url = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl;
204
+ if (!fetchImpl) {
205
+ throw new Error("Fetch implementation is required");
206
+ }
207
+ try {
208
+ const response = await fetchImpl(url);
209
+ const text = await response.text();
210
+ const parsed = safeJsonParse(text);
211
+ if (!response.ok || !parsed) {
212
+ logger?.error?.("Failed to fetch apps catalog", { status: response.status });
213
+ return [];
214
+ }
215
+ return parsed;
216
+ } catch (error) {
217
+ logger?.error?.("Failed to fetch apps catalog", {
218
+ error: error instanceof Error ? error.message : String(error)
219
+ });
220
+ return [];
221
+ }
222
+ }
223
+ async function getCrossSellOffersFromApi(options = {}) {
224
+ const appsCatalog = options.appsCatalog ?? await fetchAppsCatalog(options);
225
+ return getCrossSellOffers({ ...options, appsCatalog });
226
+ }
227
+
228
+ // src/ui/PartnerIdCard.tsx
229
+ import { FormLayout, LegacyCard, SkeletonBodyText, TextField } from "@shopify/polaris";
230
+ import { jsx } from "react/jsx-runtime";
231
+ function PartnerIdCard({
232
+ partnerId,
233
+ onPartnerIdChange,
234
+ onSave,
235
+ saveLabel,
236
+ loading,
237
+ saving,
238
+ locked,
239
+ error,
240
+ label,
241
+ placeholder,
242
+ helpText,
243
+ saveDisabled
244
+ }) {
245
+ return /* @__PURE__ */ jsx(
246
+ LegacyCard,
247
+ {
248
+ sectioned: true,
249
+ primaryFooterAction: {
250
+ content: saveLabel,
251
+ loading: Boolean(saving),
252
+ onAction: onSave,
253
+ disabled: Boolean(saveDisabled)
254
+ },
255
+ children: loading ? /* @__PURE__ */ jsx(SkeletonBodyText, { lines: 1 }) : /* @__PURE__ */ jsx(FormLayout, { children: /* @__PURE__ */ jsx(
256
+ TextField,
257
+ {
258
+ error,
259
+ label,
260
+ value: partnerId,
261
+ onChange: onPartnerIdChange,
262
+ placeholder,
263
+ helpText,
264
+ autoComplete: "off",
265
+ disabled: Boolean(locked || loading)
266
+ }
267
+ ) })
268
+ }
269
+ );
270
+ }
271
+
272
+ // src/ui/Recommendations.tsx
273
+ import { ActionList, Badge, Button, Layout, LegacyCard as LegacyCard2, Popover, Text } from "@shopify/polaris";
274
+ import { ExternalIcon, MenuHorizontalIcon } from "@shopify/polaris-icons";
275
+ import { useState } from "react";
276
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
277
+ function Recommendations({
278
+ recommendations,
279
+ loading,
280
+ title,
281
+ dismissLabel,
282
+ installLabel,
283
+ freePlanLabel,
284
+ newBadgeLabel,
285
+ onDismiss,
286
+ openUrl,
287
+ getDescription
288
+ }) {
289
+ const [menuActive, setMenuActive] = useState(false);
290
+ if (!recommendations || recommendations.length === 0) {
291
+ return null;
292
+ }
293
+ const handleOpenUrl = (url) => {
294
+ if (openUrl) return openUrl(url);
295
+ if (typeof window !== "undefined") {
296
+ window.open(url, "_blank", "noopener,noreferrer");
297
+ }
298
+ };
299
+ return /* @__PURE__ */ jsx2(
300
+ LegacyCard2,
301
+ {
302
+ sectioned: true,
303
+ title: /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
304
+ /* @__PURE__ */ jsx2(Text, { variant: "headingSm", as: "h3", children: title }),
305
+ /* @__PURE__ */ jsx2("div", { children: /* @__PURE__ */ jsx2(
306
+ Popover,
307
+ {
308
+ active: menuActive,
309
+ activator: /* @__PURE__ */ jsx2(
310
+ Button,
311
+ {
312
+ onClick: () => setMenuActive(!menuActive),
313
+ icon: MenuHorizontalIcon,
314
+ variant: "plain",
315
+ accessibilityLabel: "Menu"
316
+ }
317
+ ),
318
+ onClose: () => setMenuActive(false),
319
+ preferredAlignment: "right",
320
+ children: /* @__PURE__ */ jsx2(
321
+ ActionList,
322
+ {
323
+ actionRole: "menuitem",
324
+ items: [
325
+ {
326
+ content: dismissLabel,
327
+ onAction: () => {
328
+ setMenuActive(false);
329
+ onDismiss();
330
+ }
331
+ }
332
+ ]
333
+ }
334
+ )
335
+ }
336
+ ) })
337
+ ] }),
338
+ children: /* @__PURE__ */ jsx2(Layout, { children: recommendations.map((rec, index) => /* @__PURE__ */ jsx2(Layout.Section, { variant: "oneThird", children: /* @__PURE__ */ jsx2(LegacyCard2, { children: /* @__PURE__ */ jsxs(LegacyCard2.Section, { children: [
339
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: "16px", position: "relative" }, children: [
340
+ /* @__PURE__ */ jsx2(
341
+ "div",
342
+ {
343
+ style: {
344
+ flexShrink: 0,
345
+ width: "48px",
346
+ height: "48px",
347
+ borderRadius: "12px",
348
+ overflow: "hidden",
349
+ backgroundColor: "#f6f6f7",
350
+ display: "flex",
351
+ alignItems: "center",
352
+ justifyContent: "center"
353
+ },
354
+ children: rec.icon ? /* @__PURE__ */ jsx2(
355
+ "img",
356
+ {
357
+ src: rec.icon,
358
+ alt: rec.title,
359
+ style: { width: "48px", height: "48px", objectFit: "cover" }
360
+ }
361
+ ) : /* @__PURE__ */ jsx2(
362
+ "div",
363
+ {
364
+ style: {
365
+ width: "48px",
366
+ height: "48px",
367
+ backgroundColor: "#e1e1e1",
368
+ borderRadius: "12px"
369
+ }
370
+ }
371
+ )
372
+ }
373
+ ),
374
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
375
+ rec.newBadge && newBadgeLabel && /* @__PURE__ */ jsx2("div", { style: { position: "absolute", right: "-8px", top: "-8px" }, children: /* @__PURE__ */ jsx2(Badge, { tone: "info", children: newBadgeLabel }) }),
376
+ /* @__PURE__ */ jsx2(Text, { variant: "headingSm", as: "h3", truncate: true, children: rec.title }),
377
+ freePlanLabel && /* @__PURE__ */ jsx2("div", { style: { marginTop: "6px" }, children: /* @__PURE__ */ jsx2(Badge, { tone: "info", children: freePlanLabel }) })
378
+ ] }) })
379
+ ] }),
380
+ /* @__PURE__ */ jsx2("div", { style: { marginTop: "12px" }, children: /* @__PURE__ */ jsx2(Text, { variant: "bodySm", tone: "subdued", as: "p", children: getDescription ? getDescription(rec) : rec.desc }) }),
381
+ /* @__PURE__ */ jsx2("div", { style: { marginTop: "12px" }, children: /* @__PURE__ */ jsx2(
382
+ Button,
383
+ {
384
+ disabled: loading,
385
+ size: "micro",
386
+ variant: "plain",
387
+ onClick: () => handleOpenUrl(rec.url),
388
+ icon: ExternalIcon,
389
+ children: installLabel
390
+ }
391
+ ) })
392
+ ] }) }) }, `${rec.key}-${index}`)) })
393
+ }
394
+ );
221
395
  }
222
- var appsCatalog = catalog;
223
396
  export {
224
- appsCatalog,
397
+ PartnerIdCard,
398
+ Recommendations,
225
399
  createProgusConnector,
400
+ fetchAppsCatalog,
226
401
  getCrossSellOffers,
402
+ getCrossSellOffersFromApi,
227
403
  normalizePartnerId,
228
404
  signPayload
229
405
  };
package/package.json CHANGED
@@ -1,23 +1,22 @@
1
1
  {
2
2
  "name": "@progus/connector",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Progus partner/affiliate connector helpers",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs",
8
- "module": "./dist/index.mjs",
8
+ "module": "./dist/index.js",
9
9
  "types": "./dist/index.d.ts",
10
10
  "exports": {
11
11
  ".": {
12
12
  "types": "./dist/index.d.ts",
13
- "import": "./dist/index.mjs",
13
+ "import": "./dist/index.js",
14
14
  "require": "./dist/index.cjs"
15
15
  }
16
16
  },
17
17
  "files": [
18
18
  "dist",
19
- "README.md",
20
- "src/appsCatalog.json"
19
+ "README.md"
21
20
  ],
22
21
  "sideEffects": false,
23
22
  "engines": {
@@ -27,12 +26,21 @@
27
26
  "access": "public"
28
27
  },
29
28
  "scripts": {
30
- "build": "tsup src/index.ts --format esm,cjs --dts --clean --target node18",
31
- "dev": "tsup src/index.ts --format esm,cjs --dts --watch --target node18",
29
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean --target node18 --external react --external react-dom --external @shopify/polaris --external @shopify/polaris-icons",
30
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch --target node18 --external react --external react-dom --external @shopify/polaris --external @shopify/polaris-icons",
31
+ "prepare": "npm run build",
32
32
  "smoke": "tsx scripts/smoke.ts"
33
33
  },
34
+ "peerDependencies": {
35
+ "@shopify/polaris": ">=13",
36
+ "@shopify/polaris-icons": ">=8",
37
+ "react": ">=18",
38
+ "react-dom": ">=18"
39
+ },
34
40
  "devDependencies": {
35
41
  "@types/node": "^20.11.30",
42
+ "@types/react": "^18.2.66",
43
+ "@types/react-dom": "^18.2.22",
36
44
  "tsup": "^8.0.1",
37
45
  "tsx": "^4.7.1",
38
46
  "typescript": "^5.4.5"
@@ -1,26 +0,0 @@
1
- [
2
- {
3
- "key": "progus_cod",
4
- "type": "app",
5
- "title": "Progus COD",
6
- "company": "Progus",
7
- "companyUrl": "https://progus.com",
8
- "desc": "Automate COD Fees & Hide/Show Cash on Delivery by Rules",
9
- "url": "https://apps.shopify.com/progus-cod",
10
- "icon": "https://cdn.shopify.com/app-store/listing_images/bc537219cc3ed2bd4e7e3e683fe6b74a/icon/CMi_6dTEkIoDEAE=.png",
11
- "priority": 100,
12
- "enabled": true
13
- },
14
- {
15
- "key": "progus_trust_badges",
16
- "type": "app",
17
- "title": "Progus Trust Badges",
18
- "company": "Progus",
19
- "companyUrl": "https://progus.com",
20
- "desc": "Add Trust Badges to your store to build trust and credibility.",
21
- "url": "https://apps.shopify.com/progus-trust-badges-1",
22
- "icon": "https://cdn.shopify.com/app-store/listing_images/f9d0009e237f27d2db35b41ef99be858/icon/CJ3y1qDn1JEDEAE=.png",
23
- "priority": 90,
24
- "enabled": true
25
- }
26
- ]