@mostfeatured/dbi 0.2.14 → 0.2.16
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/src/types/Components/HTMLComponentsV2/index.d.ts +35 -1
- package/dist/src/types/Components/HTMLComponentsV2/index.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/index.js +416 -83
- package/dist/src/types/Components/HTMLComponentsV2/index.js.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/parser.d.ts +52 -0
- package/dist/src/types/Components/HTMLComponentsV2/parser.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/parser.js +275 -0
- package/dist/src/types/Components/HTMLComponentsV2/parser.js.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.d.ts +28 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js +478 -34
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts +12 -0
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.js +102 -18
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.js.map +1 -1
- package/dist/test/index.js +76 -3
- package/dist/test/index.js.map +1 -1
- package/dist/test/test.d.ts +2 -0
- package/dist/test/test.d.ts.map +1 -0
- package/dist/test/test.js +7 -0
- package/dist/test/test.js.map +1 -0
- package/docs/ADVANCED_FEATURES.md +4 -0
- package/docs/API_REFERENCE.md +4 -0
- package/docs/CHAT_INPUT.md +4 -0
- package/docs/COMPONENTS.md +4 -0
- package/docs/EVENTS.md +4 -0
- package/docs/GETTING_STARTED.md +4 -0
- package/docs/LOCALIZATION.md +4 -0
- package/docs/README.md +4 -0
- package/docs/SVELTE_COMPONENTS.md +162 -6
- package/docs/llm/ADVANCED_FEATURES.txt +521 -0
- package/docs/llm/API_REFERENCE.txt +659 -0
- package/docs/llm/CHAT_INPUT.txt +514 -0
- package/docs/llm/COMPONENTS.txt +595 -0
- package/docs/llm/EVENTS.txt +449 -0
- package/docs/llm/GETTING_STARTED.txt +296 -0
- package/docs/llm/LOCALIZATION.txt +501 -0
- package/docs/llm/README.txt +193 -0
- package/docs/llm/SVELTE_COMPONENTS.txt +566 -0
- package/generated/svelte-dbi.d.ts +122 -0
- package/package.json +1 -1
- package/src/types/Components/HTMLComponentsV2/index.ts +478 -95
- package/src/types/Components/HTMLComponentsV2/parser.ts +317 -0
- package/src/types/Components/HTMLComponentsV2/svelteParser.ts +536 -35
- package/src/types/Components/HTMLComponentsV2/svelteRenderer.ts +121 -20
- package/test/index.ts +76 -3
- package/test/product-showcase.svelte +383 -24
- package/test/test.ts +3 -0
- package/llm.txt +0 -1088
|
@@ -1,31 +1,132 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
/// <reference types="@mostfeatured/dbi/svelte" />
|
|
3
3
|
import stuffs from "stuffs";
|
|
4
|
+
import { add } from "./test.js";
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
products = [],
|
|
7
8
|
currentIndex = 0,
|
|
8
9
|
cart = [],
|
|
9
|
-
view = "browse", // 'browse' | 'cart' | 'details'
|
|
10
|
+
view = "browse", // 'browse' | 'cart' | 'details' | 'reviews'
|
|
10
11
|
elapsedTime = 0,
|
|
12
|
+
editingProduct = null, // Product being edited in modal
|
|
13
|
+
reviewText = "", // Review text from modal
|
|
14
|
+
reviews = [], // Array of { productId, productName, rating, review, date }
|
|
15
|
+
selectedCategory = "all", // Category filter
|
|
16
|
+
sortBy = "default", // Sorting option
|
|
11
17
|
} = $props();
|
|
12
18
|
|
|
19
|
+
// Get unique categories from products
|
|
20
|
+
// Note: Uses local variables for SSR template rendering
|
|
21
|
+
function getCategories() {
|
|
22
|
+
const cats = [...new Set(products.map((p) => p.category))];
|
|
23
|
+
return cats;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Filter and sort products
|
|
27
|
+
// Note: Uses local variables for SSR template rendering
|
|
28
|
+
function getFilteredProducts() {
|
|
29
|
+
let filtered = products;
|
|
30
|
+
|
|
31
|
+
// Apply category filter
|
|
32
|
+
if (selectedCategory !== "all") {
|
|
33
|
+
filtered = filtered.filter((p) => p.category === selectedCategory);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Apply sorting
|
|
37
|
+
if (sortBy === "price-low") {
|
|
38
|
+
filtered = [...filtered].sort((a, b) => a.price - b.price);
|
|
39
|
+
} else if (sortBy === "price-high") {
|
|
40
|
+
filtered = [...filtered].sort((a, b) => b.price - a.price);
|
|
41
|
+
} else if (sortBy === "name") {
|
|
42
|
+
filtered = [...filtered].sort((a, b) => a.name.localeCompare(b.name));
|
|
43
|
+
} else if (sortBy === "rating") {
|
|
44
|
+
filtered = [...filtered].sort((a, b) => b.rating - a.rating);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return filtered;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Get current product from filtered list
|
|
51
|
+
// Note: Uses local variables for SSR template rendering
|
|
52
|
+
function getCurrentProduct() {
|
|
53
|
+
const filtered = getFilteredProducts();
|
|
54
|
+
if (filtered.length === 0) return null;
|
|
55
|
+
const idx = Math.min(currentIndex, filtered.length - 1);
|
|
56
|
+
return filtered[idx];
|
|
57
|
+
}
|
|
58
|
+
|
|
13
59
|
// Format elapsed time using stuffs
|
|
14
60
|
function formatTime(seconds) {
|
|
15
61
|
return stuffs.formatSeconds(seconds);
|
|
16
62
|
}
|
|
17
63
|
|
|
64
|
+
// Handle category selection
|
|
65
|
+
function onCategoryChange(ctx) {
|
|
66
|
+
const selected = ctx.interaction.values[0];
|
|
67
|
+
data.selectedCategory = selected;
|
|
68
|
+
data.currentIndex = 0; // Reset to first product when filter changes
|
|
69
|
+
ctx.interaction.deferUpdate();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Handle sort selection
|
|
73
|
+
function onSortChange(ctx) {
|
|
74
|
+
const selected = ctx.interaction.values[0];
|
|
75
|
+
data.sortBy = selected;
|
|
76
|
+
data.currentIndex = 0; // Reset to first product when sort changes
|
|
77
|
+
ctx.interaction.deferUpdate();
|
|
78
|
+
}
|
|
79
|
+
|
|
18
80
|
function nextProduct() {
|
|
19
|
-
|
|
81
|
+
// Get filtered length using data context
|
|
82
|
+
let filtered = data.products || [];
|
|
83
|
+
const category = data.selectedCategory ?? "all";
|
|
84
|
+
if (category !== "all") {
|
|
85
|
+
filtered = filtered.filter((p) => p.category === category);
|
|
86
|
+
}
|
|
87
|
+
const currentIdx = data.currentIndex ?? 0;
|
|
88
|
+
data.currentIndex = (currentIdx + 1) % filtered.length;
|
|
20
89
|
}
|
|
21
90
|
|
|
22
91
|
function prevProduct() {
|
|
23
|
-
|
|
92
|
+
// Get filtered length using data context
|
|
93
|
+
let filtered = data.products || [];
|
|
94
|
+
const category = data.selectedCategory ?? "all";
|
|
95
|
+
if (category !== "all") {
|
|
96
|
+
filtered = filtered.filter((p) => p.category === category);
|
|
97
|
+
}
|
|
98
|
+
const currentIdx = data.currentIndex ?? 0;
|
|
99
|
+
data.currentIndex = (currentIdx - 1 + filtered.length) % filtered.length;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Helper to get current product in handler context
|
|
103
|
+
function getProductInHandler() {
|
|
104
|
+
let filtered = data.products || [];
|
|
105
|
+
const category = data.selectedCategory ?? "all";
|
|
106
|
+
const sort = data.sortBy ?? "default";
|
|
107
|
+
|
|
108
|
+
if (category !== "all") {
|
|
109
|
+
filtered = filtered.filter((p) => p.category === category);
|
|
110
|
+
}
|
|
111
|
+
// Apply sorting
|
|
112
|
+
if (sort === "price-low") {
|
|
113
|
+
filtered = [...filtered].sort((a, b) => a.price - b.price);
|
|
114
|
+
} else if (sort === "price-high") {
|
|
115
|
+
filtered = [...filtered].sort((a, b) => b.price - a.price);
|
|
116
|
+
} else if (sort === "name") {
|
|
117
|
+
filtered = [...filtered].sort((a, b) => a.name.localeCompare(b.name));
|
|
118
|
+
} else if (sort === "rating") {
|
|
119
|
+
filtered = [...filtered].sort((a, b) => b.rating - a.rating);
|
|
120
|
+
}
|
|
121
|
+
if (filtered.length === 0) return null;
|
|
122
|
+
const idx = Math.min(data.currentIndex ?? 0, filtered.length - 1);
|
|
123
|
+
return filtered[idx];
|
|
24
124
|
}
|
|
25
125
|
|
|
26
126
|
function addToCart(ctx) {
|
|
27
|
-
const product =
|
|
28
|
-
|
|
127
|
+
const product = getProductInHandler();
|
|
128
|
+
if (!product) return;
|
|
129
|
+
data.cart = [...data.cart, product];
|
|
29
130
|
ctx.interaction.reply({
|
|
30
131
|
content:
|
|
31
132
|
"✅ Added **" +
|
|
@@ -49,6 +150,10 @@
|
|
|
49
150
|
data.view = "details";
|
|
50
151
|
}
|
|
51
152
|
|
|
153
|
+
function showReviews() {
|
|
154
|
+
data.view = "reviews";
|
|
155
|
+
}
|
|
156
|
+
|
|
52
157
|
function clearCart(ctx) {
|
|
53
158
|
data.cart = [];
|
|
54
159
|
ctx.interaction.reply({
|
|
@@ -58,7 +163,7 @@
|
|
|
58
163
|
}
|
|
59
164
|
|
|
60
165
|
function checkout(ctx) {
|
|
61
|
-
if (cart.length === 0) {
|
|
166
|
+
if (data.cart.length === 0) {
|
|
62
167
|
ctx.interaction.reply({
|
|
63
168
|
content: "Cart is empty!",
|
|
64
169
|
flags: ["Ephemeral"],
|
|
@@ -66,20 +171,102 @@
|
|
|
66
171
|
noRender(); // Cart boşsa UI güncellemeye gerek yok
|
|
67
172
|
return;
|
|
68
173
|
}
|
|
69
|
-
const total = cart.reduce((sum, p) => sum + p.price, 0);
|
|
174
|
+
const total = data.cart.reduce((sum, p) => sum + p.price, 0);
|
|
70
175
|
ctx.interaction.reply({
|
|
71
176
|
content:
|
|
72
|
-
"💳 **Order Placed!**\\nItems: " +
|
|
177
|
+
"💳 **Order Placed!**\\nItems: " +
|
|
178
|
+
data.cart.length +
|
|
179
|
+
"\\nTotal: $" +
|
|
180
|
+
total,
|
|
73
181
|
flags: ["Ephemeral"],
|
|
74
182
|
});
|
|
75
183
|
data.cart = [];
|
|
76
184
|
data.view = "browse";
|
|
77
185
|
}
|
|
78
186
|
|
|
187
|
+
// Open the review modal for current product
|
|
188
|
+
async function openReviewModal(ctx) {
|
|
189
|
+
// Store which product we're reviewing
|
|
190
|
+
const product = getProductInHandler();
|
|
191
|
+
if (!product) {
|
|
192
|
+
ctx.interaction.reply({
|
|
193
|
+
content: "No product selected!",
|
|
194
|
+
flags: ["Ephemeral"],
|
|
195
|
+
});
|
|
196
|
+
noRender();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
data.editingProduct = product;
|
|
200
|
+
|
|
201
|
+
// Show the modal and await the response
|
|
202
|
+
const { fields, interaction } = await showModal("review-modal");
|
|
203
|
+
|
|
204
|
+
// Handle the response - rating now comes from string-select as an array
|
|
205
|
+
const ratingArray = fields.rating || ["5"]; // string-select returns array
|
|
206
|
+
const rating = Array.isArray(ratingArray) ? ratingArray[0] : ratingArray;
|
|
207
|
+
const review = fields.review || "";
|
|
208
|
+
|
|
209
|
+
// Add review to the reviews array (use data.reviews for current value after await)
|
|
210
|
+
data.reviews = [
|
|
211
|
+
...data.reviews,
|
|
212
|
+
{
|
|
213
|
+
productId: currentIndex,
|
|
214
|
+
productName: product?.name,
|
|
215
|
+
rating: parseInt(rating),
|
|
216
|
+
review: review,
|
|
217
|
+
date: new Date().toLocaleDateString(),
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
interaction.reply({
|
|
222
|
+
content: `⭐ **Review Submitted!**\n**Product:** ${product?.name}\n**Rating:** ${rating}/5\n**Review:** ${review}`,
|
|
223
|
+
flags: ["Ephemeral"],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Clear editing state
|
|
227
|
+
data.editingProduct = null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Open quantity modal for adding to cart
|
|
231
|
+
async function openQuantityModal(ctx) {
|
|
232
|
+
const product = getProductInHandler();
|
|
233
|
+
if (!product) {
|
|
234
|
+
ctx.interaction.reply({
|
|
235
|
+
content: "No product selected!",
|
|
236
|
+
flags: ["Ephemeral"],
|
|
237
|
+
});
|
|
238
|
+
noRender();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
data.editingProduct = product;
|
|
242
|
+
|
|
243
|
+
// Show the modal and await the response
|
|
244
|
+
const { fields, interaction } = await showModal("quantity-modal");
|
|
245
|
+
|
|
246
|
+
const quantity = parseInt(fields.quantity) || 1;
|
|
247
|
+
|
|
248
|
+
// Add multiple of the same product
|
|
249
|
+
for (let i = 0; i < quantity; i++) {
|
|
250
|
+
data.cart = [...data.cart, product];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
interaction.reply({
|
|
254
|
+
content: `✅ Added **${quantity}x ${product?.name}** to cart! (Cart: ${data.cart.length} items)`,
|
|
255
|
+
flags: ["Ephemeral"],
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
data.editingProduct = null;
|
|
259
|
+
}
|
|
260
|
+
|
|
79
261
|
onMount(() => {
|
|
262
|
+
console.log(add(2, 3)); // Test the imported function
|
|
263
|
+
|
|
80
264
|
data.elapsedTime = 0;
|
|
81
265
|
const interval = setInterval(() => {
|
|
82
|
-
|
|
266
|
+
// Use lowPriorityUpdate to skip render if a user interaction is in progress
|
|
267
|
+
lowPriorityUpdate(() => {
|
|
268
|
+
data.elapsedTime += 1;
|
|
269
|
+
});
|
|
83
270
|
}, 1000);
|
|
84
271
|
|
|
85
272
|
return () => {
|
|
@@ -88,6 +275,7 @@
|
|
|
88
275
|
});
|
|
89
276
|
</script>
|
|
90
277
|
|
|
278
|
+
<!-- Main UI Components -->
|
|
91
279
|
<components>
|
|
92
280
|
{#if view === "browse"}
|
|
93
281
|
<container accent-color="5865F2">
|
|
@@ -96,32 +284,115 @@
|
|
|
96
284
|
<components>
|
|
97
285
|
<text-display>## 🛍️ Product Showcase</text-display>
|
|
98
286
|
<text-display
|
|
99
|
-
>**{
|
|
100
|
-
{
|
|
287
|
+
>**{getCurrentProduct()?.name || "No products"}**
|
|
288
|
+
{getCurrentProduct()?.description || ""}</text-display
|
|
101
289
|
>
|
|
102
|
-
<text-display
|
|
290
|
+
<text-display
|
|
291
|
+
>💰 **\${getCurrentProduct()?.price || 0}**</text-display
|
|
103
292
|
>
|
|
104
293
|
</components>
|
|
105
|
-
<thumbnail media={
|
|
294
|
+
<thumbnail media={getCurrentProduct()?.image}></thumbnail>
|
|
106
295
|
</section>
|
|
107
296
|
|
|
108
297
|
<separator></separator>
|
|
109
298
|
|
|
299
|
+
<!-- Category Filter -->
|
|
300
|
+
<action-row>
|
|
301
|
+
<string-select
|
|
302
|
+
placeholder="🏷️ Filter by Category"
|
|
303
|
+
onchange={onCategoryChange}
|
|
304
|
+
>
|
|
305
|
+
<option
|
|
306
|
+
value="all"
|
|
307
|
+
description="Show all products"
|
|
308
|
+
default={selectedCategory === "all"}
|
|
309
|
+
>
|
|
310
|
+
📦 All Categories
|
|
311
|
+
</option>
|
|
312
|
+
{#each getCategories() as cat}
|
|
313
|
+
<option
|
|
314
|
+
value={cat}
|
|
315
|
+
description="Filter by {cat}"
|
|
316
|
+
default={selectedCategory === cat}
|
|
317
|
+
>
|
|
318
|
+
🏷️ {cat}
|
|
319
|
+
</option>
|
|
320
|
+
{/each}
|
|
321
|
+
</string-select>
|
|
322
|
+
</action-row>
|
|
323
|
+
|
|
324
|
+
<!-- Sort Options -->
|
|
325
|
+
<action-row>
|
|
326
|
+
<string-select placeholder="📊 Sort Products" onchange={onSortChange}>
|
|
327
|
+
<option
|
|
328
|
+
value="default"
|
|
329
|
+
description="Original order"
|
|
330
|
+
default={sortBy === "default"}
|
|
331
|
+
>
|
|
332
|
+
📋 Default Order
|
|
333
|
+
</option>
|
|
334
|
+
<option
|
|
335
|
+
value="price-low"
|
|
336
|
+
description="Cheapest first"
|
|
337
|
+
default={sortBy === "price-low"}
|
|
338
|
+
>
|
|
339
|
+
💰 Price: Low to High
|
|
340
|
+
</option>
|
|
341
|
+
<option
|
|
342
|
+
value="price-high"
|
|
343
|
+
description="Most expensive first"
|
|
344
|
+
default={sortBy === "price-high"}
|
|
345
|
+
>
|
|
346
|
+
💎 Price: High to Low
|
|
347
|
+
</option>
|
|
348
|
+
<option
|
|
349
|
+
value="name"
|
|
350
|
+
description="A-Z alphabetically"
|
|
351
|
+
default={sortBy === "name"}
|
|
352
|
+
>
|
|
353
|
+
🔤 Name: A-Z
|
|
354
|
+
</option>
|
|
355
|
+
<option
|
|
356
|
+
value="rating"
|
|
357
|
+
description="Best rated first"
|
|
358
|
+
default={sortBy === "rating"}
|
|
359
|
+
>
|
|
360
|
+
⭐ Rating: Best First
|
|
361
|
+
</option>
|
|
362
|
+
</string-select>
|
|
363
|
+
</action-row>
|
|
364
|
+
|
|
365
|
+
<separator></separator>
|
|
366
|
+
|
|
110
367
|
<text-display
|
|
111
|
-
>📦 Product {currentIndex + 1} of {
|
|
112
|
-
|
|
368
|
+
>📦 Product {currentIndex + 1} of {getFilteredProducts().length} | 🛒 Cart:
|
|
369
|
+
{cart.length}
|
|
370
|
+
items {selectedCategory !== "all"
|
|
371
|
+
? `| 🏷️ ${selectedCategory}`
|
|
372
|
+
: ""}</text-display
|
|
113
373
|
>
|
|
114
374
|
|
|
115
375
|
<action-row>
|
|
116
376
|
<button style="Secondary" onclick={prevProduct}>◀️ Prev</button>
|
|
117
377
|
<button style="Secondary" onclick={nextProduct}>Next ▶️</button>
|
|
118
|
-
<button style="Success" onclick={
|
|
378
|
+
<button style="Success" onclick={openQuantityModal}
|
|
379
|
+
>🛒 Add to Cart</button
|
|
380
|
+
>
|
|
119
381
|
<button style="Primary" onclick={showDetails}>📋 Details</button>
|
|
120
382
|
<button style="Primary" onclick={showCart}
|
|
121
383
|
>🛒 View Cart ({cart.length})</button
|
|
122
384
|
>
|
|
123
385
|
</action-row>
|
|
124
386
|
|
|
387
|
+
<action-row>
|
|
388
|
+
<button style="Secondary" onclick={openReviewModal}
|
|
389
|
+
>⭐ Write Review</button
|
|
390
|
+
>
|
|
391
|
+
<button style="Secondary" onclick={showReviews}
|
|
392
|
+
>📝 View Reviews ({reviews.length})</button
|
|
393
|
+
>
|
|
394
|
+
</action-row>
|
|
395
|
+
|
|
125
396
|
<separator></separator>
|
|
126
397
|
<text-display>⏱️ Session: {formatTime(elapsedTime)}</text-display>
|
|
127
398
|
</components>
|
|
@@ -164,31 +435,75 @@
|
|
|
164
435
|
<section>
|
|
165
436
|
<components>
|
|
166
437
|
<text-display>## 📋 Product Details</text-display>
|
|
167
|
-
<text-display>**{
|
|
438
|
+
<text-display>**{getCurrentProduct()?.name}**</text-display>
|
|
168
439
|
</components>
|
|
169
|
-
<thumbnail media={
|
|
440
|
+
<thumbnail media={getCurrentProduct()?.image}></thumbnail>
|
|
170
441
|
</section>
|
|
171
442
|
|
|
172
443
|
<separator></separator>
|
|
173
444
|
|
|
174
445
|
<text-display
|
|
175
446
|
>### Description
|
|
176
|
-
{
|
|
447
|
+
{getCurrentProduct()?.description}</text-display
|
|
177
448
|
>
|
|
178
449
|
|
|
179
450
|
<separator></separator>
|
|
180
451
|
|
|
181
452
|
<text-display
|
|
182
|
-
>### Specifications • **Category:** {
|
|
183
|
-
• **Rating:** ⭐ {
|
|
184
|
-
|
|
185
|
-
]?.stock} available • **Price:** 💰 **\${products[currentIndex]
|
|
453
|
+
>### Specifications • **Category:** {getCurrentProduct()?.category}
|
|
454
|
+
• **Rating:** ⭐ {getCurrentProduct()?.rating}/5 • **Stock:** {getCurrentProduct()
|
|
455
|
+
?.stock} available • **Price:** 💰 **\${getCurrentProduct()
|
|
186
456
|
?.price}**</text-display
|
|
187
457
|
>
|
|
188
458
|
|
|
189
459
|
<action-row>
|
|
190
460
|
<button style="Secondary" onclick={showBrowse}>◀️ Back</button>
|
|
191
|
-
<button style="Success" onclick={
|
|
461
|
+
<button style="Success" onclick={openQuantityModal}
|
|
462
|
+
>🛒 Add to Cart</button
|
|
463
|
+
>
|
|
464
|
+
<button style="Secondary" onclick={openReviewModal}
|
|
465
|
+
>⭐ Write Review</button
|
|
466
|
+
>
|
|
467
|
+
</action-row>
|
|
468
|
+
|
|
469
|
+
<separator></separator>
|
|
470
|
+
<text-display>⏱️ Session: {formatTime(elapsedTime)}</text-display>
|
|
471
|
+
</components>
|
|
472
|
+
</container>
|
|
473
|
+
{:else if view === "reviews"}
|
|
474
|
+
<container accent-color="EB459E">
|
|
475
|
+
<components>
|
|
476
|
+
<text-display>## 📝 Product Reviews</text-display>
|
|
477
|
+
|
|
478
|
+
{#if reviews.length === 0}
|
|
479
|
+
<text-display
|
|
480
|
+
>*No reviews yet. Be the first to write one!*</text-display
|
|
481
|
+
>
|
|
482
|
+
{:else}
|
|
483
|
+
{#each reviews as r, i}
|
|
484
|
+
<text-display
|
|
485
|
+
>**{r.productName}** - {"⭐".repeat(r.rating)}{"☆".repeat(
|
|
486
|
+
5 - r.rating
|
|
487
|
+
)} ({r.rating}/5)
|
|
488
|
+
{r.review ? `> ${r.review}` : ""}
|
|
489
|
+
*{r.date}*</text-display
|
|
490
|
+
>
|
|
491
|
+
{#if i < reviews.length - 1}
|
|
492
|
+
<separator></separator>
|
|
493
|
+
{/if}
|
|
494
|
+
{/each}
|
|
495
|
+
{/if}
|
|
496
|
+
|
|
497
|
+
<separator></separator>
|
|
498
|
+
<text-display>**Total Reviews:** {reviews.length}</text-display>
|
|
499
|
+
|
|
500
|
+
<action-row>
|
|
501
|
+
<button style="Secondary" onclick={showBrowse}
|
|
502
|
+
>◀️ Back to Browse</button
|
|
503
|
+
>
|
|
504
|
+
<button style="Primary" onclick={openReviewModal}
|
|
505
|
+
>⭐ Write Review</button
|
|
506
|
+
>
|
|
192
507
|
</action-row>
|
|
193
508
|
|
|
194
509
|
<separator></separator>
|
|
@@ -197,3 +512,47 @@
|
|
|
197
512
|
</container>
|
|
198
513
|
{/if}
|
|
199
514
|
</components>
|
|
515
|
+
|
|
516
|
+
<!-- Review Modal: Now using new Label component structure with String Select -->
|
|
517
|
+
<components
|
|
518
|
+
type="modal"
|
|
519
|
+
id="review-modal"
|
|
520
|
+
title="Write a Review for {editingProduct?.name || 'Product'}"
|
|
521
|
+
>
|
|
522
|
+
<field label="Rating" description="How would you rate this product?">
|
|
523
|
+
<string-select id="rating" placeholder="Select a rating">
|
|
524
|
+
<option value="5" emoji="⭐">5 Stars - Excellent!</option>
|
|
525
|
+
<option value="4" emoji="⭐">4 Stars - Great</option>
|
|
526
|
+
<option value="3" emoji="⭐">3 Stars - Good</option>
|
|
527
|
+
<option value="2" emoji="⭐">2 Stars - Fair</option>
|
|
528
|
+
<option value="1" emoji="⭐">1 Star - Poor</option>
|
|
529
|
+
</string-select>
|
|
530
|
+
</field>
|
|
531
|
+
<field
|
|
532
|
+
label="Your Review"
|
|
533
|
+
description="Share your experience with this product"
|
|
534
|
+
>
|
|
535
|
+
<text-input
|
|
536
|
+
id="review"
|
|
537
|
+
placeholder="Write your detailed review here..."
|
|
538
|
+
style="Paragraph"
|
|
539
|
+
></text-input>
|
|
540
|
+
</field>
|
|
541
|
+
</components>
|
|
542
|
+
|
|
543
|
+
<!-- Quantity Modal: Opens when user clicks "Add to Cart" -->
|
|
544
|
+
<components
|
|
545
|
+
type="modal"
|
|
546
|
+
id="quantity-modal"
|
|
547
|
+
title="Add {editingProduct?.name || 'Product'} to Cart"
|
|
548
|
+
>
|
|
549
|
+
<field label="Quantity" description="How many would you like to add?">
|
|
550
|
+
<text-input
|
|
551
|
+
id="quantity"
|
|
552
|
+
placeholder="Enter quantity (1-10)"
|
|
553
|
+
style="Short"
|
|
554
|
+
value="1"
|
|
555
|
+
required
|
|
556
|
+
></text-input>
|
|
557
|
+
</field>
|
|
558
|
+
</components>
|
package/test/test.ts
ADDED