@levo-so/blocks 0.1.101 → 0.1.103

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 (177) hide show
  1. package/package.json +24 -23
  2. package/src/blocks/about-us/about-us-1.schema.ts +302 -295
  3. package/src/blocks/about-us/about-us-2.schema.ts +225 -220
  4. package/src/blocks/blogs/blog-author-1.schema.ts +719 -0
  5. package/src/blocks/blogs/blog-author-1.tsx +121 -0
  6. package/src/blocks/blogs/blog-listing-1.tsx +111 -33
  7. package/src/blocks/blogs/blog-listing-2.schema.ts +15 -33
  8. package/src/blocks/blogs/blog-listing-2.tsx +118 -33
  9. package/src/blocks/blogs/blog-listing-3.schema.ts +10 -22
  10. package/src/blocks/blogs/blog-listing-3.tsx +118 -33
  11. package/src/blocks/blogs/blog-post-1.schema.ts +766 -16
  12. package/src/blocks/blogs/blog-post-1.tsx +272 -39
  13. package/src/blocks/calendar/calendar-1.schema.ts +641 -192
  14. package/src/blocks/calendar/calendar-1.tsx +1 -0
  15. package/src/blocks/cards/cards-10.schema.ts +557 -544
  16. package/src/blocks/cards/cards-11.schema.ts +525 -419
  17. package/src/blocks/cards/cards-12.schema.ts +538 -147
  18. package/src/blocks/cards/cards-13.schema.ts +482 -312
  19. package/src/blocks/cards/cards-14.schema.ts +423 -196
  20. package/src/blocks/cards/cards-15.schema.ts +309 -143
  21. package/src/blocks/cards/cards-16.schema.ts +1277 -175
  22. package/src/blocks/cards/cards-17.schema.ts +2908 -1303
  23. package/src/blocks/cards/cards-18.schema.ts +1404 -217
  24. package/src/blocks/cards/cards-19.schema.ts +1105 -1084
  25. package/src/blocks/cards/cards-19.tsx +4 -5
  26. package/src/blocks/cards/cards-2.schema.ts +513 -42
  27. package/src/blocks/cards/cards-20.schema.ts +790 -212
  28. package/src/blocks/cards/cards-21.schema.ts +1215 -229
  29. package/src/blocks/cards/cards-21.tsx +51 -6
  30. package/src/blocks/cards/cards-22.schema.ts +1252 -2809
  31. package/src/blocks/cards/cards-22.tsx +6 -7
  32. package/src/blocks/cards/cards-23.schema.ts +571 -109
  33. package/src/blocks/cards/cards-3.schema.ts +355 -116
  34. package/src/blocks/cards/cards-4.schema.ts +805 -321
  35. package/src/blocks/cards/cards-5.schema.ts +216 -64
  36. package/src/blocks/cards/cards-6.schema.ts +568 -1330
  37. package/src/blocks/cards/cards-7.schema.ts +1181 -1378
  38. package/src/blocks/cards/cards-8.schema.ts +585 -221
  39. package/src/blocks/cards/cards-9.schema.ts +763 -382
  40. package/src/blocks/carousel/carousel-1.schema.ts +257 -502
  41. package/src/blocks/carousel/carousel-1.tsx +2 -2
  42. package/src/blocks/carousel/carousel-2.schema.ts +771 -761
  43. package/src/blocks/community/community-1.schema.ts +8 -23
  44. package/src/blocks/community/community-1.tsx +3 -3
  45. package/src/blocks/community/community-post-1.tsx +4 -4
  46. package/src/blocks/contact-us/contact-us-1.schema.ts +360 -14
  47. package/src/blocks/contact-us/contact-us-2.schema.ts +441 -325
  48. package/src/blocks/content/content-1.schema.ts +800 -381
  49. package/src/blocks/content/content-10.schema.ts +763 -20
  50. package/src/blocks/content/content-2.schema.ts +426 -52
  51. package/src/blocks/content/content-3.schema.ts +631 -432
  52. package/src/blocks/content/content-4.schema.ts +378 -123
  53. package/src/blocks/content/content-5.schema.ts +1152 -682
  54. package/src/blocks/content/content-6.schema.ts +679 -707
  55. package/src/blocks/content/content-7.schema.ts +694 -291
  56. package/src/blocks/content/content-8.schema.ts +247 -7
  57. package/src/blocks/content/content-9.schema.ts +855 -116
  58. package/src/blocks/cta/cta-1.schema.ts +294 -62
  59. package/src/blocks/cta/cta-2.schema.ts +302 -66
  60. package/src/blocks/cta/cta-2.tsx +1 -2
  61. package/src/blocks/cta/cta-3.schema.ts +312 -62
  62. package/src/blocks/ecommerce/ecommerce-1.schema.ts +477 -0
  63. package/src/blocks/ecommerce/ecommerce-1.tsx +243 -0
  64. package/src/blocks/ecommerce/ecommerce-2.schema.ts +268 -0
  65. package/src/blocks/ecommerce/ecommerce-2.tsx +238 -0
  66. package/src/blocks/ecommerce/ecommerce-3.schema.ts +56 -0
  67. package/src/blocks/ecommerce/ecommerce-3.tsx +72 -0
  68. package/src/blocks/ecommerce/ecommerce-4.schema.ts +461 -0
  69. package/src/blocks/ecommerce/ecommerce-4.tsx +249 -0
  70. package/src/blocks/ecommerce/ecommerce-5.schema.ts +230 -0
  71. package/src/blocks/ecommerce/ecommerce-5.tsx +146 -0
  72. package/src/blocks/embed/embed-1.schema.ts +181 -0
  73. package/src/blocks/event/event-listing-2.schema.ts +5 -11
  74. package/src/blocks/event/event-listing-3.schema.ts +5 -11
  75. package/src/blocks/event/event-listing-4.schema.ts +5 -11
  76. package/src/blocks/event/event-listing.schema.ts +5 -11
  77. package/src/blocks/faq/faq-1.schema.ts +439 -260
  78. package/src/blocks/faq/faq-2.schema.ts +444 -266
  79. package/src/blocks/faq/faq-3.schema.ts +1420 -224
  80. package/src/blocks/features/features-1/features-1.schema.ts +21 -21
  81. package/src/blocks/features/features-1/variants/default.ts +120 -138
  82. package/src/blocks/features/features-1/variants/index.ts +2 -2
  83. package/src/blocks/features/features-1/variants/minimal-list.ts +139 -0
  84. package/src/blocks/features/features-1/variants/stylized-cards-v1.ts +69 -385
  85. package/src/blocks/features/features-1/variants/stylized-cards-v2.ts +74 -408
  86. package/src/blocks/features/features-2.schema.ts +368 -286
  87. package/src/blocks/features/features-3.schema.ts +557 -280
  88. package/src/blocks/features/features-4.schema.ts +940 -432
  89. package/src/blocks/features/features-5.schema.ts +566 -407
  90. package/src/blocks/features/features-6.schema.ts +557 -523
  91. package/src/blocks/filter-listing/filter-listing-1.schema.ts +922 -270
  92. package/src/blocks/floating-buttons/floating-button-1.schema.ts +123 -0
  93. package/src/blocks/footer/footer-1.schema.ts +383 -401
  94. package/src/blocks/footer/footer-2.schema.ts +337 -878
  95. package/src/blocks/footer/footer-3.schema.ts +445 -381
  96. package/src/blocks/footer/footer-4.schema.ts +610 -439
  97. package/src/blocks/footer/footer-5.schema.ts +367 -321
  98. package/src/blocks/footer/footer-6.schema.ts +610 -357
  99. package/src/blocks/form/form-1.schema.ts +237 -0
  100. package/src/blocks/hero/hero-1.schema.ts +251 -221
  101. package/src/blocks/hero/hero-10.schema.ts +23 -14
  102. package/src/blocks/hero/hero-11.schema.ts +728 -601
  103. package/src/blocks/hero/hero-12.schema.ts +524 -334
  104. package/src/blocks/hero/hero-13.schema.ts +83 -95
  105. package/src/blocks/hero/hero-2.schema.ts +277 -68
  106. package/src/blocks/hero/hero-3.schema.ts +371 -235
  107. package/src/blocks/hero/hero-3.tsx +1 -1
  108. package/src/blocks/hero/hero-4.schema.ts +605 -338
  109. package/src/blocks/hero/hero-5.schema.ts +545 -61
  110. package/src/blocks/hero/hero-6.schema.ts +446 -257
  111. package/src/blocks/hero/hero-6.tsx +1 -1
  112. package/src/blocks/hero/hero-7.schema.ts +1299 -131
  113. package/src/blocks/hero/hero-7.tsx +43 -23
  114. package/src/blocks/hero/hero-8.schema.ts +736 -236
  115. package/src/blocks/hero/hero-9.schema.ts +908 -58
  116. package/src/blocks/index.ts +14 -2
  117. package/src/blocks/jobs/jobs-1.schema.ts +741 -219
  118. package/src/blocks/logos/logos-1.schema.ts +317 -321
  119. package/src/blocks/logos/logos-1.tsx +32 -19
  120. package/src/blocks/logos/logos-2.schema.ts +223 -0
  121. package/src/blocks/logos/logos-2.tsx +55 -0
  122. package/src/blocks/navbar/navbar-1.schema.ts +319 -186
  123. package/src/blocks/navbar/navbar-3.schema.ts +618 -570
  124. package/src/blocks/navbar/navbar-3.tsx +3 -2
  125. package/src/blocks/navbar/navbar-4.schema.ts +731 -1234
  126. package/src/blocks/navbar/navbar-4.tsx +1 -6
  127. package/src/blocks/polling/polling-1.schema.ts +161 -0
  128. package/src/blocks/pricing/pricing-1.schema.ts +549 -626
  129. package/src/blocks/products/products-listing-1.schema.ts +363 -249
  130. package/src/blocks/products/products-listing-2.schema.ts +515 -257
  131. package/src/blocks/products/products-listing-3.schema.ts +790 -839
  132. package/src/blocks/products/products-listing-3.tsx +86 -84
  133. package/src/blocks/profile/profile-1.schema.ts +168 -187
  134. package/src/blocks/profile/profile-2.schema.ts +150 -122
  135. package/src/blocks/profile/profile-3.schema.ts +307 -256
  136. package/src/blocks/profile/profile-4.schema.ts +372 -504
  137. package/src/blocks/profile/profile-5.schema.ts +186 -79
  138. package/src/blocks/services/service-1.schema.ts +700 -631
  139. package/src/blocks/services/service-2.schema.ts +559 -82
  140. package/src/blocks/services/service-2.tsx +5 -6
  141. package/src/blocks/services/service-3.schema.ts +592 -207
  142. package/src/blocks/services/service-3.tsx +10 -2
  143. package/src/blocks/stats/stats-1.schema.ts +347 -257
  144. package/src/blocks/stats/stats-2.schema.ts +14 -2
  145. package/src/blocks/stats/variants/default.ts +14 -37
  146. package/src/blocks/stats/variants/extra.ts +232 -0
  147. package/src/blocks/stats/variants/hero.ts +1 -30
  148. package/src/blocks/stats/variants/index.ts +13 -1
  149. package/src/blocks/tabs/tab-1.schema.ts +1361 -214
  150. package/src/blocks/tabs/tab-2.schema.ts +984 -390
  151. package/src/blocks/tabs/tab-3.schema.ts +961 -95
  152. package/src/blocks/team/team-1.schema.ts +681 -564
  153. package/src/blocks/team/team-2.schema.ts +589 -935
  154. package/src/blocks/team/team-3.schema.ts +375 -284
  155. package/src/blocks/team/team-4.schema.ts +569 -355
  156. package/src/blocks/team/team-5.schema.ts +307 -111
  157. package/src/blocks/testimonial/testimonial-1.schema.ts +605 -706
  158. package/src/blocks/testimonial/testimonial-2.schema.ts +825 -1099
  159. package/src/blocks/testimonial/testimonial-3.schema.ts +580 -235
  160. package/src/blocks/testimonial/testimonial-4.schema.ts +1326 -1779
  161. package/src/blocks/testimonial/testimonial-5/variants/default.ts +179 -328
  162. package/src/blocks/testimonial/testimonial-5/variants/index.ts +9 -1
  163. package/src/blocks/testimonial/testimonial-5/variants/testimonialCentered.ts +194 -0
  164. package/src/blocks/testimonial/testimonial-5/variants/testimonialCompact.ts +188 -0
  165. package/src/blocks/testimonial/testimonial-5/variants/testimonialWithLogo.ts +275 -396
  166. package/src/blocks/testimonial/testimonial-5/variants/testimonialWithRatings.ts +254 -602
  167. package/src/blocks/testimonial/testimonial-5.schema.ts +6 -2
  168. package/src/blocks/testimonial/testimonial-6.schema.ts +962 -1234
  169. package/src/blocks/testimonial/testimonial-7.schema.ts +515 -233
  170. package/src/blocks/timeline/timeline-1.schema.ts +382 -140
  171. package/src/blocks/users/edit-profile.schema.ts +2 -8
  172. package/src/blocks/users/edit-profile.tsx +2 -3
  173. package/src/schemas/blocks.ts +14 -2
  174. package/src/schemas/categories.ts +6 -0
  175. package/src/blocks/features/features-1/variants/stylized-cards-v3.ts +0 -268
  176. package/src/blocks/navbar/navbar-2.schema.ts +0 -1134
  177. package/src/blocks/navbar/navbar-2.tsx +0 -381
