@levo-so/blocks 0.1.100 → 0.1.102
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 +5 -4
- 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 +256 -32
- 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-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/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 +2 -0
- 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/navbar/navbar-1.schema.ts +319 -186
- package/src/blocks/navbar/navbar-2.schema.ts +311 -190
- package/src/blocks/navbar/navbar-3.schema.ts +612 -570
- package/src/blocks/navbar/navbar-4.schema.ts +741 -1250
- 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 +789 -836
- 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 -3
- package/src/blocks/services/service-3.schema.ts +587 -215
- package/src/blocks/services/service-3.tsx +4 -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 +2 -0
- package/src/blocks/features/features-1/variants/stylized-cards-v3.ts +0 -268
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { ILevoBlockBaseProps } from "@levo-so/studio";
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Button,
|
|
5
|
+
Container,
|
|
6
|
+
Heading,
|
|
7
|
+
Icon,
|
|
8
|
+
Image,
|
|
9
|
+
Section,
|
|
10
|
+
Typography,
|
|
11
|
+
usePageContext,
|
|
12
|
+
} from "@levo-so/studio";
|
|
13
|
+
import type React from "react";
|
|
14
|
+
|
|
15
|
+
import { type IBlogAuthor1Content, MEDIA } from "./blog-author-1.schema";
|
|
16
|
+
|
|
17
|
+
/** Canonical display order for social platform icons. */
|
|
18
|
+
const SOCIAL_ORDER = [
|
|
19
|
+
"instagram",
|
|
20
|
+
"twitter",
|
|
21
|
+
"linkedin",
|
|
22
|
+
"facebook",
|
|
23
|
+
"youtube",
|
|
24
|
+
"github",
|
|
25
|
+
"tiktok",
|
|
26
|
+
"website",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const sortSocials = (entries: [string, unknown][]) =>
|
|
30
|
+
[...entries].sort(([a], [b]) => {
|
|
31
|
+
const aIdx = SOCIAL_ORDER.indexOf(a);
|
|
32
|
+
const bIdx = SOCIAL_ORDER.indexOf(b);
|
|
33
|
+
return (aIdx === -1 ? SOCIAL_ORDER.length : aIdx) - (bIdx === -1 ? SOCIAL_ORDER.length : bIdx);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const BlogAuthor1: React.FC<ILevoBlockBaseProps<IBlogAuthor1Content>> = () => {
|
|
37
|
+
// On an author page the blog context exposes the author as the primary entity
|
|
38
|
+
// via the same authors array shape used across the blog module.
|
|
39
|
+
const data = usePageContext("blog_author") as {
|
|
40
|
+
account: any;
|
|
41
|
+
blogs: string[];
|
|
42
|
+
post_count?: number | null;
|
|
43
|
+
};
|
|
44
|
+
const author = data?.account;
|
|
45
|
+
|
|
46
|
+
const authorName = [author?.first_name, author?.last_name].filter(Boolean).join(" ").trim();
|
|
47
|
+
const authorAltStr = authorName ? `Profile photo of ${authorName}` : "Author profile photo";
|
|
48
|
+
|
|
49
|
+
const socials = sortSocials(
|
|
50
|
+
Object.entries((author?.socials as Record<string, string>) ?? {}).filter(([_key, value]) =>
|
|
51
|
+
Boolean(value),
|
|
52
|
+
),
|
|
53
|
+
) as [string, string][];
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Section elementKey="layout">
|
|
57
|
+
<Container elementKey="container">
|
|
58
|
+
<Box elementKey="author-card-section">
|
|
59
|
+
<Box elementKey="author-card-inner">
|
|
60
|
+
{/* Left: Avatar — plain img for grayscale hover effect */}
|
|
61
|
+
<Image
|
|
62
|
+
elementKey="author-card-image"
|
|
63
|
+
image={author?.profile_picture ?? MEDIA}
|
|
64
|
+
alt={authorAltStr}
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
{/* Right: Content */}
|
|
68
|
+
<Box elementKey="author-card-content">
|
|
69
|
+
{/* Name + social row */}
|
|
70
|
+
<Box
|
|
71
|
+
elementKey="author-card-content-header"
|
|
72
|
+
className="mb-4 flex flex-col justify-between gap-4 sm:flex-row sm:items-center"
|
|
73
|
+
>
|
|
74
|
+
<Box elementKey="author-card-content-info">
|
|
75
|
+
<Heading elementKey="author-card-name">{authorName}</Heading>
|
|
76
|
+
<Typography elementKey="author-card-designation">
|
|
77
|
+
{/* {(author as { designation?: string }).designation ?? ""} */}
|
|
78
|
+
{`@${author?.username}`}
|
|
79
|
+
</Typography>
|
|
80
|
+
</Box>
|
|
81
|
+
{/* Social icons — plain HTML per exec spec */}
|
|
82
|
+
<Box elementKey="author-card-content-socials" className="flex items-center gap-3">
|
|
83
|
+
{socials?.map(([key, value]) => {
|
|
84
|
+
return (
|
|
85
|
+
<Box
|
|
86
|
+
config={{
|
|
87
|
+
action: {
|
|
88
|
+
type: "external_link",
|
|
89
|
+
value,
|
|
90
|
+
options: {
|
|
91
|
+
openInNewTab: true,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
}}
|
|
95
|
+
elementKey={`author-card-content-socials-${key}-wrapper`}
|
|
96
|
+
key={key}
|
|
97
|
+
>
|
|
98
|
+
<Icon elementKey={`author-card-content-socials-${key}-icon`} />
|
|
99
|
+
</Box>
|
|
100
|
+
);
|
|
101
|
+
})}
|
|
102
|
+
</Box>
|
|
103
|
+
</Box>
|
|
104
|
+
{/* Bio */}
|
|
105
|
+
<Typography elementKey="author-bio">{author?.bio}</Typography>
|
|
106
|
+
{/* Action buttons — plain HTML per exec spec */}
|
|
107
|
+
<Box elementKey="author-card-ctas" className="flex flex-wrap items-center gap-4">
|
|
108
|
+
<Button elementKey="author-card-cta-first">
|
|
109
|
+
{data?.post_count ?? 0} contributions made so far
|
|
110
|
+
</Button>
|
|
111
|
+
{/* <Button elementKey="author-card-cta-second">View Profile</Button> */}
|
|
112
|
+
</Box>
|
|
113
|
+
</Box>
|
|
114
|
+
</Box>
|
|
115
|
+
</Box>
|
|
116
|
+
</Container>
|
|
117
|
+
</Section>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default BlogAuthor1;
|
|
@@ -2,6 +2,8 @@ import type { IModuleStudioConfig, IPost } from "@levo-so/core";
|
|
|
2
2
|
import {
|
|
3
3
|
Avatar,
|
|
4
4
|
AvatarFallbackV2,
|
|
5
|
+
AvatarGroup,
|
|
6
|
+
AvatarGroupCount,
|
|
5
7
|
AvatarImageV2,
|
|
6
8
|
Box,
|
|
7
9
|
Button,
|
|
@@ -10,6 +12,7 @@ import {
|
|
|
10
12
|
Icon,
|
|
11
13
|
type ILevoBlockBaseProps,
|
|
12
14
|
Image,
|
|
15
|
+
type IStudioSource,
|
|
13
16
|
Section,
|
|
14
17
|
Typography,
|
|
15
18
|
useBlockContext,
|
|
@@ -24,11 +27,106 @@ import type { IBlogListing1Content } from "./blog-listing-1.schema";
|
|
|
24
27
|
interface IBlogMeta {
|
|
25
28
|
total: number;
|
|
26
29
|
pages: number;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
};
|
|
30
|
+
studio_config?: (IModuleStudioConfig & {
|
|
31
|
+
module: IStudioSource["module"];
|
|
32
|
+
})[];
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
const getAuthorsText = (authors: any[]): string => {
|
|
36
|
+
const names = authors.map((a) => [a?.first_name].filter(Boolean).join(" ") || "Anonymous");
|
|
37
|
+
|
|
38
|
+
if (names.length === 2) return `${names[0]} and ${names[1]}`;
|
|
39
|
+
// if (names.length === 3) return `${names[0]}, ${names[1]}, and ${names[2]}`;
|
|
40
|
+
|
|
41
|
+
// 4+ authors: show first 3 names then remaining count
|
|
42
|
+
const remaining = names.length - 2;
|
|
43
|
+
return `${names[0]}, ${names[1]} and ${remaining} ${remaining === 1 ? "other" : "others"}`;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const AuthorRenderer: React.FC<{
|
|
47
|
+
index: number;
|
|
48
|
+
authors: any[];
|
|
49
|
+
}> = ({ authors, index }) => {
|
|
50
|
+
if (authors?.length === 1) {
|
|
51
|
+
const authorNameArr = [authors?.[0]?.first_name, authors?.[0]?.last_name].filter(Boolean);
|
|
52
|
+
|
|
53
|
+
const authorName = authorNameArr.join(" ");
|
|
54
|
+
|
|
55
|
+
const authorNameFallback = authorNameArr
|
|
56
|
+
?.map((v) => v?.[0])
|
|
57
|
+
.join("")
|
|
58
|
+
.toUpperCase();
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Box elementKey={`blogs.${index}.author_wrapper`}>
|
|
62
|
+
<Avatar className="h-10 w-10">
|
|
63
|
+
<AvatarImageV2 src={authors?.[0]?.profile_picture?.location} />
|
|
64
|
+
<AvatarFallbackV2>{authorNameFallback}</AvatarFallbackV2>
|
|
65
|
+
</Avatar>
|
|
66
|
+
<Box elementKey={`blogs.${index}.author-info-wrapper`}>
|
|
67
|
+
<Typography elementKey={`blogs.${index}.author-name`}>
|
|
68
|
+
{authorName ? authorName : "Anonymous"}
|
|
69
|
+
</Typography>
|
|
70
|
+
{/* <Typography elementKey={`blogs.${index}.author-description`}>
|
|
71
|
+
{authors?.[0]?.username}
|
|
72
|
+
</Typography> */}
|
|
73
|
+
</Box>
|
|
74
|
+
</Box>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (authors?.length > 1) {
|
|
79
|
+
const firstThreeAuthors = authors?.slice(0, 2);
|
|
80
|
+
const remainingAuthors = authors?.slice(2);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Box elementKey={`blogs.${index}.author_wrapper`}>
|
|
84
|
+
<AvatarGroup className="">
|
|
85
|
+
{firstThreeAuthors?.map((author, authorIndex) => {
|
|
86
|
+
const authorNameArr = [author?.first_name, author?.last_name].filter(Boolean);
|
|
87
|
+
|
|
88
|
+
const authorName = authorNameArr.join(" ");
|
|
89
|
+
|
|
90
|
+
const authorNameFallback = authorNameArr
|
|
91
|
+
?.map((v) => v?.[0])
|
|
92
|
+
.join("")
|
|
93
|
+
.toUpperCase();
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<Avatar key={`blogs.${index}.blog-author.${authorIndex}`} className="h-10 w-10">
|
|
97
|
+
<AvatarImageV2
|
|
98
|
+
src={author?.profile_picture?.location}
|
|
99
|
+
className="object-cover"
|
|
100
|
+
alt={authorName}
|
|
101
|
+
/>
|
|
102
|
+
<AvatarFallbackV2>{authorNameFallback}</AvatarFallbackV2>
|
|
103
|
+
</Avatar>
|
|
104
|
+
);
|
|
105
|
+
})}
|
|
106
|
+
{remainingAuthors?.length > 0 && (
|
|
107
|
+
<AvatarGroupCount
|
|
108
|
+
className="h-10 w-10"
|
|
109
|
+
style={{
|
|
110
|
+
background: "var(--color-brand-secondary)",
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
+{remainingAuthors?.length}
|
|
114
|
+
</AvatarGroupCount>
|
|
115
|
+
)}
|
|
116
|
+
</AvatarGroup>
|
|
117
|
+
<Typography
|
|
118
|
+
elementKey={`blogs.${index}.author-name`}
|
|
119
|
+
title={authors?.map((v) => v?.first_name)?.join(", ")}
|
|
120
|
+
>
|
|
121
|
+
{getAuthorsText(authors)}
|
|
122
|
+
</Typography>
|
|
123
|
+
</Box>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return <Box elementKey={`blogs.${index}.author_wrapper`} />;
|
|
128
|
+
};
|
|
129
|
+
|
|
32
130
|
const BlogListing1: React.FC<ILevoBlockBaseProps<IBlogListing1Content>> = ({ content }) => {
|
|
33
131
|
const { response, query, setQuery } = useContextEngine<IPost, IBlogMeta>("blogs");
|
|
34
132
|
const { isStudio, blockData } = useBlockContext();
|
|
@@ -36,7 +134,9 @@ const BlogListing1: React.FC<ILevoBlockBaseProps<IBlogListing1Content>> = ({ con
|
|
|
36
134
|
const handleNext = () => setQuery({ page: (query?.page || 0) + 1 });
|
|
37
135
|
const handlePrevious = () => setQuery({ page: (query?.page || 0) - 1 });
|
|
38
136
|
|
|
39
|
-
const
|
|
137
|
+
const blogStudioConfig = response?.meta?.studio_config?.find(
|
|
138
|
+
(item) => !item.block_instance_id && item?.module === "blog",
|
|
139
|
+
);
|
|
40
140
|
const fallbackConfig = (blockData as any)?.config?.blogs?.[0]?.blog?.action?.value;
|
|
41
141
|
|
|
42
142
|
return (
|
|
@@ -67,15 +167,16 @@ const BlogListing1: React.FC<ILevoBlockBaseProps<IBlogListing1Content>> = ({ con
|
|
|
67
167
|
key={index}
|
|
68
168
|
elementKey={`blogs.${index}.blog`}
|
|
69
169
|
config={
|
|
70
|
-
(isStudio &&
|
|
170
|
+
(isStudio && blogStudioConfig?.preview_url) ||
|
|
171
|
+
(!isStudio && blogStudioConfig?.live_url)
|
|
71
172
|
? {
|
|
72
173
|
action: {
|
|
73
174
|
type: "external_link",
|
|
74
175
|
value:
|
|
75
|
-
isStudio &&
|
|
76
|
-
?
|
|
77
|
-
: !isStudio &&
|
|
78
|
-
?
|
|
176
|
+
isStudio && blogStudioConfig.preview_url
|
|
177
|
+
? blogStudioConfig.preview_url.replace(/\{[^}]+\}/g, item?.slug)
|
|
178
|
+
: !isStudio && blogStudioConfig.live_url
|
|
179
|
+
? blogStudioConfig.live_url.replace(/\{[^}]+\}/g, item?.slug)
|
|
79
180
|
: "",
|
|
80
181
|
options: {},
|
|
81
182
|
},
|
|
@@ -128,30 +229,7 @@ const BlogListing1: React.FC<ILevoBlockBaseProps<IBlogListing1Content>> = ({ con
|
|
|
128
229
|
{item?.summary}
|
|
129
230
|
</Typography>
|
|
130
231
|
</Box>
|
|
131
|
-
<
|
|
132
|
-
<Avatar className="h-10 w-10">
|
|
133
|
-
<AvatarImageV2 src={item?.authors?.[0]?.profile_picture?.location} />
|
|
134
|
-
<AvatarFallbackV2>
|
|
135
|
-
{[item?.authors?.[0]?.first_name, item?.authors?.[0]?.last_name]
|
|
136
|
-
?.map((v) => v?.[0])
|
|
137
|
-
.filter(Boolean)
|
|
138
|
-
.join("")
|
|
139
|
-
.toUpperCase()}
|
|
140
|
-
</AvatarFallbackV2>
|
|
141
|
-
</Avatar>
|
|
142
|
-
<Box elementKey={`blogs.${index}.author_info_wrapper`}>
|
|
143
|
-
<Heading elementKey={`blogs.${index}.author_name`}>
|
|
144
|
-
{item?.authors?.[0]
|
|
145
|
-
? [item.authors[0].first_name, item.authors[0].last_name]
|
|
146
|
-
.filter(Boolean)
|
|
147
|
-
.join(" ")
|
|
148
|
-
: "Anonymous"}
|
|
149
|
-
</Heading>
|
|
150
|
-
<Typography elementKey={`blogs.${index}.author_description`}>
|
|
151
|
-
{item?.authors?.[0]?.bio || "Author"}
|
|
152
|
-
</Typography>
|
|
153
|
-
</Box>
|
|
154
|
-
</Box>
|
|
232
|
+
<AuthorRenderer authors={item?.authors ?? []} index={index} />
|
|
155
233
|
</Box>
|
|
156
234
|
<Button elementKey={`blogs.${index}.cta`} />
|
|
157
235
|
</Box>
|
|
@@ -945,18 +945,12 @@ export const BlogListing2: IBlock = {
|
|
|
945
945
|
"column-gap": "md",
|
|
946
946
|
"align-items": "center",
|
|
947
947
|
},
|
|
948
|
-
"header-
|
|
949
|
-
{
|
|
950
|
-
"
|
|
951
|
-
"white-space": "nowrap",
|
|
952
|
-
},
|
|
953
|
-
},
|
|
954
|
-
{
|
|
955
|
-
"header-cta": {
|
|
956
|
-
"white-space": "nowrap",
|
|
957
|
-
},
|
|
948
|
+
"header-ctas_levoGroupItem": {
|
|
949
|
+
"header-cta": {
|
|
950
|
+
"white-space": "nowrap",
|
|
958
951
|
},
|
|
959
|
-
|
|
952
|
+
},
|
|
953
|
+
"header-ctas": [{}, {}],
|
|
960
954
|
blogs_levoGroup: {
|
|
961
955
|
display: "grid",
|
|
962
956
|
width: "full",
|
|
@@ -1329,18 +1323,12 @@ export const BlogListing2: IBlock = {
|
|
|
1329
1323
|
"column-gap": "md",
|
|
1330
1324
|
"align-items": "center",
|
|
1331
1325
|
},
|
|
1332
|
-
"header-
|
|
1333
|
-
{
|
|
1334
|
-
"
|
|
1335
|
-
"white-space": "nowrap",
|
|
1336
|
-
},
|
|
1337
|
-
},
|
|
1338
|
-
{
|
|
1339
|
-
"header-cta": {
|
|
1340
|
-
"white-space": "nowrap",
|
|
1341
|
-
},
|
|
1326
|
+
"header-ctas_levoGroupItem": {
|
|
1327
|
+
"header-cta": {
|
|
1328
|
+
"white-space": "nowrap",
|
|
1342
1329
|
},
|
|
1343
|
-
|
|
1330
|
+
},
|
|
1331
|
+
"header-ctas": [{}, {}],
|
|
1344
1332
|
blogs_levoGroup: {
|
|
1345
1333
|
display: "grid",
|
|
1346
1334
|
width: "full",
|
|
@@ -1721,18 +1709,12 @@ export const BlogListing2: IBlock = {
|
|
|
1721
1709
|
"column-gap": "md",
|
|
1722
1710
|
"align-items": "center",
|
|
1723
1711
|
},
|
|
1724
|
-
"header-
|
|
1725
|
-
{
|
|
1726
|
-
"
|
|
1727
|
-
"white-space": "nowrap",
|
|
1728
|
-
},
|
|
1712
|
+
"header-ctas_levoGroupItem": {
|
|
1713
|
+
"header-cta": {
|
|
1714
|
+
"white-space": "nowrap",
|
|
1729
1715
|
},
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
"white-space": "nowrap",
|
|
1733
|
-
},
|
|
1734
|
-
},
|
|
1735
|
-
],
|
|
1716
|
+
},
|
|
1717
|
+
"header-ctas": [{}, {}],
|
|
1736
1718
|
blogs_levoGroup: {
|
|
1737
1719
|
display: "grid",
|
|
1738
1720
|
width: "full",
|
|
@@ -2,6 +2,8 @@ import type { IModuleStudioConfig, IPost } from "@levo-so/core";
|
|
|
2
2
|
import {
|
|
3
3
|
Avatar,
|
|
4
4
|
AvatarFallbackV2,
|
|
5
|
+
AvatarGroup,
|
|
6
|
+
AvatarGroupCount,
|
|
5
7
|
AvatarImageV2,
|
|
6
8
|
Box,
|
|
7
9
|
Button,
|
|
@@ -9,6 +11,7 @@ import {
|
|
|
9
11
|
Heading,
|
|
10
12
|
Icon,
|
|
11
13
|
type ILevoBlockBaseProps,
|
|
14
|
+
type IStudioSource,
|
|
12
15
|
Media,
|
|
13
16
|
Section,
|
|
14
17
|
Typography,
|
|
@@ -20,15 +23,117 @@ import dayjs from "dayjs";
|
|
|
20
23
|
import { ClientOnly } from "../event/ClientOnly";
|
|
21
24
|
import type { IBlogListing2Content } from "./blog-listing-2.schema";
|
|
22
25
|
|
|
26
|
+
const getAuthorsText = (authors: any[]): string => {
|
|
27
|
+
const names = authors.map((a) => [a?.first_name].filter(Boolean).join(" ") || "Anonymous");
|
|
28
|
+
|
|
29
|
+
if (names.length === 2) return `${names[0]} and ${names[1]}`;
|
|
30
|
+
// if (names.length === 3) return `${names[0]}, ${names[1]}, and ${names[2]}`;
|
|
31
|
+
|
|
32
|
+
// 4+ authors: show first 3 names then remaining count
|
|
33
|
+
const remaining = names.length - 2;
|
|
34
|
+
return `${names[0]}, ${names[1]} and ${remaining} ${remaining === 1 ? "other" : "others"}`;
|
|
35
|
+
};
|
|
36
|
+
|
|
23
37
|
/** */
|
|
24
38
|
interface IBlogMeta {
|
|
25
39
|
total: number;
|
|
26
40
|
pages: number;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
};
|
|
41
|
+
studio_config?: (IModuleStudioConfig & {
|
|
42
|
+
module: IStudioSource["module"];
|
|
43
|
+
})[];
|
|
30
44
|
}
|
|
31
45
|
|
|
46
|
+
const AuthorRenderer: React.FC<{
|
|
47
|
+
index: number;
|
|
48
|
+
authors: any[];
|
|
49
|
+
}> = ({ authors, index }) => {
|
|
50
|
+
if (authors?.length === 1) {
|
|
51
|
+
const authorNameArr = [authors?.[0]?.first_name, authors?.[0]?.last_name].filter(Boolean);
|
|
52
|
+
|
|
53
|
+
const authorName = authorNameArr.join(" ");
|
|
54
|
+
|
|
55
|
+
const authorNameFallback = authorNameArr
|
|
56
|
+
?.map((v) => v?.[0])
|
|
57
|
+
.join("")
|
|
58
|
+
.toUpperCase();
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Box elementKey={`blogs.${index}.blog-author`}>
|
|
62
|
+
<Avatar className="h-8 w-8">
|
|
63
|
+
<AvatarImageV2 src={authors?.[0]?.profile_picture?.location} />
|
|
64
|
+
<AvatarFallbackV2>{authorNameFallback}</AvatarFallbackV2>
|
|
65
|
+
</Avatar>
|
|
66
|
+
<Box elementKey={`blogs.${index}.author-info-wrapper`}>
|
|
67
|
+
<Typography elementKey={`blogs.${index}.author-name`}>
|
|
68
|
+
{authorName ? authorName : "Anonymous"}
|
|
69
|
+
</Typography>
|
|
70
|
+
{/* <Typography elementKey={`blogs.${index}.author-description`}>
|
|
71
|
+
{authors?.[0]?.username}
|
|
72
|
+
</Typography> */}
|
|
73
|
+
</Box>
|
|
74
|
+
</Box>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (authors?.length > 1) {
|
|
79
|
+
const firstThreeAuthors = authors?.slice(0, 2);
|
|
80
|
+
const remainingAuthors = authors?.slice(2);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Box
|
|
84
|
+
elementKey={`blogs.${index}.blog-authors`}
|
|
85
|
+
style={{
|
|
86
|
+
display: "flex",
|
|
87
|
+
gap: "16px",
|
|
88
|
+
alignItems: "center",
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<AvatarGroup className="">
|
|
92
|
+
{firstThreeAuthors?.map((author, authorIndex) => {
|
|
93
|
+
const authorNameArr = [author?.first_name, author?.last_name].filter(Boolean);
|
|
94
|
+
|
|
95
|
+
const authorName = authorNameArr.join(" ");
|
|
96
|
+
|
|
97
|
+
const authorNameFallback = authorNameArr
|
|
98
|
+
?.map((v) => v?.[0])
|
|
99
|
+
.join("")
|
|
100
|
+
.toUpperCase();
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<Avatar key={`blogs.${index}.blog-author.${authorIndex}`} className="h-8 w-8">
|
|
104
|
+
<AvatarImageV2
|
|
105
|
+
src={author?.profile_picture?.location}
|
|
106
|
+
className="object-cover"
|
|
107
|
+
alt={authorName}
|
|
108
|
+
/>
|
|
109
|
+
<AvatarFallbackV2>{authorNameFallback}</AvatarFallbackV2>
|
|
110
|
+
</Avatar>
|
|
111
|
+
);
|
|
112
|
+
})}
|
|
113
|
+
{remainingAuthors?.length > 0 && (
|
|
114
|
+
<AvatarGroupCount
|
|
115
|
+
className="h-8 w-8"
|
|
116
|
+
style={{
|
|
117
|
+
background: "var(--color-brand-secondary)",
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
+{remainingAuthors?.length}
|
|
121
|
+
</AvatarGroupCount>
|
|
122
|
+
)}
|
|
123
|
+
</AvatarGroup>
|
|
124
|
+
<Typography
|
|
125
|
+
elementKey={`blogs.${index}.author-name`}
|
|
126
|
+
title={authors?.map((v) => v?.first_name)?.join(", ")}
|
|
127
|
+
>
|
|
128
|
+
{getAuthorsText(authors)}
|
|
129
|
+
</Typography>
|
|
130
|
+
</Box>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return <Box elementKey={`blogs.${index}.blog-author`} />;
|
|
135
|
+
};
|
|
136
|
+
|
|
32
137
|
const BlogListing2: React.FC<ILevoBlockBaseProps<IBlogListing2Content>> = ({ content }) => {
|
|
33
138
|
const { response, query, setQuery } = useContextEngine<IPost, IBlogMeta>("blogs");
|
|
34
139
|
const { isStudio, blockData } = useBlockContext();
|
|
@@ -36,7 +141,9 @@ const BlogListing2: React.FC<ILevoBlockBaseProps<IBlogListing2Content>> = ({ con
|
|
|
36
141
|
const handleNext = () => setQuery({ page: (query?.page || 0) + 1 });
|
|
37
142
|
const handlePrevious = () => setQuery({ page: (query?.page || 0) - 1 });
|
|
38
143
|
|
|
39
|
-
const
|
|
144
|
+
const blogStudioConfig = response?.meta?.studio_config?.find(
|
|
145
|
+
(item) => !item.block_instance_id && item?.module === "blog",
|
|
146
|
+
);
|
|
40
147
|
const fallbackConfig = (blockData as any)?.config?.blogs?.[0]?.blog?.action?.value;
|
|
41
148
|
|
|
42
149
|
return (
|
|
@@ -67,15 +174,16 @@ const BlogListing2: React.FC<ILevoBlockBaseProps<IBlogListing2Content>> = ({ con
|
|
|
67
174
|
data-levo_group_item
|
|
68
175
|
elementKey={`blogs.${index}.blog`}
|
|
69
176
|
config={
|
|
70
|
-
(isStudio &&
|
|
177
|
+
(isStudio && blogStudioConfig?.preview_url) ||
|
|
178
|
+
(!isStudio && blogStudioConfig?.live_url)
|
|
71
179
|
? {
|
|
72
180
|
action: {
|
|
73
181
|
type: "external_link",
|
|
74
182
|
value:
|
|
75
|
-
isStudio &&
|
|
76
|
-
?
|
|
77
|
-
: !isStudio &&
|
|
78
|
-
?
|
|
183
|
+
isStudio && blogStudioConfig.preview_url
|
|
184
|
+
? blogStudioConfig.preview_url.replace(/\{[^}]+\}/g, item?.slug)
|
|
185
|
+
: !isStudio && blogStudioConfig.live_url
|
|
186
|
+
? blogStudioConfig.live_url.replace(/\{[^}]+\}/g, item?.slug)
|
|
79
187
|
: "",
|
|
80
188
|
options: {},
|
|
81
189
|
},
|
|
@@ -123,30 +231,7 @@ const BlogListing2: React.FC<ILevoBlockBaseProps<IBlogListing2Content>> = ({ con
|
|
|
123
231
|
</Typography>
|
|
124
232
|
</Box>
|
|
125
233
|
<Box elementKey={`blogs.${index}.blog-footer`}>
|
|
126
|
-
<
|
|
127
|
-
<Avatar className="h-10 w-10">
|
|
128
|
-
<AvatarImageV2 src={item?.authors?.[0]?.profile_picture?.location} />
|
|
129
|
-
<AvatarFallbackV2>
|
|
130
|
-
{[item?.authors?.[0]?.first_name, item?.authors?.[0]?.last_name]
|
|
131
|
-
?.map((v) => v?.[0])
|
|
132
|
-
.filter(Boolean)
|
|
133
|
-
.join("")
|
|
134
|
-
.toUpperCase()}
|
|
135
|
-
</AvatarFallbackV2>
|
|
136
|
-
</Avatar>
|
|
137
|
-
<Box elementKey={`blogs.${index}.author-info-wrapper`}>
|
|
138
|
-
<Typography elementKey={`blogs.${index}.author-name`}>
|
|
139
|
-
{item?.authors?.[0]
|
|
140
|
-
? [item?.authors?.[0]?.first_name, item?.authors?.[0]?.last_name]
|
|
141
|
-
.filter(Boolean)
|
|
142
|
-
.join(" ")
|
|
143
|
-
: "Anonymous"}
|
|
144
|
-
</Typography>
|
|
145
|
-
<Typography elementKey={`blogs.${index}.author-description`}>
|
|
146
|
-
{item?.authors?.[0]?.bio || "Author"}
|
|
147
|
-
</Typography>
|
|
148
|
-
</Box>
|
|
149
|
-
</Box>
|
|
234
|
+
<AuthorRenderer authors={item?.authors ?? []} index={index} />
|
|
150
235
|
<Box elementKey={`blogs.${index}.blog-reading-time-wrapper`}>
|
|
151
236
|
<Icon elementKey={`blogs.${index}.reading-time-icon`} />
|
|
152
237
|
<Typography elementKey={`blogs.${index}.reading-time`}>
|
|
@@ -594,18 +594,12 @@ export const BlogListing3: IBlock = {
|
|
|
594
594
|
"column-gap": "md",
|
|
595
595
|
"align-items": "center",
|
|
596
596
|
},
|
|
597
|
-
"header-
|
|
598
|
-
{
|
|
599
|
-
"
|
|
600
|
-
"white-space": "nowrap",
|
|
601
|
-
},
|
|
602
|
-
},
|
|
603
|
-
{
|
|
604
|
-
"header-cta": {
|
|
605
|
-
"white-space": "nowrap",
|
|
606
|
-
},
|
|
597
|
+
"header-ctas_levoGroupItem": {
|
|
598
|
+
"header-cta": {
|
|
599
|
+
"white-space": "nowrap",
|
|
607
600
|
},
|
|
608
|
-
|
|
601
|
+
},
|
|
602
|
+
"header-ctas": [{}, {}],
|
|
609
603
|
blogs_levoGroup: {
|
|
610
604
|
display: "grid",
|
|
611
605
|
"grid-template-columns": "repeat(2, minmax(0, 1fr))",
|
|
@@ -893,18 +887,12 @@ export const BlogListing3: IBlock = {
|
|
|
893
887
|
"column-gap": "md",
|
|
894
888
|
"align-items": "center",
|
|
895
889
|
},
|
|
896
|
-
"header-
|
|
897
|
-
{
|
|
898
|
-
"
|
|
899
|
-
"white-space": "nowrap",
|
|
900
|
-
},
|
|
901
|
-
},
|
|
902
|
-
{
|
|
903
|
-
"header-cta": {
|
|
904
|
-
"white-space": "nowrap",
|
|
905
|
-
},
|
|
890
|
+
"header-ctas_levoGroupItem": {
|
|
891
|
+
"header-cta": {
|
|
892
|
+
"white-space": "nowrap",
|
|
906
893
|
},
|
|
907
|
-
|
|
894
|
+
},
|
|
895
|
+
"header-ctas": [{}, {}],
|
|
908
896
|
blogs_levoGroup: {
|
|
909
897
|
display: "grid",
|
|
910
898
|
"grid-template-columns": "repeat(2, minmax(0, 1fr))",
|