@wix/headless-stores 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/cjs/dist/react/Category.d.ts +17 -0
  2. package/cjs/dist/react/Category.js +37 -0
  3. package/cjs/dist/react/Collection.d.ts +141 -0
  4. package/cjs/dist/react/Collection.js +198 -0
  5. package/cjs/dist/react/FilteredCollection.d.ts +65 -0
  6. package/cjs/dist/react/FilteredCollection.js +117 -0
  7. package/cjs/dist/react/Product.d.ts +70 -0
  8. package/cjs/dist/react/Product.js +56 -0
  9. package/cjs/dist/react/ProductMediaGallery.d.ts +128 -0
  10. package/cjs/dist/react/ProductMediaGallery.js +100 -0
  11. package/cjs/dist/react/ProductModifiers.d.ts +156 -0
  12. package/cjs/dist/react/ProductModifiers.js +159 -0
  13. package/cjs/dist/react/ProductVariantSelector.d.ts +169 -0
  14. package/cjs/dist/react/ProductVariantSelector.js +166 -0
  15. package/cjs/dist/react/RelatedProducts.d.ts +60 -0
  16. package/cjs/dist/react/RelatedProducts.js +68 -0
  17. package/cjs/dist/react/SocialSharing.d.ts +119 -0
  18. package/cjs/dist/react/SocialSharing.js +80 -0
  19. package/cjs/dist/react/Sort.d.ts +17 -0
  20. package/cjs/dist/react/Sort.js +41 -0
  21. package/cjs/dist/react/index.d.ts +10 -0
  22. package/cjs/dist/react/index.js +33 -0
  23. package/cjs/dist/services/catalog-options-service.d.ts +30 -0
  24. package/cjs/dist/services/catalog-options-service.js +162 -0
  25. package/cjs/dist/services/catalog-price-range-service.d.ts +23 -0
  26. package/cjs/dist/services/catalog-price-range-service.js +95 -0
  27. package/cjs/dist/services/category-service.d.ts +25 -0
  28. package/cjs/dist/services/category-service.js +67 -0
  29. package/cjs/dist/services/collection-service.d.ts +37 -0
  30. package/cjs/dist/services/collection-service.js +454 -0
  31. package/cjs/dist/services/filter-service.d.ts +56 -0
  32. package/cjs/dist/services/filter-service.js +155 -0
  33. package/cjs/dist/services/product-media-gallery-service.d.ts +25 -0
  34. package/cjs/dist/services/product-media-gallery-service.js +105 -0
  35. package/cjs/dist/services/product-modifiers-service.d.ts +36 -0
  36. package/cjs/dist/services/product-modifiers-service.js +104 -0
  37. package/cjs/dist/services/product-service.d.ts +27 -0
  38. package/cjs/dist/services/product-service.js +51 -0
  39. package/cjs/dist/services/related-products-service.d.ts +25 -0
  40. package/cjs/dist/services/related-products-service.js +54 -0
  41. package/cjs/dist/services/selected-variant-service.d.ts +51 -0
  42. package/cjs/dist/services/selected-variant-service.js +396 -0
  43. package/cjs/dist/services/social-sharing-service.d.ts +41 -0
  44. package/cjs/dist/services/social-sharing-service.js +157 -0
  45. package/cjs/dist/services/sort-service.d.ts +19 -0
  46. package/cjs/dist/services/sort-service.js +37 -0
  47. package/cjs/dist/utils/url-params.d.ts +5 -0
  48. package/cjs/dist/utils/url-params.js +50 -0
  49. package/dist/react/Category.d.ts +17 -0
  50. package/dist/react/Category.js +31 -0
  51. package/dist/react/Collection.d.ts +141 -0
  52. package/dist/react/Collection.js +190 -0
  53. package/dist/react/FilteredCollection.d.ts +65 -0
  54. package/dist/react/FilteredCollection.js +107 -0
  55. package/dist/react/Product.d.ts +70 -0
  56. package/dist/react/Product.js +50 -0
  57. package/dist/react/ProductMediaGallery.d.ts +128 -0
  58. package/dist/react/ProductMediaGallery.js +92 -0
  59. package/dist/react/ProductModifiers.d.ts +156 -0
  60. package/dist/react/ProductModifiers.js +151 -0
  61. package/dist/react/ProductVariantSelector.d.ts +169 -0
  62. package/dist/react/ProductVariantSelector.js +157 -0
  63. package/dist/react/RelatedProducts.d.ts +60 -0
  64. package/dist/react/RelatedProducts.js +60 -0
  65. package/dist/react/SocialSharing.d.ts +119 -0
  66. package/dist/react/SocialSharing.js +71 -0
  67. package/dist/react/Sort.d.ts +17 -0
  68. package/dist/react/Sort.js +36 -0
  69. package/dist/react/index.d.ts +10 -0
  70. package/dist/react/index.js +10 -0
  71. package/dist/services/catalog-options-service.d.ts +30 -0
  72. package/dist/services/catalog-options-service.js +158 -0
  73. package/dist/services/catalog-price-range-service.d.ts +23 -0
  74. package/dist/services/catalog-price-range-service.js +91 -0
  75. package/dist/services/category-service.d.ts +25 -0
  76. package/dist/services/category-service.js +63 -0
  77. package/dist/services/collection-service.d.ts +37 -0
  78. package/dist/services/collection-service.js +417 -0
  79. package/dist/services/filter-service.d.ts +56 -0
  80. package/dist/services/filter-service.js +152 -0
  81. package/dist/services/product-media-gallery-service.d.ts +25 -0
  82. package/dist/services/product-media-gallery-service.js +101 -0
  83. package/dist/services/product-modifiers-service.d.ts +36 -0
  84. package/dist/services/product-modifiers-service.js +100 -0
  85. package/dist/services/product-service.d.ts +27 -0
  86. package/dist/services/product-service.js +47 -0
  87. package/dist/services/related-products-service.d.ts +25 -0
  88. package/dist/services/related-products-service.js +50 -0
  89. package/dist/services/selected-variant-service.d.ts +51 -0
  90. package/dist/services/selected-variant-service.js +392 -0
  91. package/dist/services/social-sharing-service.d.ts +41 -0
  92. package/dist/services/social-sharing-service.js +153 -0
  93. package/dist/services/sort-service.d.ts +19 -0
  94. package/dist/services/sort-service.js +34 -0
  95. package/dist/utils/url-params.d.ts +5 -0
  96. package/dist/utils/url-params.js +46 -0
  97. package/package.json +2 -1
