@shop-prompter/react 0.1.1 → 1.0.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/CHANGELOG.md +6 -0
- package/dist/index.js +1361 -372
- package/dist/index.mjs +1362 -373
- package/package.json +24 -23
- package/src/api/icons.ts +9 -0
- package/src/api/product-api.ts +120 -0
- package/src/api/session-handler.ts +153 -0
- package/src/api/shop-prompter-api.ts +58 -30
- package/src/api/shop-prompter.types.ts +3 -1
- package/src/product-card-list.component.jsx +212 -0
- package/src/shop-prompter.component.jsx +897 -273
- package/src/shop-prompter.service.ts +60 -18
- package/src/thumbs-down-icon.component.jsx +25 -0
- package/src/thumbs-up-icon.component.jsx +25 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { productApi } from "./api/product-api";
|
|
5
|
+
const styles = {
|
|
6
|
+
productCardsContainer: {
|
|
7
|
+
marginTop: "0.75rem",
|
|
8
|
+
display: "flex",
|
|
9
|
+
gap: "0.75rem",
|
|
10
|
+
overflowX: "auto",
|
|
11
|
+
width: "100%",
|
|
12
|
+
paddingBottom: "0.5rem",
|
|
13
|
+
boxSizing: "border-box",
|
|
14
|
+
scrollbarWidth: "thin",
|
|
15
|
+
scrollSnapType: "x mandatory",
|
|
16
|
+
WebkitOverflowScrolling: "touch",
|
|
17
|
+
},
|
|
18
|
+
productCard: {
|
|
19
|
+
minWidth: "9.5rem",
|
|
20
|
+
maxWidth: "9.5rem",
|
|
21
|
+
backgroundColor: "white",
|
|
22
|
+
border: "1px solid #f3f4f6",
|
|
23
|
+
borderRadius: "0.75rem",
|
|
24
|
+
overflow: "hidden",
|
|
25
|
+
boxShadow:
|
|
26
|
+
"0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03)",
|
|
27
|
+
display: "flex",
|
|
28
|
+
flexDirection: "column",
|
|
29
|
+
scrollSnapAlign: "start",
|
|
30
|
+
},
|
|
31
|
+
productImageContainer: {
|
|
32
|
+
height: "7rem",
|
|
33
|
+
backgroundColor: "#f9fafb",
|
|
34
|
+
display: "flex",
|
|
35
|
+
alignItems: "center",
|
|
36
|
+
justifyContent: "center",
|
|
37
|
+
padding: "0.5rem",
|
|
38
|
+
borderBottom: "1px solid #f3f4f6",
|
|
39
|
+
},
|
|
40
|
+
productImage: {
|
|
41
|
+
maxHeight: "100%",
|
|
42
|
+
maxWidth: "100%",
|
|
43
|
+
objectFit: "contain",
|
|
44
|
+
},
|
|
45
|
+
productInfo: {
|
|
46
|
+
padding: "0.625rem",
|
|
47
|
+
display: "flex",
|
|
48
|
+
flexDirection: "column",
|
|
49
|
+
gap: "0.25rem",
|
|
50
|
+
},
|
|
51
|
+
productName: {
|
|
52
|
+
fontSize: "0.8125rem",
|
|
53
|
+
fontWeight: 600,
|
|
54
|
+
color: "#1f2937",
|
|
55
|
+
margin: 0,
|
|
56
|
+
whiteSpace: "nowrap",
|
|
57
|
+
overflow: "hidden",
|
|
58
|
+
textOverflow: "ellipsis",
|
|
59
|
+
},
|
|
60
|
+
productPrice: {
|
|
61
|
+
fontSize: "0.8125rem",
|
|
62
|
+
fontWeight: 500,
|
|
63
|
+
color: "#4b5563",
|
|
64
|
+
margin: 0,
|
|
65
|
+
},
|
|
66
|
+
statusContainer: {
|
|
67
|
+
padding: "0.5rem 0",
|
|
68
|
+
fontSize: "0.75rem",
|
|
69
|
+
color: "#6b7280",
|
|
70
|
+
display: "flex",
|
|
71
|
+
alignItems: "center",
|
|
72
|
+
gap: "0.5rem",
|
|
73
|
+
},
|
|
74
|
+
errorText: {
|
|
75
|
+
color: "#ef4444",
|
|
76
|
+
},
|
|
77
|
+
spinner: {
|
|
78
|
+
color: "#6b7280",
|
|
79
|
+
},
|
|
80
|
+
wrapper: {
|
|
81
|
+
width: "100%",
|
|
82
|
+
maxWidth: "100%",
|
|
83
|
+
boxSizing: "border-box",
|
|
84
|
+
overflow: "hidden",
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function ProductCardList(props) {
|
|
89
|
+
const [products, setProducts] = useState(() => []);
|
|
90
|
+
|
|
91
|
+
const [loading, setLoading] = useState(() => false);
|
|
92
|
+
|
|
93
|
+
const [error, setError] = useState(() => null);
|
|
94
|
+
|
|
95
|
+
function fetchProducts() {
|
|
96
|
+
let skusList = props.skus;
|
|
97
|
+
if (typeof skusList === "string") {
|
|
98
|
+
skusList = skusList
|
|
99
|
+
.split(",")
|
|
100
|
+
.map((s) => s.trim())
|
|
101
|
+
.filter(Boolean);
|
|
102
|
+
}
|
|
103
|
+
if (!skusList || skusList.length === 0) {
|
|
104
|
+
setProducts([]);
|
|
105
|
+
setLoading(false);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (productApi.isCached(skusList)) {
|
|
109
|
+
setProducts(productApi.getCached(skusList));
|
|
110
|
+
setLoading(false);
|
|
111
|
+
setError(null);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
setLoading(true);
|
|
115
|
+
setError(null);
|
|
116
|
+
productApi
|
|
117
|
+
.getProducts(skusList)
|
|
118
|
+
.then((data) => {
|
|
119
|
+
setProducts(data || []);
|
|
120
|
+
setLoading(false);
|
|
121
|
+
})
|
|
122
|
+
.catch((err) => {
|
|
123
|
+
console.error("Failed to fetch products:", err);
|
|
124
|
+
setError(err?.message || "Failed to load products");
|
|
125
|
+
setLoading(false);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getProductName(product) {
|
|
130
|
+
const locale = props.defaultLocale || "de";
|
|
131
|
+
const localized =
|
|
132
|
+
product.localization?.[locale] ||
|
|
133
|
+
Object.values(product.localization || {})[0];
|
|
134
|
+
return localized?.name || product.sku;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getProductPrice(product) {
|
|
138
|
+
if (!product.pricing) return "";
|
|
139
|
+
const priceVal = product.pricing.gross;
|
|
140
|
+
const currency = product.pricing.currency;
|
|
141
|
+
if (priceVal === undefined || priceVal === null) return "";
|
|
142
|
+
return priceVal.toFixed(2) + " " + (currency || "€");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getProductImage(product) {
|
|
146
|
+
return (
|
|
147
|
+
product.images?.[0] || "https://placehold.co/150x150?text=" + product.sku
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
fetchProducts();
|
|
153
|
+
}, []);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
fetchProducts();
|
|
156
|
+
}, [
|
|
157
|
+
props.skus
|
|
158
|
+
? Array.isArray(props.skus)
|
|
159
|
+
? props.skus.join(",")
|
|
160
|
+
: props.skus
|
|
161
|
+
: "",
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<>
|
|
166
|
+
{props.skus && props.skus.length > 0 ? (
|
|
167
|
+
<>
|
|
168
|
+
<div style={styles.wrapper}>
|
|
169
|
+
{loading ? (
|
|
170
|
+
<div style={styles.statusContainer}>
|
|
171
|
+
<span>Loading products...</span>
|
|
172
|
+
</div>
|
|
173
|
+
) : null}
|
|
174
|
+
{!loading && error ? (
|
|
175
|
+
<div style={styles.statusContainer}>
|
|
176
|
+
<span style={styles.errorText}>Error: {error}</span>
|
|
177
|
+
</div>
|
|
178
|
+
) : null}
|
|
179
|
+
{!loading && products && products.length > 0 ? (
|
|
180
|
+
<div style={styles.productCardsContainer}>
|
|
181
|
+
{products?.map((product) => (
|
|
182
|
+
<div key={product.sku} style={styles.productCard}>
|
|
183
|
+
<div style={styles.productImageContainer}>
|
|
184
|
+
<img
|
|
185
|
+
src={getProductImage(product)}
|
|
186
|
+
alt={getProductName(product)}
|
|
187
|
+
style={styles.productImage}
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
<div style={styles.productInfo}>
|
|
191
|
+
<p
|
|
192
|
+
style={styles.productName}
|
|
193
|
+
title={getProductName(product)}
|
|
194
|
+
>
|
|
195
|
+
{getProductName(product)}
|
|
196
|
+
</p>
|
|
197
|
+
<p style={styles.productPrice}>
|
|
198
|
+
{getProductPrice(product)}
|
|
199
|
+
</p>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
))}
|
|
203
|
+
</div>
|
|
204
|
+
) : null}
|
|
205
|
+
</div>
|
|
206
|
+
</>
|
|
207
|
+
) : null}
|
|
208
|
+
</>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export default ProductCardList;
|