@@ -1,23 +1,30 @@
1
- import type { ILevoBlockBaseProps } from "@levo-so/studio";
1
+ import type { IModuleStudioConfig } from "@levo-so/core";
2
+ import type { ILevoBlockBaseProps, IStudioSource } from "@levo-so/studio";
2
3
  import {
3
4
  Avatar,
4
5
  AvatarFallbackV2,
5
6
  AvatarImageV2,
6
7
  BlogContent,
7
8
  Box,
9
+ Button,
8
10
  Container,
11
+ convertHtmlToJSON,
9
12
  Heading,
10
13
  Icon,
14
+ Image,
11
15
  Media,
12
16
  Section,
13
17
  Typography,
18
+ useBlockContext,
14
19
  usePageContext,
20
+ usePageMeta,
15
21
  } from "@levo-so/studio";
16
22
  import dayjs from "dayjs";
23
+ import { ChevronDown } from "lucide-react";
17
24
  import type React from "react";
18
25
  import { useEffect, useMemo, useState } from "react";
19
26
 
20
- import type { IBlogPost1Content } from "./blog-post-1.schema";
27
+ import { type IBlogPost1Content, MEDIA } from "./blog-post-1.schema";
21
28
  import { ClientOnly } from "./ClientOnly";
22
29
 
