@levo-so/blocks 0.1.102 → 0.1.104

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 (30) hide show
  1. package/package.json +23 -23
  2. package/src/blocks/blogs/blog-post-1.tsx +16 -7
  3. package/src/blocks/cards/cards-21.tsx +6 -6
  4. package/src/blocks/carousel/carousel-1.tsx +2 -2
  5. package/src/blocks/ecommerce/ecommerce-1.schema.ts +477 -0
  6. package/src/blocks/ecommerce/ecommerce-1.tsx +243 -0
  7. package/src/blocks/ecommerce/ecommerce-2.schema.ts +268 -0
  8. package/src/blocks/ecommerce/ecommerce-2.tsx +238 -0
  9. package/src/blocks/ecommerce/ecommerce-3.schema.ts +56 -0
  10. package/src/blocks/ecommerce/ecommerce-3.tsx +72 -0
  11. package/src/blocks/ecommerce/ecommerce-4.schema.ts +461 -0
  12. package/src/blocks/ecommerce/ecommerce-4.tsx +249 -0
  13. package/src/blocks/ecommerce/ecommerce-5.schema.ts +230 -0
  14. package/src/blocks/ecommerce/ecommerce-5.tsx +146 -0
  15. package/src/blocks/index.ts +12 -2
  16. package/src/blocks/logos/logos-2.schema.ts +223 -0
  17. package/src/blocks/logos/logos-2.tsx +55 -0
  18. package/src/blocks/navbar/navbar-3.schema.ts +6 -0
  19. package/src/blocks/navbar/navbar-3.tsx +3 -2
  20. package/src/blocks/navbar/navbar-4.schema.ts +6 -0
  21. package/src/blocks/navbar/navbar-4.tsx +1 -6
  22. package/src/blocks/products/products-listing-3.schema.ts +2 -4
  23. package/src/blocks/products/products-listing-3.tsx +86 -84
  24. package/src/blocks/services/service-2.tsx +0 -3
  25. package/src/blocks/services/service-3.schema.ts +22 -0
  26. package/src/blocks/services/service-3.tsx +12 -4
  27. package/src/schemas/blocks.ts +13 -3
  28. package/src/schemas/categories.ts +6 -0
  29. package/src/blocks/navbar/navbar-2.schema.ts +0 -1255
  30. package/src/blocks/navbar/navbar-2.tsx +0 -381