@@ -0,0 +1,392 @@
1
+ import { defineService, implementService, } from "@wix/services-definitions";
2
+ import { SignalsServiceDefinition } from "@wix/services-definitions/core-services/signals";
3
+ import { productsV3 } from "@wix/stores";
4
+ export const SelectedVariantServiceDefinition = defineService("selectedVariant");
5
+ export const SelectedVariantService = implementService.withConfig()(SelectedVariantServiceDefinition, ({ getService, config }) => {
6
+ const signalsService = getService(SignalsServiceDefinition);
7
+ // const cartService = getService(CurrentCartServiceDefinition);
8
+ const configProduct = config.product;
9
+ const parsePrice = (amount) => {
10
+ if (!amount)
11
+ return 0;
12
+ const parsed = parseFloat(amount);
13
+ return isNaN(parsed) ? 0 : parsed;
14
+ };
15
+ const processVariantChoices = (variant) => {
16
+ const choices = {};
17
+ if (variant.choices) {
18
+ for (const choice of variant.choices) {
19
+ if (choice.optionChoiceNames?.optionName &&
20
+ choice.optionChoiceNames?.choiceName) {
21
+ choices[choice.optionChoiceNames.optionName] =
22
+ choice.optionChoiceNames.choiceName;
23
+ }
24
+ }
25
+ }
26
+ return choices;
27
+ };
28
+ const findVariantByChoices = (variants, selectedChoices) => {
29
+ return (variants.find((variant) => {
30
+ const variantChoices = processVariantChoices(variant);
31
+ const choiceKeys = Object.keys(selectedChoices);
32
+ return choiceKeys.every((key) => variantChoices[key] === selectedChoices[key]);
33
+ }) || null);
34
+ };
35
+ // const getDefaultVariant = (): productsV3.Variant | null => {
36
+ // const variantsList = variants.get();
37
+ // return variantsList[0] || null;
38
+ // };
39
+ const updateQuantityFromVariant = (variant) => {
40
+ if (variant) {
41
+ const inStock = variant.inventoryStatus?.inStock ?? true;
42
+ const preOrderEnabled = variant.inventoryStatus?.preorderEnabled ?? false;
43
+ // If in stock, allow 999. If out of stock but pre-order enabled, allow 999. Otherwise 0.
44
+ quantityAvailable.set(inStock || preOrderEnabled ? 999 : 0);
45
+ }
46
+ else {
47
+ quantityAvailable.set(0);
48
+ }
49
+ };
50
+ const selectedChoices = signalsService.signal({});
51
+ const isLoading = signalsService.signal(false);
52
+ const error = signalsService.signal(null);
53
+ const variants = signalsService.signal([]);
54
+ const options = signalsService.signal({});
55
+ const basePrice = signalsService.signal(0);
56
+ const discountPrice = signalsService.signal(null);
57
+ const isOnSale = signalsService.signal(null);
58
+ const quantityAvailable = signalsService.signal(0);
59
+ const productId = signalsService.signal("");
60
+ const sku = signalsService.signal("");
61
+ const ribbonLabel = signalsService.signal(null);
62
+ const v3Product = signalsService.signal(configProduct);
63
+ if (configProduct) {
64
+ productId.set(configProduct._id || "");
65
+ ribbonLabel.set(configProduct.ribbon?.name || null);
66
+ const actualPrice = configProduct.actualPriceRange?.minValue?.amount;
67
+ const compareAtPrice = configProduct.compareAtPriceRange?.minValue?.amount;
68
+ basePrice.set(parsePrice(actualPrice));
69
+ discountPrice.set(compareAtPrice ? parsePrice(compareAtPrice) : null);
70
+ isOnSale.set(!!compareAtPrice && parsePrice(compareAtPrice) > parsePrice(actualPrice));
71
+ if (configProduct.options) {
72
+ const optionsMap = {};
73
+ configProduct.options.forEach((option) => {
74
+ if (option.name && option.choicesSettings?.choices) {
75
+ optionsMap[option.name] = option.choicesSettings.choices.map((choice) => choice.name || "");
76
+ }
77
+ });
78
+ options.set(optionsMap);
79
+ }
80
+ if (configProduct.variantsInfo?.variants) {
81
+ variants.set(configProduct.variantsInfo.variants);
82
+ if (configProduct.variantsInfo.variants.length > 0) {
83
+ updateQuantityFromVariant(configProduct.variantsInfo.variants[0] || null);
84
+ }
85
+ }
86
+ else {
87
+ const singleVariant = {
88
+ _id: "default",
89
+ visible: true,
90
+ choices: [],
91
+ price: {
92
+ actualPrice: configProduct.actualPriceRange?.minValue,
93
+ compareAtPrice: configProduct.compareAtPriceRange?.minValue,
94
+ },
95
+ inventoryStatus: {
96
+ inStock: configProduct.inventory?.availabilityStatus === "IN_STOCK" ||
97
+ configProduct.inventory?.availabilityStatus ===
98
+ "PARTIALLY_OUT_OF_STOCK",
99
+ preorderEnabled: configProduct.inventory?.preorderStatus === "ENABLED",
100
+ },
101
+ };
102
+ variants.set([singleVariant]);
103
+ updateQuantityFromVariant(singleVariant);
104
+ }
105
+ }
106
+ const currentVariant = signalsService.computed((() => {
107
+ const prod = v3Product.get();
108
+ const choices = selectedChoices.get();
109
+ if (!prod?.variantsInfo?.variants)
110
+ return null;
111
+ return (prod.variantsInfo.variants.find((variant) => {
112
+ const variantChoices = processVariantChoices(variant);
113
+ if (Object.keys(choices).length !== Object.keys(variantChoices).length)
114
+ return false;
115
+ return Object.entries(choices).every(([optionName, optionValue]) => {
116
+ return variantChoices[optionName] === optionValue;
117
+ });
118
+ }) || null);
119
+ }));
120
+ const selectedVariantId = signalsService.computed(() => {
121
+ const variant = currentVariant.get();
122
+ return variant?._id || null;
123
+ });
124
+ const currentPrice = signalsService.computed(() => {
125
+ const variant = currentVariant.get();
126
+ const prod = v3Product.get();
127
+ // Try to get formatted amount first (if fields worked)
128
+ if (variant?.price?.actualPrice?.formattedAmount) {
129
+ return variant.price.actualPrice.formattedAmount;
130
+ }
131
+ if (prod?.actualPriceRange?.minValue?.formattedAmount) {
132
+ return prod.actualPriceRange.minValue.formattedAmount;
133
+ }
134
+ // Fallback: create our own formatted price from amount
135
+ let rawAmount = null;
136
+ if (variant?.price?.actualPrice?.amount) {
137
+ rawAmount = variant.price.actualPrice.amount;
138
+ }
139
+ else if (prod?.actualPriceRange?.minValue?.amount) {
140
+ rawAmount = prod.actualPriceRange.minValue.amount;
141
+ }
142
+ return rawAmount ? `$${rawAmount}` : "";
143
+ });
144
+ const currentCompareAtPrice = signalsService.computed(() => {
145
+ const variant = currentVariant.get();
146
+ const prod = v3Product.get();
147
+ // Try to get formatted compare-at price first
148
+ if (variant?.price?.compareAtPrice?.formattedAmount) {
149
+ return variant.price.compareAtPrice.formattedAmount;
150
+ }
151
+ if (prod?.compareAtPriceRange?.minValue?.formattedAmount) {
152
+ return prod.compareAtPriceRange.minValue.formattedAmount;
153
+ }
154
+ // Fallback: create our own formatted price from amount
155
+ let rawAmount = null;
156
+ if (variant?.price?.compareAtPrice?.amount) {
157
+ rawAmount = variant.price.compareAtPrice.amount;
158
+ }
159
+ else if (prod?.compareAtPriceRange?.minValue?.amount) {
160
+ rawAmount = prod.compareAtPriceRange.minValue.amount;
161
+ }
162
+ return rawAmount ? `$${rawAmount}` : null;
163
+ });
164
+ const isInStock = signalsService.computed(() => {
165
+ const variant = currentVariant.get();
166
+ if (variant) {
167
+ return variant.inventoryStatus?.inStock ?? false;
168
+ }
169
+ else {
170
+ return false;
171
+ }
172
+ });
173
+ const isPreOrderEnabled = signalsService.computed(() => {
174
+ const variant = currentVariant.get();
175
+ return variant?.inventoryStatus?.preorderEnabled ?? false;
176
+ });
177
+ const product = v3Product;
178
+ const productOptions = signalsService.computed(() => {
179
+ const prod = v3Product.get();
180
+ return prod?.options || [];
181
+ });
182
+ const currency = signalsService.computed(() => {
183
+ const prod = v3Product.get();
184
+ return prod?.currency || "USD";
185
+ });
186
+ const selectedVariant = () => {
187
+ const variantId = selectedVariantId.get();
188
+ const variantsList = variants.get();
189
+ return variantsList.find((v) => v._id === variantId) || null;
190
+ };
191
+ const finalPrice = () => {
192
+ const discount = discountPrice.get();
193
+ const base = basePrice.get();
194
+ return discount !== null ? discount : base;
195
+ };
196
+ // @ts-ignore
197
+ const isLowStock = (threshold = 5) => {
198
+ return false;
199
+ };
200
+ const setSelectedChoices = (choices) => {
201
+ selectedChoices.set(choices);
202
+ const matchingVariant = findVariantByChoices(variants.get(), choices);
203
+ updateQuantityFromVariant(matchingVariant);
204
+ };
205
+ const addToCart = async (quantity = 1, modifiers) => {
206
+ try {
207
+ isLoading.set(true);
208
+ error.set(null);
209
+ const prod = v3Product.get();
210
+ const variant = currentVariant.get();
211
+ if (!prod?._id) {
212
+ throw new Error("Product not found");
213
+ }
214
+ // Build catalog reference with modifiers if provided
215
+ const catalogReference = {
216
+ catalogItemId: prod._id,
217
+ appId: "215238eb-22a5-4c36-9e7b-e7c08025e04e",
218
+ options: variant?._id ? { variantId: variant._id, preOrderRequested: !!variant?.inventoryStatus?.preorderEnabled } : undefined,
219
+ };
220
+ // Transform and add modifiers to catalog reference if they exist
221
+ if (modifiers && Object.keys(modifiers).length > 0) {
222
+ const options = {};
223
+ const customTextFields = {};
224
+ // Get product modifiers to determine types and keys
225
+ const productModifiers = prod.modifiers || [];
226
+ Object.values(modifiers).forEach((modifierValue) => {
227
+ const modifierName = modifierValue.modifierName;
228
+ const productModifier = productModifiers.find((m) => m.name === modifierName);
229
+ if (!productModifier)
230
+ return;
231
+ const renderType = productModifier.modifierRenderType;
232
+ if (renderType === "TEXT_CHOICES" ||
233
+ renderType === "SWATCH_CHOICES") {
234
+ // For choice modifiers, use the modifier key and choice value
235
+ const modifierKey = productModifier.key || modifierName;
236
+ if (modifierValue.choiceValue) {
237
+ options[modifierKey] = modifierValue.choiceValue;
238
+ }
239
+ }
240
+ else if (renderType === "FREE_TEXT") {
241
+ // For free text modifiers, use the freeTextSettings key
242
+ const freeTextKey = productModifier.freeTextSettings?.key || modifierName;
243
+ if (modifierValue.freeTextValue) {
244
+ customTextFields[freeTextKey] = modifierValue.freeTextValue;
245
+ }
246
+ }
247
+ });
248
+ // Add formatted modifiers to catalog reference
249
+ if (Object.keys(options).length > 0) {
250
+ catalogReference.options = {
251
+ ...catalogReference.options,
252
+ options,
253
+ };
254
+ }
255
+ if (Object.keys(customTextFields).length > 0) {
256
+ catalogReference.options = {
257
+ ...catalogReference.options,
258
+ customTextFields,
259
+ };
260
+ }
261
+ }
262
+ // @ts-ignore
263
+ const lineItems = [
264
+ {
265
+ catalogReference,
266
+ quantity,
267
+ },
268
+ ];
269
+ // await cartService.addToCart(lineItems);
270
+ }
271
+ catch (err) {
272
+ error.set(err instanceof Error ? err.message : "Failed to add to cart");
273
+ }
274
+ finally {
275
+ isLoading.set(false);
276
+ }
277
+ };
278
+ const setOption = (group, value) => {
279
+ const currentChoices = selectedChoices.get();
280
+ const newChoices = { ...currentChoices, [group]: value };
281
+ setSelectedChoices(newChoices);
282
+ };
283
+ const selectVariantById = (id) => {
284
+ const variantsList = variants.get();
285
+ const variant = variantsList.find((v) => v._id === id);
286
+ if (variant) {
287
+ const variantChoices = processVariantChoices(variant);
288
+ selectedChoices.set(variantChoices);
289
+ updateQuantityFromVariant(variant);
290
+ }
291
+ };
292
+ const loadProductVariants = (data) => {
293
+ variants.set(data);
294
+ if (data.length > 0) {
295
+ updateQuantityFromVariant(data[0] || null);
296
+ }
297
+ };
298
+ const resetSelections = () => {
299
+ selectedChoices.set({});
300
+ };
301
+ // New methods for smart variant selection
302
+ const getAvailableChoicesForOption = (optionName) => {
303
+ const currentChoices = selectedChoices.get();
304
+ const variantsList = variants.get();
305
+ // Get all possible choices for this option that result in valid variants
306
+ const availableChoices = new Set();
307
+ variantsList.forEach((variant) => {
308
+ const variantChoices = processVariantChoices(variant);
309
+ // Check if this variant matches all currently selected choices (except for the option we're checking)
310
+ const matchesOtherChoices = Object.entries(currentChoices)
311
+ .filter(([key]) => key !== optionName)
312
+ .every(([key, value]) => variantChoices[key] === value);
313
+ if (matchesOtherChoices && variantChoices[optionName]) {
314
+ availableChoices.add(variantChoices[optionName]);
315
+ }
316
+ });
317
+ return Array.from(availableChoices);
318
+ };
319
+ const isChoiceAvailable = (optionName, choiceValue) => {
320
+ const availableChoices = getAvailableChoicesForOption(optionName);
321
+ return availableChoices.includes(choiceValue);
322
+ };
323
+ const hasAnySelections = () => {
324
+ const currentChoices = selectedChoices.get();
325
+ return Object.keys(currentChoices).length > 0;
326
+ };
327
+ return {
328
+ selectedChoices,
329
+ selectedVariantId,
330
+ currentVariant,
331
+ currentPrice,
332
+ currentCompareAtPrice,
333
+ isInStock,
334
+ isPreOrderEnabled,
335
+ isLoading,
336
+ error,
337
+ variants,
338
+ options,
339
+ basePrice,
340
+ discountPrice,
341
+ isOnSale,
342
+ quantityAvailable,
343
+ productId,
344
+ sku,
345
+ ribbonLabel,
346
+ setSelectedChoices,
347
+ addToCart,
348
+ setOption,
349
+ selectVariantById,
350
+ loadProductVariants,
351
+ resetSelections,
352
+ // New methods for smart variant selection
353
+ getAvailableChoicesForOption,
354
+ isChoiceAvailable,
355
+ hasAnySelections,
356
+ selectedVariant,
357
+ finalPrice,
358
+ isLowStock,
359
+ product,
360
+ productOptions,
361
+ currency,
362
+ };
363
+ });
364
+ export async function loadSelectedVariantServiceConfig(productSlug) {
365
+ try {
366
+ // Use getProductBySlug directly - single API call with comprehensive fields
367
+ const productResponse = await productsV3.getProductBySlug(productSlug, {
368
+ fields: [
369
+ "DESCRIPTION",
370
+ "DIRECT_CATEGORIES_INFO",
371
+ "BREADCRUMBS_INFO",
372
+ "INFO_SECTION",
373
+ "MEDIA_ITEMS_INFO",
374
+ "PLAIN_DESCRIPTION",
375
+ "THUMBNAIL",
376
+ "URL",
377
+ "VARIANT_OPTION_CHOICE_NAMES",
378
+ "WEIGHT_MEASUREMENT_UNIT_INFO",
379
+ ],
380
+ });
381
+ if (!productResponse.product) {
382
+ throw new Error("Product not found");
383
+ }
384
+ return {
385
+ product: productResponse.product,
386
+ };
387
+ }
388
+ catch (error) {
389
+ console.error(`Failed to load product for slug "${productSlug}":`, error);
390
+ throw error;
391
+ }
392
+ }
@@ -0,0 +1,41 @@
1
+ import { type ServiceFactoryConfig } from "@wix/services-definitions";
2
+ import type { Signal } from "./Signal";
3
+ export interface SharingPlatform {
4
+ name: string;
5
+ icon: string;
6
+ color: string;
7
+ shareUrl: string;
8
+ }
9
+ export interface SocialSharingServiceAPI {
10
+ availablePlatforms: Signal<SharingPlatform[]>;
11
+ shareCount: Signal<number>;
12
+ lastSharedPlatform: Signal<string | null>;
13
+ shareToFacebook: (url: string, title: string, description?: string) => void;
14
+ shareToTwitter: (url: string, text: string, hashtags?: string[]) => void;
15
+ shareToLinkedIn: (url: string, title: string, summary?: string) => void;
16
+ shareToWhatsApp: (url: string, text: string) => void;
17
+ shareToEmail: (url: string, subject: string, body: string) => void;
18
+ copyToClipboard: (url: string) => Promise<boolean>;
19
+ shareNative: (data: {
20
+ title: string;
21
+ text: string;
22
+ url: string;
23
+ }) => Promise<boolean>;
24
+ trackShare: (platform: string) => void;
25
+ }
26
+ export declare const SocialSharingServiceDefinition: string & {
27
+ __api: SocialSharingServiceAPI;
28
+ __config: {};
29
+ isServiceDefinition?: boolean;
30
+ } & SocialSharingServiceAPI;
31
+ export declare const SocialSharingService: import("@wix/services-definitions").ServiceFactory<string & {
32
+ __api: SocialSharingServiceAPI;
33
+ __config: {};
34
+ isServiceDefinition?: boolean;
35
+ } & SocialSharingServiceAPI, {
36
+ productName: string;
37
+ productUrl: string;
38
+ productDescription?: string;
39
+ productImage?: string;
40
+ }, import("@wix/services-definitions").ThreadMode.MAIN>;
41
+ export declare function loadSocialSharingServiceConfig(productName: string, productUrl: string, productDescription?: string, productImage?: string): Promise<ServiceFactoryConfig<typeof SocialSharingService>>;
@@ -0,0 +1,153 @@
1
+ import { defineService, implementService, } from "@wix/services-definitions";
2
+ import { SignalsServiceDefinition } from "@wix/services-definitions/core-services/signals";
3
+ export const SocialSharingServiceDefinition = defineService("socialSharing");
4
+ export const SocialSharingService = implementService.withConfig()(SocialSharingServiceDefinition, ({ getService }) => {
5
+ const signalsService = getService(SignalsServiceDefinition);
6
+ const availablePlatforms = signalsService.signal([
7
+ {
8
+ name: "Facebook",
9
+ icon: "facebook",
10
+ color: "#1877F2",
11
+ shareUrl: "https://www.facebook.com/sharer/sharer.php",
12
+ },
13
+ {
14
+ name: "Twitter",
15
+ icon: "twitter",
16
+ color: "#1DA1F2",
17
+ shareUrl: "https://twitter.com/intent/tweet",
18
+ },
19
+ {
20
+ name: "LinkedIn",
21
+ icon: "linkedin",
22
+ color: "#0A66C2",
23
+ shareUrl: "https://www.linkedin.com/sharing/share-offsite/",
24
+ },
25
+ {
26
+ name: "WhatsApp",
27
+ icon: "whatsapp",
28
+ color: "#25D366",
29
+ shareUrl: "https://wa.me/",
30
+ },
31
+ {
32
+ name: "Email",
33
+ icon: "mail",
34
+ color: "#EA4335",
35
+ shareUrl: "mailto:",
36
+ },
37
+ ]);
38
+ const shareCount = signalsService.signal(0);
39
+ const lastSharedPlatform = signalsService.signal(null);
40
+ const openShareWindow = (url, platform) => {
41
+ const width = 600;
42
+ const height = 400;
43
+ const left = (window.screen.width - width) / 2;
44
+ const top = (window.screen.height - height) / 2;
45
+ window.open(url, `share-${platform}`, `width=${width},height=${height},left=${left},top=${top},scrollbars=yes,resizable=yes`);
46
+ trackShare(platform);
47
+ };
48
+ const shareToFacebook = (url, title, description) => {
49
+ const shareUrl = new URL("https://www.facebook.com/sharer/sharer.php");
50
+ shareUrl.searchParams.set("u", url);
51
+ shareUrl.searchParams.set("quote", `${title}${description ? ` - ${description}` : ""}`);
52
+ openShareWindow(shareUrl.toString(), "facebook");
53
+ };
54
+ const shareToTwitter = (url, text, hashtags) => {
55
+ const shareUrl = new URL("https://twitter.com/intent/tweet");
56
+ shareUrl.searchParams.set("url", url);
57
+ shareUrl.searchParams.set("text", text);
58
+ if (hashtags && hashtags.length > 0) {
59
+ shareUrl.searchParams.set("hashtags", hashtags.join(","));
60
+ }
61
+ openShareWindow(shareUrl.toString(), "twitter");
62
+ };
63
+ const shareToLinkedIn = (url, title, summary) => {
64
+ const shareUrl = new URL("https://www.linkedin.com/sharing/share-offsite/");
65
+ shareUrl.searchParams.set("url", url);
66
+ shareUrl.searchParams.set("title", title);
67
+ if (summary) {
68
+ shareUrl.searchParams.set("summary", summary);
69
+ }
70
+ openShareWindow(shareUrl.toString(), "linkedin");
71
+ };
72
+ const shareToWhatsApp = (url, text) => {
73
+ const message = `${text} ${url}`;
74
+ const shareUrl = `https://wa.me/?text=${encodeURIComponent(message)}`;
75
+ openShareWindow(shareUrl, "whatsapp");
76
+ };
77
+ const shareToEmail = (url, subject, body) => {
78
+ const emailBody = `${body}\n\n${url}`;
79
+ const mailtoUrl = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(emailBody)}`;
80
+ window.location.href = mailtoUrl;
81
+ trackShare("email");
82
+ };
83
+ const copyToClipboard = async (url) => {
84
+ try {
85
+ if (navigator.clipboard && window.isSecureContext) {
86
+ await navigator.clipboard.writeText(url);
87
+ trackShare("clipboard");
88
+ return true;
89
+ }
90
+ else {
91
+ const textArea = document.createElement("textarea");
92
+ textArea.value = url;
93
+ textArea.style.position = "fixed";
94
+ textArea.style.left = "-999999px";
95
+ textArea.style.top = "-999999px";
96
+ document.body.appendChild(textArea);
97
+ textArea.focus();
98
+ textArea.select();
99
+ const success = document.execCommand("copy");
100
+ textArea.remove();
101
+ if (success) {
102
+ trackShare("clipboard");
103
+ }
104
+ return success;
105
+ }
106
+ }
107
+ catch (err) {
108
+ console.error("Failed to copy to clipboard:", err);
109
+ return false;
110
+ }
111
+ };
112
+ const shareNative = async (data) => {
113
+ try {
114
+ if (navigator.share) {
115
+ await navigator.share(data);
116
+ trackShare("native");
117
+ return true;
118
+ }
119
+ return false;
120
+ }
121
+ catch (err) {
122
+ console.error("Failed to share natively:", err);
123
+ return false;
124
+ }
125
+ };
126
+ const trackShare = (platform) => {
127
+ const currentCount = shareCount.get();
128
+ shareCount.set(currentCount + 1);
129
+ lastSharedPlatform.set(platform);
130
+ console.log(`Shared to ${platform} - Total shares: ${currentCount + 1}`);
131
+ };
132
+ return {
133
+ availablePlatforms,
134
+ shareCount,
135
+ lastSharedPlatform,
136
+ shareToFacebook,
137
+ shareToTwitter,
138
+ shareToLinkedIn,
139
+ shareToWhatsApp,
140
+ shareToEmail,
141
+ copyToClipboard,
142
+ shareNative,
143
+ trackShare,
144
+ };
145
+ });
146
+ export async function loadSocialSharingServiceConfig(productName, productUrl, productDescription, productImage) {
147
+ return {
148
+ productName,
149
+ productUrl,
150
+ productDescription,
151
+ productImage,
152
+ };
153
+ }
@@ -0,0 +1,19 @@
1
+ import type { Signal } from "./Signal";
2
+ export type SortBy = "" | "name-asc" | "name-desc" | "price-asc" | "price-desc";
3
+ export interface SortServiceAPI {
4
+ currentSort: Signal<SortBy>;
5
+ setSortBy: (sortBy: SortBy) => Promise<void>;
6
+ }
7
+ export declare const SortServiceDefinition: string & {
8
+ __api: SortServiceAPI;
9
+ __config: {};
10
+ isServiceDefinition?: boolean;
11
+ } & SortServiceAPI;
12
+ export declare const defaultSort: SortBy;
13
+ export declare const SortService: import("@wix/services-definitions").ServiceFactory<string & {
14
+ __api: SortServiceAPI;
15
+ __config: {};
16
+ isServiceDefinition?: boolean;
17
+ } & SortServiceAPI, {
18
+ initialSort?: SortBy;
19
+ }, import("@wix/services-definitions").ThreadMode.MAIN>;
@@ -0,0 +1,34 @@
1
+ import { defineService, implementService } from "@wix/services-definitions";
2
+ import { SignalsServiceDefinition } from "@wix/services-definitions/core-services/signals";
3
+ import { URLParamsUtils } from "../utils/url-params";
4
+ export const SortServiceDefinition = defineService("sort");
5
+ export const defaultSort = "";
6
+ export const SortService = implementService.withConfig()(SortServiceDefinition, ({ getService, config }) => {
7
+ const signalsService = getService(SignalsServiceDefinition);
8
+ const currentSort = signalsService.signal((config.initialSort || defaultSort));
9
+ const setSortBy = async (sortBy) => {
10
+ currentSort.set(sortBy);
11
+ // Update URL with sort parameter
12
+ const currentParams = URLParamsUtils.getURLParams();
13
+ const sortMap = {
14
+ "name-asc": "name_asc",
15
+ "name-desc": "name_desc",
16
+ "price-asc": "price_asc",
17
+ "price-desc": "price_desc",
18
+ "": "newest",
19
+ };
20
+ const sortParam = sortMap[sortBy] || "newest";
21
+ const urlParams = { ...currentParams };
22
+ if (sortParam !== "newest") {
23
+ urlParams["sort"] = sortParam;
24
+ }
25
+ else {
26
+ delete urlParams["sort"];
27
+ }
28
+ URLParamsUtils.updateURL(urlParams);
29
+ };
30
+ return {
31
+ currentSort,
32
+ setSortBy,
33
+ };
34
+ });
@@ -0,0 +1,5 @@
1
+ export declare class URLParamsUtils {
2
+ static parseSearchParams(searchParams: URLSearchParams): Record<string, string | string[]>;
3
+ static updateURL(params: Record<string, string | string[]>): void;
4
+ static getURLParams(): Record<string, string | string[]>;
5
+ }