23
30
  interface ITocItem {
@@ -42,7 +49,12 @@ interface IContent {
42
49
  }
43
50
 
44
51
  const extractHeadingsFromContent = (content: IContent): ITocItem[] => {
45
- if (!content?.json?.content) return [];
52
+ if (!content?.json?.content) {
53
+ // Fallback: convert HTML to JSON when content.json is absent
54
+ if (!content?.html) return [];
55
+ const parsed = convertHtmlToJSON(content.html);
56
+ return extractHeadingsFromContent({ json: parsed } as IContent);
57
+ }
46
58
 
47
59
  const headings: ITocItem[] = [];
48
60
 
@@ -103,7 +115,7 @@ const TableOfContents: React.FC<{ headings: ITocItem[] }> = ({ headings }) => {
103
115
  const element = document.getElementById(id);
104
116
  if (element) {
105
117
  const offset = 100;
106
- const elementPosition = element.getBoundingClientRect().top + window.pageYOffset;
118
+ const elementPosition = element.getBoundingClientRect().top + window.scrollY;
107
119
  window.scrollTo({
108
120
  top: elementPosition - offset,
109
121
  behavior: "smooth",
@@ -117,7 +129,7 @@ const TableOfContents: React.FC<{ headings: ITocItem[] }> = ({ headings }) => {
117
129
  <Box elementKey="toc-container">
118
130
  <Heading elementKey="toc-title" />
119
131
  <Box elementKey="toc-list">
120
- {headings.map((heading) => (
132
+ {headings?.map((heading) => (
121
133
  <Box elementKey="toc-item-container" key={heading.id}>
122
134
  <Icon elementKey="toc-item-icon" />
123
135
  <Typography
@@ -138,21 +150,92 @@ const TableOfContents: React.FC<{ headings: ITocItem[] }> = ({ headings }) => {
138
150
  );
139
151
  };
140
152
 
153
+ /** Canonical display order for social platform icons. */
154
+ const SOCIAL_ORDER = [
155
+ "instagram",
156
+ "twitter",
157
+ "linkedin",
158
+ "facebook",
159
+ "youtube",
160
+ "github",
161
+ "tiktok",
162
+ "website",
163
+ ];
164
+
165
+ const sortSocials = (entries: [string, unknown][]) =>
166
+ [...entries].sort(([a], [b]) => {
167
+ const aIdx = SOCIAL_ORDER.indexOf(a);
168
+ const bIdx = SOCIAL_ORDER.indexOf(b);
169
+ return (aIdx === -1 ? SOCIAL_ORDER.length : aIdx) - (bIdx === -1 ? SOCIAL_ORDER.length : bIdx);
170
+ });
171
+
172
+ const BIO_CHAR_LIMIT = 180;
173
+
174
+ const AuthorBioRenderer: React.FC<{
175
+ author: any;
176
+ expandedBios: Record<number, boolean>;
177
+ setExpandedBios: React.Dispatch<React.SetStateAction<Record<number, boolean>>>;
178
+ index: number;
179
+ }> = ({ author, expandedBios, setExpandedBios, index }) => {
180
+ const bio = author?.bio ?? "";
181
+ const isExpanded = expandedBios[index] ?? false;
182
+ const isTruncated = bio.length > BIO_CHAR_LIMIT;
183
+ const displayBio = !isExpanded && isTruncated ? `${bio.slice(0, BIO_CHAR_LIMIT)}…` : bio;
184
+ return (
185
+ <Box elementKey="author-bio-wrapper">
186
+ <Typography elementKey="author-bio">{displayBio}</Typography>
187
+ {isTruncated && (
188
+ <Button
189
+ elementKey="author-bio-read-more"
190
+ type="button"
191
+ onClick={() => setExpandedBios((prev) => ({ ...prev, [index]: !isExpanded }))}
192
+ >
193
+ {isExpanded ? "Read Less" : "Read More"}
194
+ <ChevronDown
195
+ style={{
196
+ float: "right",
197
+ rotate: isExpanded ? "180deg" : "0deg",
198
+ transition: "rotate 0.2s ease-in-out",
199
+ marginLeft: "4px",
200
+ marginTop: "2px",
201
+ }}
202
+ size={16}
203
+ />
204
+ </Button>
205
+ )}
206
+ </Box>
207
+ );
208
+ };
209
+
141
210
  const BlogPost1: React.FC<ILevoBlockBaseProps<IBlogPost1Content>> = () => {
211
+ const { isStudio } = useBlockContext();
142
212
  const data = usePageContext("blog");
143
- const headings = useMemo(() => extractHeadingsFromContent(data.content), [data.content]);
213
+ /**
214
+ * Tracks which author bios are expanded, keyed by author index.
215
+ * Absent keys default to collapsed.
216
+ */
217
+ const [expandedBios, setExpandedBios] = useState<Record<number, boolean>>({});
218
+ const meta = usePageMeta("blog") as {
219
+ studio_config?: (IModuleStudioConfig & {
220
+ module: IStudioSource["module"];
221
+ })[];
222
+ };
223
+ const headings = useMemo(() => extractHeadingsFromContent(data.content), [data?.content]);
144
224
 
145
225
  useEffect(() => {
146
226
  if (!data.content?.html || headings.length === 0) return;
147
227
 
148
- const contentElement = document.querySelector('[data-element-key="content"]');
228
+ const contentElement = document.querySelector('[data-levo_element_key="content"]');
149
229
  if (!contentElement) return;
150
230
 
151
- const headingElements = contentElement.querySelectorAll("h1, h2, h3, h4, h5, h6");
231
+ const headingElements = Array.from(
232
+ contentElement.querySelectorAll("h1, h2, h3, h4, h5, h6"),
233
+ ).filter((el) => el.textContent?.trim().length > 0);
152
234
 
153
- headingElements.forEach((el) => {
235
+ headingElements.forEach((el, index) => {
154
236
  const dataId = el.getAttribute("data-id");
155
- const match = headings.find((h) => h.id === dataId);
237
+ const match = dataId ? headings.find((h) => h.id === dataId) : headings[index];
238
+
156
239
  if (match) {
157
240
  el.id = match.id;
158
241
  (el as HTMLElement).style.scrollMarginTop = "100px";
@@ -160,6 +243,10 @@ const BlogPost1: React.FC<ILevoBlockBaseProps<IBlogPost1Content>> = () => {
160
243
  });
161
244
  }, [data.content?.html, headings]);
162
245
 
246
+ const blogAuthorStudioConfig = meta?.studio_config?.find(
247
+ (item) => !item.block_instance_id && item?.module === "blog_author",
248
+ );
249
+
163
250
  return (
164
251
  <Section elementKey="layout">
165
252
  <Container elementKey="container">
@@ -175,38 +262,57 @@ const BlogPost1: React.FC<ILevoBlockBaseProps<IBlogPost1Content>> = () => {
175
262
  <Box elementKey="title-section">
176
263
  <Heading elementKey="title">{data.title}</Heading>
177
264
  <Box elementKey="meta-info">
178
- <Box elementKey="author-container">
179
- <Avatar className="h-10 w-10">
180
- <AvatarImageV2 src={data?.authors?.[0]?.profile_picture?.location} />
181
- <AvatarFallbackV2>
182
- {[data?.authors?.[0]?.first_name, data?.authors?.[0]?.last_name]
183
- ?.map((v) => v?.[0])
265
+ {data?.authors && Array.isArray(data?.authors) && (
266
+ <Box elementKey="authors-container">
267
+ {data?.authors?.map((v: any, index: number) => {
268
+ const authorName = [v?.first_name, v?.last_name]
184
269
  .filter(Boolean)
185
- .join("")
186
- .toUpperCase()}
187
- </AvatarFallbackV2>
188
- </Avatar>
189
- <Box elementKey="author-info-wrapper">
190
- <Heading elementKey="author-name">
191
- {data?.authors?.[0]
192
- ? [data?.authors?.[0]?.first_name, data?.authors?.[0]?.last_name]
193
- .filter(Boolean)
194
- .join(" ")
195
- : "Anonymous"}
196
- </Heading>
197
- <Typography elementKey="author-description">
198
- {data?.authors?.[0]?.bio || "Author"}
199
- </Typography>
270
+ .join(" ")
271
+ .trim();
272
+ const authorAltStr = authorName
273
+ ? `Profile photo of ${authorName}`
274
+ : "Author profile photo";
275
+ return (
276
+ <Box
277
+ key={`${v?.first_name}-${v?.last_name}-${index}`}
278
+ elementKey="author-container"
279
+ >
280
+ <Avatar className="h-10 w-10">
281
+ <AvatarImageV2
282
+ src={v?.profile_picture?.location}
283
+ className="object-cover"
284
+ alt={authorAltStr}
285
+ />
286
+ <AvatarFallbackV2>
287
+ {[v?.first_name, v?.last_name]
288
+ ?.map((v) => v?.[0])
289
+ .filter(Boolean)
290
+ .join("")
291
+ .toUpperCase()}
292
+ </AvatarFallbackV2>
293
+ </Avatar>
294
+ <Box elementKey="author-info-wrapper">
295
+ <Heading elementKey="author-name">{authorName || "Anonymous"}</Heading>
296
+ <Typography elementKey="author-description">
297
+ {/* {v?.bio || "Author"} */}
298
+ {`@${v?.username}`}
299
+ </Typography>
300
+ </Box>
301
+ </Box>
302
+ );
303
+ })}
200
304
  </Box>
305
+ )}
306
+ <Box elementKey="meta-info-sub-container">
307
+ <ClientOnly>
308
+ <Typography elementKey="publish-date">
309
+ {data.published_at
310
+ ? dayjs(data.published_at ?? data.created_at).format("DD MMM, YYYY")
311
+ : ""}
312
+ </Typography>
313
+ </ClientOnly>
314
+ <Typography elementKey="reading-time">{`${data.reading_time} mins`}</Typography>
201
315
  </Box>
202
- <ClientOnly>
203
- <Typography elementKey="publish-date">
204
- {data.published_at
205
- ? dayjs(data.published_at ?? data.created_at).format("DD MMM, YYYY")
206
- : ""}
207
- </Typography>
208
- </ClientOnly>
209
- <Typography elementKey="reading-time">{`${data.reading_time} mins`}</Typography>
210
316
  </Box>
211
317
  </Box>
212
318
  </Box>
@@ -222,6 +328,133 @@ const BlogPost1: React.FC<ILevoBlockBaseProps<IBlogPost1Content>> = () => {
222
328
  <BlogContent elementKey="content" content={data.content?.html || ""} />
223
329
  </Box>
224
330
  </Box>
331
+ {data?.authors && Array.isArray(data?.authors) && (
332
+ <Box elementKey="author-cards-container">
333
+ {data?.authors?.map((author: any, index: number) => {
334
+ const authorName = [author?.first_name, author?.last_name]
335
+ .filter(Boolean)
336
+ .join(" ")
337
+ .trim();
338
+ const authorAltStr = authorName
339
+ ? `Profile photo of ${authorName}`
340
+ : "Author profile photo";
341
+
342
+ const socials = sortSocials(
343
+ Object.entries((author?.socials as Record<string, string>) ?? {}).filter(
344
+ ([_key, value]) => Boolean(value),
345
+ ),
346
+ ) as [string, string][];
347
+
348
+ return (
349
+ <Box
350
+ elementKey="author-card-section"
351
+ key={`${author?.first_name}-${author?.last_name}-${index}`}
352
+ >
353
+ <Box elementKey="author-card-inner">
354
+ {/* Left: Avatar — plain img for grayscale hover effect */}
355
+ <Image
356
+ elementKey="author-card-image"
357
+ image={author?.profile_picture ?? MEDIA}
358
+ alt={authorAltStr}
359
+ />
360
+
361
+ {/* Right: Content */}
362
+ <Box elementKey="author-card-content">
363
+ {/* Name + social row */}
364
+ <Box
365
+ elementKey="author-card-content-header"
366
+ className="mb-4 flex flex-col justify-between gap-4 sm:flex-row sm:items-center"
367
+ >
368
+ <Box elementKey="author-card-content-info">
369
+ <Heading elementKey="author-card-name">{authorName}</Heading>
370
+ <Typography elementKey="author-card-designation">
371
+ {/* {(author as { designation?: string }).designation ?? ""} */}
372
+ {`@${author?.username}`}
373
+ </Typography>
374
+ </Box>
375
+ {/* Social icons — plain HTML per exec spec */}
376
+ <Box
377
+ elementKey="author-card-content-socials"
378
+ className="flex items-center gap-3"
379
+ >
380
+ {socials?.map(([key, value]) => {
381
+ return (
382
+ <Box
383
+ config={{
384
+ action: {
385
+ type: "external_link",
386
+ value,
387
+ options: {
388
+ openInNewTab: true,
389
+ },
390
+ },
391
+ }}
392
+ elementKey={`author-card-content-socials-${key}-wrapper`}
393
+ key={key}
394
+ >
395
+ <Icon elementKey={`author-card-content-socials-${key}-icon`} />
396
+ </Box>
397
+ );
398
+ })}
399
+ </Box>
400
+ </Box>
401
+ {/* Bio with Read More / Read Less toggle */}
402
+ <AuthorBioRenderer
403
+ index={index}
404
+ author={author}
405
+ expandedBios={expandedBios}
406
+ setExpandedBios={setExpandedBios}
407
+ />
408
+ {/* Action buttons — plain HTML per exec spec */}
409
+ <Box
410
+ elementKey="author-card-ctas"
411
+ className="flex flex-wrap items-center gap-4"
412
+ >
413
+ <Button elementKey="author-card-cta-first">
414
+ Follow {author.first_name}
415
+ </Button>
416
+ <Button
417
+ elementKey="author-card-cta-second"
418
+ config={
419
+ (isStudio && blogAuthorStudioConfig?.preview_url) ||
420
+ (!isStudio && blogAuthorStudioConfig?.live_url)
421
+ ? {
422
+ action: {
423
+ type: "external_link",
424
+ value:
425
+ isStudio && blogAuthorStudioConfig.preview_url
426
+ ? blogAuthorStudioConfig.preview_url.replace(
427
+ /\{[^}]+\}/g,
428
+ author?.username,
429
+ )
430
+ : !isStudio && blogAuthorStudioConfig.live_url
431
+ ? blogAuthorStudioConfig.live_url.replace(
432
+ /\{[^}]+\}/g,
433
+ author?.username,
434
+ )
435
+ : "",
436
+ options: {},
437
+ },
438
+ }
439
+ : {}
440
+ }
441
+ >
442
+ View Profile
443
+ </Button>
444
+ </Box>
445
+ </Box>
446
+ </Box>
447
+ {/* Footer divider */}
448
+ {index === data?.authors?.length - 1 && (
449
+ <Box elementKey="author-card-section-footer">
450
+ <Typography elementKey="author-card-editorial" />
451
+ </Box>
452
+ )}
453
+ </Box>
454
+ );
455
+ })}
456
+ </Box>
457
+ )}
225
458
  </Container>
226
459
  </Section>
227
460
  );