@@ -0,0 +1,243 @@
1
+ "use client";
2
+
3
+ import type { ILevoBlockBaseProps } from "@levo-so/studio";
4
+ import {
5
+ Box,
6
+ Button,
7
+ Container,
8
+ FormInput,
9
+ Heading,
10
+ Icon,
11
+ Image,
12
+ Section,
13
+ Typography,
14
+ useContentEngine,
15
+ } from "@levo-so/studio";
16
+ import { Search } from "lucide-react";
17
+ import { useCallback, useEffect, useState } from "react";
18
+ import { useDebouncedCallback } from "use-debounce";
19
+
20
+ import type { IEcommerce1Content } from "./ecommerce-1.schema";
21
+
22
+ function getCart(key: string): any[] {
23
+ try {
24
+ return JSON.parse(localStorage.getItem(key) || "[]");
25
+ } catch {
26
+ return [];
27
+ }
28
+ }
29
+
30
+ function saveCart(key: string, cart: any[]) {
31
+ localStorage.setItem(key, JSON.stringify(cart));
32
+ window.dispatchEvent(new Event("levo_cart_updated"));
33
+ }
34
+
35
+ const Ecommerce1: React.FC<ILevoBlockBaseProps<IEcommerce1Content>> = ({ content }) => {
36
+ const { data, query, setQuery, meta } = useContentEngine("events");
37
+
38
+ const [mounted, setMounted] = useState(false);
39
+ const [search, setSearch] = useState("");
40
+ const [cartIds, setCartIds] = useState<Set<string>>(new Set());
41
+
42
+ const rawKey = content?.cart_key || "collectiona";
43
+ const cleanKey = rawKey.replace(/<[^>]*>/g, "").trim();
44
+ const CART_KEY = `levo_collection_cart_${cleanKey}`;
45
+
46
+ useEffect(() => {
47
+ setMounted(true);
48
+ const syncCart = () => {
49
+ const cart = getCart(CART_KEY);
50
+ // Prioritize _id for collection items
51
+ setCartIds(new Set(cart.map((item: any) => item._id || item.id)));
52
+ };
53
+ syncCart();
54
+ window.addEventListener("levo_cart_updated", syncCart);
55
+ window.addEventListener("storage", syncCart);
56
+ return () => {
57
+ window.removeEventListener("levo_cart_updated", syncCart);
58
+ window.removeEventListener("storage", syncCart);
59
+ };
60
+ }, [CART_KEY]);
61
+
62
+ const setDebouncedSearchKey = useDebouncedCallback((v: string) => {
63
+ if (v) {
64
+ setQuery({ related: v, page: 1 });
65
+ return;
66
+ }
67
+ setQuery({ related: undefined, page: 1 });
68
+ }, 1000);
69
+
70
+ const handleNext = () => setQuery({ page: (query?.page || 1) + 1 });
71
+ const handlePrevious = () => setQuery({ page: Math.max(1, (query?.page || 1) - 1) });
72
+
73
+ const handleCartToggle = useCallback(
74
+ (item: any, index: number) => {
75
+ const cart = getCart(CART_KEY);
76
+ // Prioritize collection _id, then manual id, then index
77
+ const itemId = item?._id || item?.id || `item-${index}`;
78
+ const itemName =
79
+ item?.name || item?.title || item?.["caption-text"] || `Product ${index + 1}`;
80
+
81
+ console.log(
82
+ `[Cart] Toggling item. Collection _id: ${item?._id}, Manual id: ${item?.id}, Used itemId: ${itemId}`,
83
+ );
84
+
85
+ // Robust price parsing: handle both numbers and strings like "₹4,000"
86
+ const rawPrice = item?.price || item?.["publishedAt-header"] || "0";
87
+ const itemPrice =
88
+ typeof rawPrice === "number"
89
+ ? rawPrice
90
+ : parseFloat(String(rawPrice).replace(/[^0-9.]/g, "")) || 0;
91
+
92
+ const isInCart = cart.some((c: any) => (c._id || c.id) === itemId);
93
+ if (isInCart) {
94
+ saveCart(
95
+ CART_KEY,
96
+ cart.filter((c: any) => (c._id || c.id) !== itemId),
97
+ );
98
+ } else {
99
+ saveCart(CART_KEY, [
100
+ ...cart,
101
+ {
102
+ _id: itemId,
103
+ id: itemId,
104
+ name: itemName,
105
+ price: itemPrice,
106
+ image: item.image,
107
+ },
108
+ ]);
109
+ }
110
+ setCartIds((prev) => {
111
+ const next = new Set(prev);
112
+ if (isInCart) next.delete(itemId);
113
+ else next.add(itemId);
114
+ return next;
115
+ });
116
+ },
117
+ [CART_KEY],
118
+ );
119
+
120
+ console.log({ data });
121
+
122
+ return (
123
+ <Section elementKey="layout">
124
+ <Container elementKey="container">
125
+ <Box elementKey="header-container">
126
+ <Heading elementKey="title" />
127
+ <Button elementKey="cta-button" />
128
+ </Box>
129
+ <Box elementKey="search-container">
130
+ <Box elementKey="search-input-box">
131
+ <FormInput
132
+ name="search"
133
+ placeholder="Search..."
134
+ value={search}
135
+ onChange={(e) => {
136
+ setSearch(e?.target?.value || "");
137
+ setDebouncedSearchKey(e?.target?.value || "");
138
+ }}
139
+ leftIcon={<Search size={22} />}
140
+ inputProps={{
141
+ style: {
142
+ padding: "1rem",
143
+ paddingLeft: "3rem",
144
+ height: "unset",
145
+ fontSize: "1.125rem",
146
+ color: "var(--color-text-1)",
147
+ borderColor: "var(--color-border)",
148
+ borderRadius: "99px",
149
+ },
150
+ }}
151
+ leftIconBoxProps={{
152
+ style: {
153
+ width: "unset",
154
+ paddingLeft: "1.25rem",
155
+ },
156
+ }}
157
+ />
158
+ </Box>
159
+ </Box>
160
+ <Box elementKey="events_levoGroup" data-levo_group>
161
+ {(data || [])?.map((item: any, index: number) => {
162
+ const itemId = item?._id || item?.id || `item-${index}`;
163
+ const isInCart = mounted && itemId ? cartIds.has(itemId) : false;
164
+ return (
165
+ <Box
166
+ key={`ecommerce-listing-element-${index}`}
167
+ data-levo_group_item
168
+ elementKey={`events.${index}.eventWrapper`}
169
+ >
170
+ <Image
171
+ elementKey={`events.${index}.image`}
172
+ alt={item?.name || item?.title || "product image"}
173
+ />
174
+ <Box elementKey={`events.${index}.text-wrapper`}>
175
+ <Box elementKey={`events.${index}.text-container`}>
176
+ <Box elementKey={`events.${index}.date-wrapper`}>
177
+ <Icon elementKey={`events.${index}.date-icon`} />
178
+ <Typography elementKey={`events.${index}.publishedAt-header`}>
179
+ {item?.["publishedAt-header"] ||
180
+ (item?.price ? `₹${Number(item.price).toLocaleString("en-IN")}` : "")}
181
+ </Typography>
182
+ </Box>
183
+
184
+ <Box elementKey={`events.${index}.author-wrapper`}>
185
+ <Icon elementKey={`events.${index}.author-image`} />
186
+ <Typography elementKey={`events.${index}.author-name`}>
187
+ {item?.age_group || item?.["author-name"] || ""}
188
+ </Typography>
189
+ </Box>
190
+ </Box>
191
+
192
+ <Box elementKey={`events.${index}.content-container`}>
193
+ <Typography elementKey={`events.${index}.title`}>
194
+ {item?.name || item?.title || item?.["caption-text"] || ""}
195
+ </Typography>
196
+ <Typography elementKey={`events.${index}.description`}>
197
+ {item?.description || ""}
198
+ </Typography>
199
+ <Typography elementKey={`events.${index}.publishedAt`} />
200
+ <Box elementKey={`events.${index}.cta_wrapper`}>
201
+ <Button
202
+ elementKey={`events.${index}.cta`}
203
+ onClick={() => handleCartToggle(item, index)}
204
+ config={
205
+ isInCart
206
+ ? { selectedVariants: { Button_Variants: "Destructive" } }
207
+ : undefined
208
+ }
209
+ >
210
+ {isInCart ? "Remove" : undefined}
211
+ </Button>
212
+ <Button elementKey={`events.${index}.view_details`} />
213
+ </Box>
214
+ </Box>
215
+ </Box>
216
+ </Box>
217
+ );
218
+ })}
219
+ </Box>
220
+ {(meta?.pages || 0) > 1 && (
221
+ <Box elementKey="paginationWrapper">
222
+ <Button
223
+ elementKey="paginationLeftButton"
224
+ disabled={query?.page === 1}
225
+ onClick={handlePrevious}
226
+ />
227
+
228
+ <Typography elementKey="paginationText">
229
+ {`${query?.page || 1}/${meta?.pages || 0}`}
230
+ </Typography>
231
+ <Button
232
+ elementKey="paginationRightButton"
233
+ onClick={handleNext}
234
+ disabled={query?.page === meta?.pages}
235
+ />
236
+ </Box>
237
+ )}
238
+ </Container>
239
+ </Section>
240
+ );
241
+ };
242
+
243
+ export default Ecommerce1;
@@ -0,0 +1,268 @@
1
+ import type { IBlock } from "@levo-so/studio";
2
+
3
+ const DEFAULT_CONTENT = {
4
+ layout: null,
5
+ container: null,
6
+ title: "Your Cart",
7
+ "empty-state": null,
8
+ "empty-text": "Your cart is empty. Browse our tests and add items to get started.",
9
+ "cart-wrapper": null,
10
+ "cart-items-list": null,
11
+ "cart-item": null,
12
+ "cart-item-inner": null,
13
+ "cart-item-details": null,
14
+ "cart-item-name": "",
15
+ "cart-item-price": "",
16
+ "cart-total-wrapper": null,
17
+ "cart-total-label": "Total",
18
+ "cart-total-amount": "",
19
+ "checkout-cta": "Proceed to Checkout",
20
+ cart_key: "levo_cart_collectiona",
21
+ };
22
+
23
+ export type IEcommerce2Content = typeof DEFAULT_CONTENT;
24
+
25
+ export const Ecommerce2: IBlock = {
26
+ category_id: "ecommerce",
27
+ title: "Ecommerce 2",
28
+ key: "ecommerce-2",
29
+ version: "v1",
30
+ prompt_description:
31
+ "Cart summary block. Reads items from localStorage, shows each item with image, name and price. Displays total price and a checkout CTA that redirects to a configurable URL with cart item IDs as query params.",
32
+ content_schema: [
33
+ { key: "layout", label: "Layout", field_interface: "LayoutWidget" },
34
+ { key: "container", label: "Container", field_interface: "ContainerWidget" },
35
+ {
36
+ key: "title",
37
+ label: "Section Title",
38
+ field_interface: "HeadingWidget",
39
+ hint: {
40
+ prompt_description: "Cart page heading, e.g. 'Your Cart'.",
41
+ min_characters: 3,
42
+ max_characters: 40,
43
+ },
44
+ },
45
+ { key: "empty-state", label: "Empty State Wrapper", field_interface: "BoxWidget" },
46
+ {
47
+ key: "empty-text",
48
+ label: "Empty Cart Text",
49
+ field_interface: "TypographyWidget",
50
+ hint: {
51
+ prompt_description: "Message shown when cart is empty.",
52
+ min_characters: 10,
53
+ max_characters: 120,
54
+ },
55
+ },
56
+ {
57
+ key: "empty-cta",
58
+ label: "Empty State CTA",
59
+ field_interface: "ButtonWidget",
60
+ hint: {
61
+ prompt_description: "CTA shown when cart is empty.",
62
+ min_characters: 5,
63
+ max_characters: 30,
64
+ },
65
+ },
66
+
67
+ { key: "cart-wrapper", label: "Cart Wrapper", field_interface: "BoxWidget" },
68
+ { key: "cart-items-list", label: "Cart Items List", field_interface: "BoxWidget" },
69
+ { key: "cart-item", label: "Cart Item Row", field_interface: "BoxWidget" },
70
+ { key: "cart-item-inner", label: "Cart Item Inner", field_interface: "BoxWidget" },
71
+ { key: "cart-item-details", label: "Cart Item Details", field_interface: "BoxWidget" },
72
+ {
73
+ key: "cart-item-name",
74
+ label: "Cart Item Name",
75
+ field_interface: "TypographyWidget",
76
+ hint: {
77
+ prompt_description: "Product name inside the cart row.",
78
+ min_characters: 2,
79
+ max_characters: 80,
80
+ },
81
+ },
82
+ {
83
+ key: "cart-item-price",
84
+ label: "Cart Item Price",
85
+ field_interface: "TypographyWidget",
86
+ hint: {
87
+ prompt_description: "Product price inside the cart row.",
88
+ min_characters: 2,
89
+ max_characters: 20,
90
+ },
91
+ },
92
+ { key: "cart-item-image", label: "Cart Item Image", field_interface: "ImageWidget" },
93
+ { key: "cart-item-remove", label: "Cart Item Remove Button", field_interface: "ButtonWidget" },
94
+ { key: "cart-total-wrapper", label: "Total Wrapper", field_interface: "BoxWidget" },
95
+ {
96
+ key: "cart-total-label",
97
+ label: "Total Label",
98
+ field_interface: "TypographyWidget",
99
+ hint: {
100
+ prompt_description: "Label before the total amount, e.g. 'Total'.",
101
+ min_characters: 3,
102
+ max_characters: 20,
103
+ },
104
+ },
105
+ {
106
+ key: "cart-total-amount",
107
+ label: "Total Amount",
108
+ field_interface: "TypographyWidget",
109
+ hint: {
110
+ prompt_description: "Computed total price (auto-filled from cart).",
111
+ min_characters: 0,
112
+ max_characters: 20,
113
+ },
114
+ },
115
+ {
116
+ key: "checkout-cta",
117
+ label: "Checkout CTA",
118
+ field_interface: "ButtonWidget",
119
+ hint: {
120
+ prompt_description: "Button that redirects to checkout page.",
121
+ min_characters: 5,
122
+ max_characters: 30,
123
+ },
124
+ },
125
+ {
126
+ key: "cart_key",
127
+ label: "Cart Storage Key",
128
+ field_interface: "TypographyWidget",
129
+ hint: {
130
+ prompt_description: "Unique key for local storage to separate different carts.",
131
+ min_characters: 3,
132
+ max_characters: 50,
133
+ },
134
+ },
135
+ ],
136
+ layouts: [
137
+ {
138
+ key: "default",
139
+ title: "Default",
140
+ styles: {
141
+ layout: {
142
+ "padding-left": "5xl",
143
+ "padding-right": "5xl",
144
+ "padding-top": "5xl",
145
+ "padding-bottom": "5xl",
146
+ tablet: {
147
+ "padding-left": "3xl",
148
+ "padding-right": "3xl",
149
+ "padding-top": "4xl",
150
+ "padding-bottom": "4xl",
151
+ },
152
+ mobile: {
153
+ "padding-left": "xl",
154
+ "padding-right": "xl",
155
+ "padding-top": "3xl",
156
+ "padding-bottom": "3xl",
157
+ },
158
+ },
159
+ container: {
160
+ display: "flex",
161
+ "flex-direction": "column",
162
+ "row-gap": "2xl",
163
+ "max-width": "640px",
164
+ "margin-left": "auto",
165
+ "margin-right": "auto",
166
+ width: "100%",
167
+ },
168
+ title: { color: "var(--color-text-1)", margin: "0" },
169
+ "empty-state": {
170
+ display: "flex",
171
+ "justify-content": "center",
172
+ "align-items": "center",
173
+ "padding-top": "4xl",
174
+ "padding-bottom": "4xl",
175
+ },
176
+ "empty-text": {
177
+ color: "var(--color-text-2)",
178
+ "text-align": "center",
179
+ "font-size": "lg",
180
+ },
181
+ "cart-wrapper": {
182
+ display: "flex",
183
+ "flex-direction": "column",
184
+ "row-gap": "xl",
185
+ },
186
+ "cart-items-list": {
187
+ display: "flex",
188
+ "flex-direction": "column",
189
+ "row-gap": "md",
190
+ },
191
+ "cart-item": {
192
+ "border-radius": "lg",
193
+ border: "1px solid var(--color-border)",
194
+ "padding-top": "md",
195
+ "padding-bottom": "md",
196
+ "padding-left": "lg",
197
+ "padding-right": "lg",
198
+ "background-color": "var(--color-card, var(--color-background))",
199
+ },
200
+ "cart-item-inner": {
201
+ display: "flex",
202
+ "align-items": "center",
203
+ gap: "lg",
204
+ },
205
+ "cart-item-details": {
206
+ flex: "1",
207
+ display: "flex",
208
+ "flex-direction": "column",
209
+ "row-gap": "xs",
210
+ },
211
+ "cart-item-name": {
212
+ "font-weight": 600,
213
+ color: "var(--color-text-1)",
214
+ "font-size": "base",
215
+ },
216
+ "cart-item-price": {
217
+ color: "var(--color-brand)",
218
+ "font-weight": 700,
219
+ "font-size": "base",
220
+ },
221
+ "cart-item-image": {
222
+ width: "72px",
223
+ height: "72px",
224
+ "object-fit": "cover",
225
+ "border-radius": "md",
226
+ "flex-shrink": 0,
227
+ },
228
+ "cart-item-remove": {
229
+ background: "none",
230
+ border: "none",
231
+ color: "var(--color-error, #ef4444)",
232
+ cursor: "pointer",
233
+ padding: "xs",
234
+ display: "flex",
235
+ "align-items": "center",
236
+ "justify-content": "center",
237
+ "border-radius": "full",
238
+ transition: "background-color 0.2s",
239
+ },
240
+ "cart-total-wrapper": {
241
+ display: "flex",
242
+ "justify-content": "space-between",
243
+ "align-items": "center",
244
+ "padding-top": "lg",
245
+ "padding-bottom": "lg",
246
+ "border-top": "2px solid var(--color-border)",
247
+ },
248
+ "cart-total-label": {
249
+ "font-size": "xl",
250
+ "font-weight": 600,
251
+ color: "var(--color-text-1)",
252
+ },
253
+ "cart-total-amount": {
254
+ "font-size": "xl",
255
+ "font-weight": 700,
256
+ color: "var(--color-brand)",
257
+ },
258
+ },
259
+ content: DEFAULT_CONTENT,
260
+ config: {
261
+ title: {
262
+ selectedVariants: { Heading_Level: "H2" },
263
+ heading: { level: 2 },
264
+ },
265
+ },
266
+ },
267
+ ],
268
+ };