@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.
- package/package.json +24 -23
- package/src/blocks/about-us/about-us-1.schema.ts +302 -295
- package/src/blocks/about-us/about-us-2.schema.ts +225 -220
- package/src/blocks/blogs/blog-author-1.schema.ts +719 -0
- package/src/blocks/blogs/blog-author-1.tsx +121 -0
- package/src/blocks/blogs/blog-listing-1.tsx +111 -33
- package/src/blocks/blogs/blog-listing-2.schema.ts +15 -33
- package/src/blocks/blogs/blog-listing-2.tsx +118 -33
- package/src/blocks/blogs/blog-listing-3.schema.ts +10 -22
- package/src/blocks/blogs/blog-listing-3.tsx +118 -33
- package/src/blocks/blogs/blog-post-1.schema.ts +766 -16
- package/src/blocks/blogs/blog-post-1.tsx +272 -39
- package/src/blocks/calendar/calendar-1.schema.ts +641 -192
- package/src/blocks/calendar/calendar-1.tsx +1 -0
- package/src/blocks/cards/cards-10.schema.ts +557 -544
- package/src/blocks/cards/cards-11.schema.ts +525 -419
- package/src/blocks/cards/cards-12.schema.ts +538 -147
- package/src/blocks/cards/cards-13.schema.ts +482 -312
- package/src/blocks/cards/cards-14.schema.ts +423 -196
- package/src/blocks/cards/cards-15.schema.ts +309 -143
- package/src/blocks/cards/cards-16.schema.ts +1277 -175
- package/src/blocks/cards/cards-17.schema.ts +2908 -1303
- package/src/blocks/cards/cards-18.schema.ts +1404 -217
- package/src/blocks/cards/cards-19.schema.ts +1105 -1084
- package/src/blocks/cards/cards-19.tsx +4 -5
- package/src/blocks/cards/cards-2.schema.ts +513 -42
- package/src/blocks/cards/cards-20.schema.ts +790 -212
- package/src/blocks/cards/cards-21.schema.ts +1215 -229
- package/src/blocks/cards/cards-21.tsx +51 -6
- package/src/blocks/cards/cards-22.schema.ts +1252 -2809
- package/src/blocks/cards/cards-22.tsx +6 -7
- package/src/blocks/cards/cards-23.schema.ts +571 -109
- package/src/blocks/cards/cards-3.schema.ts +355 -116
- package/src/blocks/cards/cards-4.schema.ts +805 -321
- package/src/blocks/cards/cards-5.schema.ts +216 -64
- package/src/blocks/cards/cards-6.schema.ts +568 -1330
- package/src/blocks/cards/cards-7.schema.ts +1181 -1378
- package/src/blocks/cards/cards-8.schema.ts +585 -221
- package/src/blocks/cards/cards-9.schema.ts +763 -382
- package/src/blocks/carousel/carousel-1.schema.ts +257 -502
- package/src/blocks/carousel/carousel-1.tsx +2 -2
- package/src/blocks/carousel/carousel-2.schema.ts +771 -761
- package/src/blocks/community/community-1.schema.ts +8 -23
- package/src/blocks/community/community-1.tsx +3 -3
- package/src/blocks/community/community-post-1.tsx +4 -4
- package/src/blocks/contact-us/contact-us-1.schema.ts +360 -14
- package/src/blocks/contact-us/contact-us-2.schema.ts +441 -325
- package/src/blocks/content/content-1.schema.ts +800 -381
- package/src/blocks/content/content-10.schema.ts +763 -20
- package/src/blocks/content/content-2.schema.ts +426 -52
- package/src/blocks/content/content-3.schema.ts +631 -432
- package/src/blocks/content/content-4.schema.ts +378 -123
- package/src/blocks/content/content-5.schema.ts +1152 -682
- package/src/blocks/content/content-6.schema.ts +679 -707
- package/src/blocks/content/content-7.schema.ts +694 -291
- package/src/blocks/content/content-8.schema.ts +247 -7
- package/src/blocks/content/content-9.schema.ts +855 -116
- package/src/blocks/cta/cta-1.schema.ts +294 -62
- package/src/blocks/cta/cta-2.schema.ts +302 -66
- package/src/blocks/cta/cta-2.tsx +1 -2
- package/src/blocks/cta/cta-3.schema.ts +312 -62
- package/src/blocks/ecommerce/ecommerce-1.schema.ts +477 -0
- package/src/blocks/ecommerce/ecommerce-1.tsx +243 -0
- package/src/blocks/ecommerce/ecommerce-2.schema.ts +268 -0
- package/src/blocks/ecommerce/ecommerce-2.tsx +238 -0
- package/src/blocks/ecommerce/ecommerce-3.schema.ts +56 -0
- package/src/blocks/ecommerce/ecommerce-3.tsx +72 -0
- package/src/blocks/ecommerce/ecommerce-4.schema.ts +461 -0
- package/src/blocks/ecommerce/ecommerce-4.tsx +249 -0
- package/src/blocks/ecommerce/ecommerce-5.schema.ts +230 -0
- package/src/blocks/ecommerce/ecommerce-5.tsx +146 -0
- package/src/blocks/embed/embed-1.schema.ts +181 -0
- package/src/blocks/event/event-listing-2.schema.ts +5 -11
- package/src/blocks/event/event-listing-3.schema.ts +5 -11
- package/src/blocks/event/event-listing-4.schema.ts +5 -11
- package/src/blocks/event/event-listing.schema.ts +5 -11
- package/src/blocks/faq/faq-1.schema.ts +439 -260
- package/src/blocks/faq/faq-2.schema.ts +444 -266
- package/src/blocks/faq/faq-3.schema.ts +1420 -224
- package/src/blocks/features/features-1/features-1.schema.ts +21 -21
- package/src/blocks/features/features-1/variants/default.ts +120 -138
- package/src/blocks/features/features-1/variants/index.ts +2 -2
- package/src/blocks/features/features-1/variants/minimal-list.ts +139 -0
- package/src/blocks/features/features-1/variants/stylized-cards-v1.ts +69 -385
- package/src/blocks/features/features-1/variants/stylized-cards-v2.ts +74 -408
- package/src/blocks/features/features-2.schema.ts +368 -286
- package/src/blocks/features/features-3.schema.ts +557 -280
- package/src/blocks/features/features-4.schema.ts +940 -432
- package/src/blocks/features/features-5.schema.ts +566 -407
- package/src/blocks/features/features-6.schema.ts +557 -523
- package/src/blocks/filter-listing/filter-listing-1.schema.ts +922 -270
- package/src/blocks/floating-buttons/floating-button-1.schema.ts +123 -0
- package/src/blocks/footer/footer-1.schema.ts +383 -401
- package/src/blocks/footer/footer-2.schema.ts +337 -878
- package/src/blocks/footer/footer-3.schema.ts +445 -381
- package/src/blocks/footer/footer-4.schema.ts +610 -439
- package/src/blocks/footer/footer-5.schema.ts +367 -321
- package/src/blocks/footer/footer-6.schema.ts +610 -357
- package/src/blocks/form/form-1.schema.ts +237 -0
- package/src/blocks/hero/hero-1.schema.ts +251 -221
- package/src/blocks/hero/hero-10.schema.ts +23 -14
- package/src/blocks/hero/hero-11.schema.ts +728 -601
- package/src/blocks/hero/hero-12.schema.ts +524 -334
- package/src/blocks/hero/hero-13.schema.ts +83 -95
- package/src/blocks/hero/hero-2.schema.ts +277 -68
- package/src/blocks/hero/hero-3.schema.ts +371 -235
- package/src/blocks/hero/hero-3.tsx +1 -1
- package/src/blocks/hero/hero-4.schema.ts +605 -338
- package/src/blocks/hero/hero-5.schema.ts +545 -61
- package/src/blocks/hero/hero-6.schema.ts +446 -257
- package/src/blocks/hero/hero-6.tsx +1 -1
- package/src/blocks/hero/hero-7.schema.ts +1299 -131
- package/src/blocks/hero/hero-7.tsx +43 -23
- package/src/blocks/hero/hero-8.schema.ts +736 -236
- package/src/blocks/hero/hero-9.schema.ts +908 -58
- package/src/blocks/index.ts +14 -2
- package/src/blocks/jobs/jobs-1.schema.ts +741 -219
- package/src/blocks/logos/logos-1.schema.ts +317 -321
- package/src/blocks/logos/logos-1.tsx +32 -19
- package/src/blocks/logos/logos-2.schema.ts +223 -0
- package/src/blocks/logos/logos-2.tsx +55 -0
- package/src/blocks/navbar/navbar-1.schema.ts +319 -186
- package/src/blocks/navbar/navbar-3.schema.ts +618 -570
- package/src/blocks/navbar/navbar-3.tsx +3 -2
- package/src/blocks/navbar/navbar-4.schema.ts +731 -1234
- package/src/blocks/navbar/navbar-4.tsx +1 -6
- package/src/blocks/polling/polling-1.schema.ts +161 -0
- package/src/blocks/pricing/pricing-1.schema.ts +549 -626
- package/src/blocks/products/products-listing-1.schema.ts +363 -249
- package/src/blocks/products/products-listing-2.schema.ts +515 -257
- package/src/blocks/products/products-listing-3.schema.ts +790 -839
- package/src/blocks/products/products-listing-3.tsx +86 -84
- package/src/blocks/profile/profile-1.schema.ts +168 -187
- package/src/blocks/profile/profile-2.schema.ts +150 -122
- package/src/blocks/profile/profile-3.schema.ts +307 -256
- package/src/blocks/profile/profile-4.schema.ts +372 -504
- package/src/blocks/profile/profile-5.schema.ts +186 -79
- package/src/blocks/services/service-1.schema.ts +700 -631
- package/src/blocks/services/service-2.schema.ts +559 -82
- package/src/blocks/services/service-2.tsx +5 -6
- package/src/blocks/services/service-3.schema.ts +592 -207
- package/src/blocks/services/service-3.tsx +10 -2
- package/src/blocks/stats/stats-1.schema.ts +347 -257
- package/src/blocks/stats/stats-2.schema.ts +14 -2
- package/src/blocks/stats/variants/default.ts +14 -37
- package/src/blocks/stats/variants/extra.ts +232 -0
- package/src/blocks/stats/variants/hero.ts +1 -30
- package/src/blocks/stats/variants/index.ts +13 -1
- package/src/blocks/tabs/tab-1.schema.ts +1361 -214
- package/src/blocks/tabs/tab-2.schema.ts +984 -390
- package/src/blocks/tabs/tab-3.schema.ts +961 -95
- package/src/blocks/team/team-1.schema.ts +681 -564
- package/src/blocks/team/team-2.schema.ts +589 -935
- package/src/blocks/team/team-3.schema.ts +375 -284
- package/src/blocks/team/team-4.schema.ts +569 -355
- package/src/blocks/team/team-5.schema.ts +307 -111
- package/src/blocks/testimonial/testimonial-1.schema.ts +605 -706
- package/src/blocks/testimonial/testimonial-2.schema.ts +825 -1099
- package/src/blocks/testimonial/testimonial-3.schema.ts +580 -235
- package/src/blocks/testimonial/testimonial-4.schema.ts +1326 -1779
- package/src/blocks/testimonial/testimonial-5/variants/default.ts +179 -328
- package/src/blocks/testimonial/testimonial-5/variants/index.ts +9 -1
- package/src/blocks/testimonial/testimonial-5/variants/testimonialCentered.ts +194 -0
- package/src/blocks/testimonial/testimonial-5/variants/testimonialCompact.ts +188 -0
- package/src/blocks/testimonial/testimonial-5/variants/testimonialWithLogo.ts +275 -396
- package/src/blocks/testimonial/testimonial-5/variants/testimonialWithRatings.ts +254 -602
- package/src/blocks/testimonial/testimonial-5.schema.ts +6 -2
- package/src/blocks/testimonial/testimonial-6.schema.ts +962 -1234
- package/src/blocks/testimonial/testimonial-7.schema.ts +515 -233
- package/src/blocks/timeline/timeline-1.schema.ts +382 -140
- package/src/blocks/users/edit-profile.schema.ts +2 -8
- package/src/blocks/users/edit-profile.tsx +2 -3
- package/src/schemas/blocks.ts +14 -2
- package/src/schemas/categories.ts +6 -0
- package/src/blocks/features/features-1/variants/stylized-cards-v3.ts +0 -268
- package/src/blocks/navbar/navbar-2.schema.ts +0 -1134
- package/src/blocks/navbar/navbar-2.tsx +0 -381
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
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)
|
|
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.
|
|
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
|
|
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
|
-
|
|
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-
|
|
228
|
+
const contentElement = document.querySelector('[data-levo_element_key="content"]');
|
|
149
229
|
if (!contentElement) return;
|
|
150
230
|
|
|
151
|
-
const headingElements =
|
|
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
|
-
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
);
|