@levo-so/blocks 0.1.60 → 0.1.61

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 (230) hide show
  1. package/package.json +13 -14
  2. package/src/blocks/about-us/about-us-1.schema.ts +15 -30
  3. package/src/blocks/about-us/about-us-1.tsx +2 -4
  4. package/src/blocks/about-us/about-us-2.schema.ts +24 -48
  5. package/src/blocks/about-us/about-us-2.tsx +7 -13
  6. package/src/blocks/blogs/blog-listing-1.schema.ts +7 -14
  7. package/src/blocks/blogs/blog-listing-1.tsx +10 -21
  8. package/src/blocks/blogs/blog-listing-2.schema.ts +17 -34
  9. package/src/blocks/blogs/blog-listing-2.tsx +4 -12
  10. package/src/blocks/blogs/blog-listing-3.schema.ts +32 -64
  11. package/src/blocks/blogs/blog-listing-3.tsx +4 -12
  12. package/src/blocks/blogs/blog-post-1.schema.ts +9 -18
  13. package/src/blocks/blogs/blog-post-1.tsx +3 -14
  14. package/src/blocks/calendar/calendar-1.schema.ts +95 -6
  15. package/src/blocks/calendar/calendar-1.tsx +33 -29
  16. package/src/blocks/cards/cards-10.schema.ts +62 -124
  17. package/src/blocks/cards/cards-10.tsx +9 -14
  18. package/src/blocks/cards/cards-11.schema.ts +69 -138
  19. package/src/blocks/cards/cards-11.tsx +5 -11
  20. package/src/blocks/cards/cards-12.schema.ts +7 -14
  21. package/src/blocks/cards/cards-12.tsx +4 -10
  22. package/src/blocks/cards/cards-13.schema.ts +40 -80
  23. package/src/blocks/cards/cards-13.tsx +4 -6
  24. package/src/blocks/cards/cards-14.schema.ts +7 -14
  25. package/src/blocks/cards/cards-14.tsx +5 -11
  26. package/src/blocks/cards/cards-15.schema.ts +7 -14
  27. package/src/blocks/cards/cards-15.tsx +2 -4
  28. package/src/blocks/cards/cards-2.schema.ts +7 -14
  29. package/src/blocks/cards/cards-2.tsx +2 -15
  30. package/src/blocks/cards/cards-3.schema.ts +7 -14
  31. package/src/blocks/cards/cards-3.tsx +7 -13
  32. package/src/blocks/cards/cards-4.schema.ts +13 -26
  33. package/src/blocks/cards/cards-4.tsx +9 -15
  34. package/src/blocks/cards/cards-5.schema.ts +17 -34
  35. package/src/blocks/cards/cards-5.tsx +1 -9
  36. package/src/blocks/cards/cards-6.schema.ts +52 -104
  37. package/src/blocks/cards/cards-6.tsx +12 -20
  38. package/src/blocks/cards/cards-7.schema.ts +48 -96
  39. package/src/blocks/cards/cards-7.tsx +8 -27
  40. package/src/blocks/cards/cards-8.schema.ts +9 -20
  41. package/src/blocks/cards/cards-8.tsx +5 -11
  42. package/src/blocks/cards/cards-9.schema.ts +41 -82
  43. package/src/blocks/cards/cards-9.tsx +14 -22
  44. package/src/blocks/carousel/carousel-1.schema.ts +64 -128
  45. package/src/blocks/carousel/carousel-1.tsx +6 -8
  46. package/src/blocks/carousel/carousel-2.schema.ts +26 -52
  47. package/src/blocks/carousel/carousel-2.tsx +11 -23
  48. package/src/blocks/community/community-1.schema.ts +78 -32
  49. package/src/blocks/community/community-1.tsx +417 -550
  50. package/src/blocks/community/community-post-1.schema.ts +21 -30
  51. package/src/blocks/community/community-post-1.tsx +334 -84
  52. package/src/blocks/contact-us/contact-us-1.schema.ts +8 -16
  53. package/src/blocks/contact-us/contact-us-1.tsx +2 -12
  54. package/src/blocks/contact-us/contact-us-2.tsx +20 -31
  55. package/src/blocks/content/content-1.schema.ts +21 -42
  56. package/src/blocks/content/content-1.tsx +10 -17
  57. package/src/blocks/content/content-2.schema.ts +29 -58
  58. package/src/blocks/content/content-2.tsx +4 -4
  59. package/src/blocks/content/content-3.tsx +13 -29
  60. package/src/blocks/content/content-4.schema.ts +23 -46
  61. package/src/blocks/content/content-4.tsx +8 -17
  62. package/src/blocks/content/content-5.schema.ts +29 -24
  63. package/src/blocks/content/content-5.tsx +13 -27
  64. package/src/blocks/content/content-6.schema.ts +46 -94
  65. package/src/blocks/content/content-6.tsx +14 -31
  66. package/src/blocks/content/content-7.schema.ts +35 -72
  67. package/src/blocks/content/content-7.tsx +15 -31
  68. package/src/blocks/content/content-8.schema.ts +1 -6
  69. package/src/blocks/content/content-8.tsx +3 -3
  70. package/src/blocks/content/content-9.tsx +26 -41
  71. package/src/blocks/cta/cta-1.schema.ts +10 -20
  72. package/src/blocks/cta/cta-2.schema.ts +14 -28
  73. package/src/blocks/cta/cta-2.tsx +2 -6
  74. package/src/blocks/cta/cta-3.schema.ts +24 -50
  75. package/src/blocks/cta/cta-3.tsx +2 -6
  76. package/src/blocks/embed/embed-1.tsx +1 -9
  77. package/src/blocks/event/ClientOnly.tsx +1 -4
  78. package/src/blocks/event/event-details.schema.ts +16 -32
  79. package/src/blocks/event/event-details.tsx +8 -23
  80. package/src/blocks/event/event-listing-2.schema.ts +19 -38
  81. package/src/blocks/event/event-listing-2.tsx +12 -23
  82. package/src/blocks/event/event-listing-3.schema.ts +10 -20
  83. package/src/blocks/event/event-listing-3.tsx +9 -15
  84. package/src/blocks/event/event-listing-4.schema.ts +40 -80
  85. package/src/blocks/event/event-listing-4.tsx +5 -13
  86. package/src/blocks/event/event-listing.schema.ts +12 -24
  87. package/src/blocks/event/event-listing.tsx +9 -15
  88. package/src/blocks/faq/faq-1.schema.ts +8 -16
  89. package/src/blocks/faq/faq-1.tsx +19 -30
  90. package/src/blocks/faq/faq-2.schema.ts +8 -16
  91. package/src/blocks/faq/faq-2.tsx +11 -10
  92. package/src/blocks/faq/faq-3.schema.ts +9 -18
  93. package/src/blocks/faq/faq-3.tsx +11 -19
  94. package/src/blocks/features/features-1/features-1.tsx +2 -4
  95. package/src/blocks/features/features-1/variants/default.ts +4 -8
  96. package/src/blocks/features/features-1/variants/index.ts +1 -1
  97. package/src/blocks/features/features-2.schema.ts +32 -64
  98. package/src/blocks/features/features-2.tsx +8 -10
  99. package/src/blocks/features/features-3.schema.ts +32 -64
  100. package/src/blocks/features/features-3.tsx +7 -16
  101. package/src/blocks/features/features-4.schema.ts +16 -32
  102. package/src/blocks/features/features-4.tsx +3 -7
  103. package/src/blocks/features/features-5.schema.ts +1 -2
  104. package/src/blocks/features/features-5.tsx +6 -17
  105. package/src/blocks/features/features-6.schema.ts +32 -64
  106. package/src/blocks/features/features-6.tsx +16 -36
  107. package/src/blocks/filter-listing/filter-listing-1.tsx +12 -36
  108. package/src/blocks/floating-buttons/floating-button-1.schema.ts +5 -10
  109. package/src/blocks/floating-buttons/floating-button-1.tsx +3 -9
  110. package/src/blocks/footer/footer-1.schema.ts +8 -16
  111. package/src/blocks/footer/footer-1.tsx +15 -30
  112. package/src/blocks/footer/footer-2.schema.ts +8 -16
  113. package/src/blocks/footer/footer-2.tsx +17 -28
  114. package/src/blocks/footer/footer-3.schema.ts +1 -2
  115. package/src/blocks/footer/footer-3.tsx +9 -25
  116. package/src/blocks/footer/footer-4.schema.ts +8 -16
  117. package/src/blocks/footer/footer-4.tsx +35 -54
  118. package/src/blocks/footer/footer-5.schema.ts +10 -24
  119. package/src/blocks/footer/footer-5.tsx +14 -29
  120. package/src/blocks/footer/footer-6.schema.ts +10 -25
  121. package/src/blocks/footer/footer-6.tsx +29 -44
  122. package/src/blocks/form/form-1.schema.ts +144 -0
  123. package/src/blocks/form/form-1.tsx +33 -0
  124. package/src/blocks/hero/hero-1.schema.ts +45 -90
  125. package/src/blocks/hero/hero-1.tsx +4 -4
  126. package/src/blocks/hero/hero-10.schema.ts +1 -2
  127. package/src/blocks/hero/hero-10.tsx +4 -4
  128. package/src/blocks/hero/hero-11.schema.ts +18 -14
  129. package/src/blocks/hero/hero-11.tsx +4 -9
  130. package/src/blocks/hero/hero-12.schema.ts +18 -36
  131. package/src/blocks/hero/hero-12.tsx +3 -6
  132. package/src/blocks/hero/hero-13.schema.ts +8 -16
  133. package/src/blocks/hero/hero-13.tsx +1 -1
  134. package/src/blocks/hero/hero-2.schema.ts +3 -6
  135. package/src/blocks/hero/hero-2.tsx +3 -21
  136. package/src/blocks/hero/hero-3.schema.ts +28 -56
  137. package/src/blocks/hero/hero-3.tsx +6 -15
  138. package/src/blocks/hero/hero-4.schema.ts +22 -44
  139. package/src/blocks/hero/hero-4.tsx +16 -27
  140. package/src/blocks/hero/hero-5.schema.ts +21 -42
  141. package/src/blocks/hero/hero-5.tsx +15 -18
  142. package/src/blocks/hero/hero-6.schema.ts +35 -70
  143. package/src/blocks/hero/hero-6.tsx +6 -6
  144. package/src/blocks/hero/hero-7.schema.ts +24 -48
  145. package/src/blocks/hero/hero-7.tsx +2 -3
  146. package/src/blocks/hero/hero-8.schema.ts +24 -50
  147. package/src/blocks/hero/hero-8.tsx +5 -19
  148. package/src/blocks/hero/hero-9.schema.ts +8 -16
  149. package/src/blocks/hero/hero-9.tsx +3 -3
  150. package/src/blocks/index.ts +8 -11
  151. package/src/blocks/jobs/jobs-1.schema.ts +990 -0
  152. package/src/blocks/jobs/jobs-1.tsx +145 -0
  153. package/src/blocks/logos/logos-1.schema.ts +53 -113
  154. package/src/blocks/logos/logos-1.tsx +7 -7
  155. package/src/blocks/navbar/navbar-1.schema.ts +15 -28
  156. package/src/blocks/navbar/navbar-1.tsx +24 -60
  157. package/src/blocks/navbar/navbar-2.schema.ts +14 -28
  158. package/src/blocks/navbar/navbar-2.tsx +26 -56
  159. package/src/blocks/navbar/navbar-3.schema.ts +11 -22
  160. package/src/blocks/navbar/navbar-3.tsx +33 -82
  161. package/src/blocks/navbar/navbar-4.schema.ts +14343 -0
  162. package/src/blocks/navbar/navbar-4.tsx +546 -0
  163. package/src/blocks/polling/polling-1.schema.ts +1 -2
  164. package/src/blocks/polling/polling-1.tsx +24 -39
  165. package/src/blocks/pricing/pricing-1.schema.ts +13 -26
  166. package/src/blocks/pricing/pricing-1.tsx +20 -44
  167. package/src/blocks/products/products-listing-1.schema.ts +32 -64
  168. package/src/blocks/products/products-listing-1.tsx +6 -12
  169. package/src/blocks/products/products-listing-2.schema.ts +26 -54
  170. package/src/blocks/products/products-listing-2.tsx +8 -17
  171. package/src/blocks/products/products-listing-3.schema.ts +25 -52
  172. package/src/blocks/products/products-listing-3.tsx +87 -93
  173. package/src/blocks/profile/profile-1.schema.ts +7 -14
  174. package/src/blocks/profile/profile-1.tsx +2 -8
  175. package/src/blocks/profile/profile-2.schema.ts +9 -18
  176. package/src/blocks/profile/profile-2.tsx +4 -6
  177. package/src/blocks/profile/profile-3.schema.ts +21 -42
  178. package/src/blocks/profile/profile-3.tsx +18 -40
  179. package/src/blocks/{blogs/blog-post-2.schema.ts → profile/profile-4.schema.ts} +75 -97
  180. package/src/blocks/{blogs/blog-post-2.tsx → profile/profile-4.tsx} +32 -61
  181. package/src/blocks/{blogs/blog-post-3.schema.ts → profile/profile-5.schema.ts} +6 -6
  182. package/src/blocks/{blogs/blog-post-3.tsx → profile/profile-5.tsx} +6 -11
  183. package/src/blocks/services/service-1.schema.ts +20 -40
  184. package/src/blocks/services/service-1.tsx +45 -71
  185. package/src/blocks/services/service-2.schema.ts +8 -16
  186. package/src/blocks/services/service-2.tsx +4 -6
  187. package/src/blocks/services/service-3.schema.ts +12 -24
  188. package/src/blocks/services/service-3.tsx +4 -6
  189. package/src/blocks/stats/stats-1.tsx +3 -14
  190. package/src/blocks/stats/stats-2.tsx +3 -14
  191. package/src/blocks/stats/variants/index.ts +1 -1
  192. package/src/blocks/tabs/tab-1.schema.ts +4 -8
  193. package/src/blocks/tabs/tab-1.tsx +47 -64
  194. package/src/blocks/tabs/tab-2.schema.ts +13 -26
  195. package/src/blocks/tabs/tab-2.tsx +14 -15
  196. package/src/blocks/team/team-1.schema.ts +28 -56
  197. package/src/blocks/team/team-1.tsx +7 -21
  198. package/src/blocks/team/team-2.schema.ts +104 -208
  199. package/src/blocks/team/team-2.tsx +15 -33
  200. package/src/blocks/team/team-3.schema.ts +24 -48
  201. package/src/blocks/team/team-3.tsx +8 -14
  202. package/src/blocks/team/team-4.schema.ts +25 -50
  203. package/src/blocks/team/team-4.tsx +15 -24
  204. package/src/blocks/team/team-5.schema.ts +14 -28
  205. package/src/blocks/team/team-5.tsx +1 -1
  206. package/src/blocks/termsAndPrivacy/terms-and-conditions-1.tsx +3 -5
  207. package/src/blocks/testimonial/testimonial-1.schema.ts +1 -2
  208. package/src/blocks/testimonial/testimonial-1.tsx +10 -19
  209. package/src/blocks/testimonial/testimonial-2.schema.ts +16 -32
  210. package/src/blocks/testimonial/testimonial-2.tsx +17 -36
  211. package/src/blocks/testimonial/testimonial-3.schema.ts +8 -16
  212. package/src/blocks/testimonial/testimonial-3.tsx +13 -26
  213. package/src/blocks/testimonial/testimonial-4.schema.ts +94 -188
  214. package/src/blocks/testimonial/testimonial-4.tsx +12 -33
  215. package/src/blocks/testimonial/testimonial-5/variants/default.ts +24 -48
  216. package/src/blocks/testimonial/testimonial-5/variants/index.ts +1 -1
  217. package/src/blocks/testimonial/testimonial-5/variants/testimonialWithLogo.ts +4 -8
  218. package/src/blocks/testimonial/testimonial-5/variants/testimonialWithRatings.ts +24 -48
  219. package/src/blocks/testimonial/testimonial-5.schema.ts +2 -6
  220. package/src/blocks/testimonial/testimonial-5.tsx +15 -25
  221. package/src/blocks/testimonial/testimonial-6.schema.ts +26 -52
  222. package/src/blocks/testimonial/testimonial-6.tsx +9 -25
  223. package/src/blocks/testimonial/testimonial-7.tsx +15 -26
  224. package/src/blocks/timeline/timeline-1.schema.ts +34 -68
  225. package/src/blocks/timeline/timeline-1.tsx +2 -6
  226. package/src/blocks/users/edit-profile.tsx +10 -22
  227. package/src/constants/index.ts +3 -6
  228. package/src/index.ts +1 -1
  229. package/src/schemas/blocks.ts +14 -8
  230. package/src/schemas/categories.ts +14 -2
@@ -1,449 +1,36 @@
1
+ import type {
2
+ IForum,
3
+ IForumJoinRequest,
4
+ IForumMembershipStatus,
5
+ IForumPost,
6
+ IResponseMultiple,
7
+ IResponseSingle,
8
+ } from "@levo-so/core";
9
+ import { getLevoError } from "@levo-so/core";
10
+ import { useLevo } from "@levo-so/react";
1
11
  import {
2
- type ILevoBlockBaseProps,
3
- Heading,
4
- Typography,
5
- Container,
6
- Section,
7
12
  Box,
8
- ForumPost,
9
13
  Button,
10
- Skeleton,
11
- Image,
14
+ CategoryFilter,
15
+ Container,
16
+ CreateForumPost,
17
+ ForumPost,
18
+ ForumProvider,
19
+ Heading,
20
+ type ILevoBlockBaseProps,
12
21
  Media,
22
+ PostsRestrictionOverlay,
23
+ SamplePostSkeleton,
24
+ SearchInput,
25
+ Section,
26
+ Skeleton,
27
+ Typography,
28
+ useAuth,
13
29
  } from "@levo-so/studio";
14
30
  import { useCallback, useEffect, useRef, useState } from "react";
15
- import { useLevo } from "@levo-so/react";
16
- import type {
17
- IForumPost,
18
- ILevoMembership,
19
- IResponseMultiple,
20
- } from "@levo-so/core";
21
31
 
22
32
  import type { ICommunity1Content } from "./community-1.schema";
23
33
 
24
- const LINK_METADATA = {
25
- url: "https://example.com/renewable-energy-breakthrough",
26
- title: "New Battery Technology Increases Storage Capacity by 300%",
27
- description:
28
- "Researchers at MIT have developed a revolutionary battery technology that could transform renewable energy storage, making solar and wind power more viable for 24/7 use.",
29
- image: "https://picsum.photos/800/400?random=4",
30
- domain: "example.com",
31
- };
32
-
33
- const TEST_USER: ILevoMembership.Account = {
34
- _id: "USER_ID",
35
- first_name: "Jane",
36
- last_name: "Doe",
37
- slug: "jane-doe",
38
- username: "janedoeofficial",
39
- email: "jane@example.com",
40
- display_email: "jane@example.com",
41
- reducted_email: "j***@e****.com",
42
- email_verified: true,
43
- email_verified_at: new Date().toISOString(),
44
- verified_at: new Date().toISOString(),
45
- mobile: null,
46
- reducted_mobile: null,
47
- mobile_verified: false,
48
- mobile_verified_at: null,
49
- profile_picture: {
50
- id: "OaJDFLqx2UQyN",
51
- kind: "image",
52
- location: "https://github.com/shadcn.png",
53
- srcset: {
54
- "1080w": "https://github.com/shadcn.png",
55
- "1200w": "https://github.com/shadcn.png",
56
- "1920w": "https://github.com/shadcn.png",
57
- "320w": "https://github.com/shadcn.png",
58
- "640w": "https://github.com/shadcn.png",
59
- "750w": "https://github.com/shadcn.png",
60
- "828w": "https://github.com/shadcn.png",
61
- path: "https://github.com/shadcn.png",
62
- },
63
- },
64
- oauth_credentials: [],
65
- account_locked_at: null,
66
- credentials_expired_at: null,
67
- deactivated_at: null,
68
- last_access_at: new Date().toISOString(),
69
- flagged_at: null,
70
- created_at: new Date().toISOString(),
71
- updated_at: new Date().toISOString(),
72
- deleted_at: null,
73
- };
74
-
75
- const SAMPLE_FORUM_POSTS: IResponseMultiple<IForumPost> = {
76
- content: {
77
- data: [
78
- {
79
- _id: "7403047340596025651",
80
- comments_count: 0,
81
- content:
82
- "<p>Hey, This is a content post. <br><br>Good Luck with the rest.</p>",
83
- created_at: "2025-12-06T12:27:07.966Z",
84
- created_by: TEST_USER,
85
- expires_at: null,
86
- kind: "post",
87
- upvote_count: 50,
88
- downvote_count: 10,
89
- upvoted_at: null,
90
- downvoted_at: "2025-12-06T12:28:33.774Z",
91
- media: [],
92
- metadata: null,
93
- published_at: "2025-12-06T12:27:07.966Z",
94
- show_participant_list: false,
95
- show_participant_vote: true,
96
- slug: "ui-content-post",
97
- url: "",
98
- title: "UI: Content Post",
99
- updated_at: "2025-12-06T12:27:07.966Z",
100
- deleted_at: null,
101
- deleted_by: null,
102
- vote_count: null,
103
- options: [],
104
- },
105
- {
106
- _id: "7403047700499286828",
107
- comments_count: 0,
108
- content:
109
- "<p>This is a gallery post.<br><br>Good Luck with the rest.</p>",
110
- created_at: "2025-12-06T12:28:33.774Z",
111
- created_by: null,
112
- expires_at: null,
113
- kind: "gallery",
114
- upvoted_at: "2025-12-06T12:28:33.774Z",
115
- downvoted_at: null,
116
- upvote_count: 23,
117
- downvote_count: 66,
118
- media: [
119
- {
120
- id: "uY2rOYbtH2WKZ",
121
- location:
122
- "https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP.jpg",
123
- mimetype: "image/jpeg",
124
- kind: "image",
125
- srcset: {
126
- "1080w":
127
- "https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-1080w.webp",
128
- "1200w":
129
- "https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-1200w.webp",
130
- "1920w":
131
- "https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-1920w.webp",
132
- "320w":
133
- "https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-320w.webp",
134
- "640w":
135
- "https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-640w.webp",
136
- "750w":
137
- "https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-750w.webp",
138
- "828w":
139
- "https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-828w.webp",
140
- path: "https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-1080w.webp 1080w, https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-1200w.webp 1200w, https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-1920w.webp 1920w, https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-320w.webp 320w, https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-640w.webp 640w, https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-750w.webp 750w, https://static.levostg.online/WZZKMUBH/wp2845973-itachi-anbu-wallpaper-CISpLrr9FxcTP-828w.webp 828w",
141
- },
142
- },
143
- {
144
- id: "lDig3GwIc8cuX",
145
- location:
146
- "https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr.jpg",
147
- mimetype: "image/jpeg",
148
- kind: "image",
149
- srcset: {
150
- "1080w":
151
- "https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-1080w.webp",
152
- "1200w":
153
- "https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-1200w.webp",
154
- "1920w":
155
- "https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-1920w.webp",
156
- "320w":
157
- "https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-320w.webp",
158
- "640w":
159
- "https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-640w.webp",
160
- "750w":
161
- "https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-750w.webp",
162
- "828w":
163
- "https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-828w.webp",
164
- path: "https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-320w.webp 320w, https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-1080w.webp 1080w, https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-750w.webp 750w, https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-640w.webp 640w, https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-828w.webp 828w, https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-1200w.webp 1200w, https://static.levostg.online/WZZKMUBH/7-Must-Have-Features-for-a-Food-Beverage-ERP-system-Softype-Inc-Netsuite-Alliance-Partner-moxx13I6bnlIr-1920w.webp 1920w",
165
- },
166
- },
167
- {
168
- id: "MqnM9ygQVs3aI",
169
- location:
170
- "https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3.jpg",
171
- mimetype: "image/jpeg",
172
- kind: "image",
173
- srcset: {
174
- "1080w":
175
- "https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-1080w.webp",
176
- "1200w":
177
- "https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-1200w.webp",
178
- "1920w":
179
- "https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-1920w.webp",
180
- "320w":
181
- "https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-320w.webp",
182
- "640w":
183
- "https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-640w.webp",
184
- "750w":
185
- "https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-750w.webp",
186
- "828w":
187
- "https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-828w.webp",
188
- path: "https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-320w.webp 320w, https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-640w.webp 640w, https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-750w.webp 750w, https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-828w.webp 828w, https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-1080w.webp 1080w, https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-1200w.webp 1200w, https://static.levostg.online/WZZKMUBH/The-Future-of-Retail-Covid-19-Softype-Inc-Blog-Update-xloAzCSXADxN3-1920w.webp 1920w",
189
- },
190
- },
191
- {
192
- id: "xloJuCSXADUpL",
193
- location:
194
- "https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm.jpg",
195
- mimetype: "image/jpeg",
196
- kind: "image",
197
- srcset: {
198
- "1080w":
199
- "https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-1080w.webp",
200
- "1200w":
201
- "https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-1200w.webp",
202
- "1920w":
203
- "https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-1920w.webp",
204
- "320w":
205
- "https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-320w.webp",
206
- "640w":
207
- "https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-640w.webp",
208
- "750w":
209
- "https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-750w.webp",
210
- "828w":
211
- "https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-828w.webp",
212
- path: "https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-1920w.webp 1920w, https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-320w.webp 320w, https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-828w.webp 828w, https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-750w.webp 750w, https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-640w.webp 640w, https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-1200w.webp 1200w, https://static.levostg.online/WZZKMUBH/3-tips-for-successful-Accounts-Payable-Management-Softype-INC-4JOrxlk9pfgLm-1080w.webp 1080w",
213
- },
214
- },
215
- {
216
- id: "4JO75lk9pfVoz",
217
- location:
218
- "https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk.jpg",
219
- mimetype: "image/jpeg",
220
- kind: "image",
221
- srcset: {
222
- "1080w":
223
- "https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-1080w.webp",
224
- "1200w":
225
- "https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-1200w.webp",
226
- "1920w":
227
- "https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-1920w.webp",
228
- "320w":
229
- "https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-320w.webp",
230
- "640w":
231
- "https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-640w.webp",
232
- "750w":
233
- "https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-750w.webp",
234
- "828w":
235
- "https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-828w.webp",
236
- path: "https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-1200w.webp 1200w, https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-1920w.webp 1920w, https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-320w.webp 320w, https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-640w.webp 640w, https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-750w.webp 750w, https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-1080w.webp 1080w, https://static.levostg.online/WZZKMUBH/shutterstock572315812-xloAsCSXADxFk-828w.webp 828w",
237
- },
238
- },
239
- {
240
- id: "1d74SLgA5aCsp",
241
- location:
242
- "https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D.jpg",
243
- mimetype: "image/jpeg",
244
- kind: "image",
245
- srcset: {
246
- "1080w":
247
- "https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-1080w.webp",
248
- "1200w":
249
- "https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-1200w.webp",
250
- "1920w":
251
- "https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-1920w.webp",
252
- "320w":
253
- "https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-320w.webp",
254
- "640w":
255
- "https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-640w.webp",
256
- "750w":
257
- "https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-750w.webp",
258
- "828w":
259
- "https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-828w.webp",
260
- path: "https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-1920w.webp 1920w, https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-320w.webp 320w, https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-750w.webp 750w, https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-640w.webp 640w, https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-828w.webp 828w, https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-1080w.webp 1080w, https://static.levostg.online/WZZKMUBH/7-reasons-NetSuite-is-Worlds-1-Cloud-Financial-Accounting-Software-Softype-Inc-74dEtDIGEyR4D-1200w.webp 1200w",
261
- },
262
- },
263
- {
264
- id: "GLUJwcipcQ1DJ",
265
- location:
266
- "https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX.jpg",
267
- mimetype: "image/jpeg",
268
- kind: "image",
269
- srcset: {
270
- "1080w":
271
- "https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-1080w.webp",
272
- "1200w":
273
- "https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-1200w.webp",
274
- "1920w":
275
- "https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-1920w.webp",
276
- "320w":
277
- "https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-320w.webp",
278
- "640w":
279
- "https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-640w.webp",
280
- "750w":
281
- "https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-750w.webp",
282
- "828w":
283
- "https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-828w.webp",
284
- path: "https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-320w.webp 320w, https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-640w.webp 640w, https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-828w.webp 828w, https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-750w.webp 750w, https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-1080w.webp 1080w, https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-1200w.webp 1200w, https://static.levostg.online/WZZKMUBH/Building-an-Effective-and-Efficient-Sales-Workforce-in-the-New-Normal-Softype-Inc-XZbIObcKFJZWX-1920w.webp 1920w",
285
- },
286
- },
287
- {
288
- id: "A8sdO1BeFXg2Q",
289
- location:
290
- "https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz.jpg",
291
- mimetype: "image/jpeg",
292
- kind: "image",
293
- srcset: {
294
- "1080w":
295
- "https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-1080w.webp",
296
- "1200w":
297
- "https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-1200w.webp",
298
- "1920w":
299
- "https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-1920w.webp",
300
- "320w":
301
- "https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-320w.webp",
302
- "640w":
303
- "https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-640w.webp",
304
- "750w":
305
- "https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-750w.webp",
306
- "828w":
307
- "https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-828w.webp",
308
- path: "https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-828w.webp 828w, https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-1080w.webp 1080w, https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-1200w.webp 1200w, https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-1920w.webp 1920w, https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-320w.webp 320w, https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-750w.webp 750w, https://static.levostg.online/WZZKMUBH/Learning-to-Go-Live-Lessons-From-The-Pandemic-Trenches-Softype-INC-30WLnpdHjSoUz-640w.webp 640w",
309
- },
310
- },
311
- ],
312
- metadata: null,
313
- published_at: "2025-12-06T12:28:33.774Z",
314
- show_participant_list: false,
315
- show_participant_vote: true,
316
- slug: "ui-gallery-post",
317
- url: "",
318
- title: "UI: Gallery Post",
319
- updated_at: "2025-12-06T12:28:33.774Z",
320
- deleted_at: null,
321
- deleted_by: null,
322
- vote_count: null,
323
- options: [],
324
- },
325
- {
326
- _id: "7403048140069180235",
327
- comments_count: 0,
328
- content:
329
- "<p>Hey, This is a Poll Post.<br><br>Good Luck with the rest.</p>",
330
- created_at: "2025-12-06T12:30:18.575Z",
331
- created_by: TEST_USER,
332
- expires_at: "2026-12-31T12:30:00.000Z",
333
- kind: "poll",
334
- upvoted_at: "2025-12-06T12:28:33.774Z",
335
- downvoted_at: null,
336
- upvote_count: 100,
337
- downvote_count: 1,
338
- media: [],
339
- metadata: null,
340
- published_at: "2025-12-06T12:30:18.575Z",
341
- show_participant_list: true,
342
- show_participant_vote: true,
343
- slug: "ui-this-is-a-poll",
344
- url: "",
345
- title: "UI: Poll Post",
346
- updated_at: "2025-12-06T12:30:18.575Z",
347
- deleted_at: null,
348
- deleted_by: null,
349
- vote_count: 16,
350
- user_voted_option: "7403048138708901408",
351
- options: [
352
- {
353
- _id: "7403048138708668754",
354
- label: "Poll Option 1",
355
- count: 4,
356
- },
357
- {
358
- _id: "7403048138710723182",
359
- label: "Poll Option 2",
360
- count: 8,
361
- },
362
- {
363
- _id: "7403048138708304207",
364
- label: "Poll Option 3",
365
- count: 3,
366
- },
367
- {
368
- _id: "7403048138708901408",
369
- label: "Poll Option 4",
370
- count: 1,
371
- },
372
- ],
373
- },
374
- {
375
- _id: "7403048494051511085",
376
- comments_count: 0,
377
- content:
378
- "<p>Hey, This is a link post.<br><br>Good luck with the rest.</p>",
379
- created_at: "2025-12-06T12:31:42.971Z",
380
- created_by: TEST_USER,
381
- expires_at: null,
382
- kind: "link",
383
- upvoted_at: null,
384
- downvoted_at: "2025-12-06T12:28:33.774Z",
385
- upvote_count: 60,
386
- downvote_count: 10,
387
- media: [],
388
- metadata: null,
389
- published_at: "2025-12-06T12:31:42.971Z",
390
- show_participant_list: false,
391
- show_participant_vote: true,
392
- slug: "ui-link-post",
393
- url: "https://www.youtube.com/",
394
- title: "UI: Link Post",
395
- updated_at: "2025-12-06T12:31:42.971Z",
396
- deleted_at: null,
397
- deleted_by: null,
398
- vote_count: null,
399
- options: [],
400
- },
401
- {
402
- _id: "7403048494051511085",
403
- comments_count: 0,
404
- content:
405
- "<p>Hey, This is a link post.<br><br>Good luck with the rest.</p>",
406
- created_at: "2025-12-06T12:31:42.971Z",
407
- created_by: TEST_USER,
408
- expires_at: null,
409
- kind: "link",
410
- upvote_count: 50,
411
- downvote_count: 97,
412
- upvoted_at: null,
413
- downvoted_at: "2025-12-06T12:28:33.774Z",
414
- media: [],
415
- metadata: {
416
- meta_title: LINK_METADATA?.title,
417
- meta_description: LINK_METADATA?.description,
418
- og_image: {
419
- id: "uY2rOYbtH2WKZ",
420
- location: LINK_METADATA?.image,
421
- mimetype: "image/jpeg",
422
- kind: "image",
423
- },
424
- },
425
- published_at: "2025-12-06T12:31:42.971Z",
426
- show_participant_list: false,
427
- show_participant_vote: true,
428
- slug: "ui-link-post-2",
429
- url: LINK_METADATA?.url,
430
- title: "UI: Link Post 2",
431
- updated_at: "2025-12-06T12:31:42.971Z",
432
- deleted_at: null,
433
- deleted_by: null,
434
- vote_count: null,
435
- options: [],
436
- },
437
- ] as any,
438
-
439
- meta: {
440
- pages: 1,
441
- total: 2,
442
- },
443
- },
444
- status: true,
445
- };
446
-
447
34
  const PostSkeleton: React.FC = () => {
448
35
  return (
449
36
  <div className="flex flex-col gap-3 rounded-lg border border-[#E5E5E5] bg-white p-3">
@@ -476,13 +63,21 @@ const PostSkeleton: React.FC = () => {
476
63
  );
477
64
  };
478
65
 
66
+ /** Map API error codes to user-friendly messages */
67
+ const JOIN_REQUEST_ERROR_MESSAGES: { [key: number]: string } = {
68
+ 12111: "This forum doesn't require join requests.",
69
+ 12112: "Join requests are not enabled for this forum.",
70
+ 12113: "You are already a member of this forum.",
71
+ 12114: "You have already submitted a join request.",
72
+ };
73
+
479
74
  const MAX_RETRY_ATTEMPTS = 3;
480
75
 
481
- const Community1: React.FC<ILevoBlockBaseProps<ICommunity1Content>> = ({
482
- content,
483
- }) => {
76
+ const Community1: React.FC<ILevoBlockBaseProps<ICommunity1Content>> = ({ content }) => {
484
77
  const levoClient = useLevo();
78
+ const { isLoggedIn } = useAuth();
485
79
  const observerTarget = useRef<HTMLDivElement>(null);
80
+ const searchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
486
81
 
487
82
  const [isLoading, setIsLoading] = useState(true);
488
83
  const [isFetchingMore, setIsFetchingMore] = useState(false);
@@ -491,6 +86,107 @@ const Community1: React.FC<ILevoBlockBaseProps<ICommunity1Content>> = ({
491
86
  const [hasMore, setHasMore] = useState(true);
492
87
  const [failedAttempts, setFailedAttempts] = useState(0);
493
88
  const [hasError, setHasError] = useState(false);
89
+ const [forumData, setForumData] = useState<IForum | null>(null);
90
+ const [isMember, setIsMember] = useState<boolean>(false);
91
+ const [isLoadingMembership, setIsLoadingMembership] = useState(false);
92
+ const [searchQuery, setSearchQuery] = useState("");
93
+ const [isSearching, setIsSearching] = useState(false);
94
+ const hasFetchedMembershipRef = useRef(false);
95
+
96
+ const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
97
+
98
+ // Track initial mount to prevent duplicate fetches
99
+ const isInitialMountRef = useRef(true);
100
+
101
+ // Join request state
102
+ const [joinRequest, setJoinRequest] = useState<IForumJoinRequest | null>(null);
103
+
104
+ const forumKey = content?.forum_key as unknown as string;
105
+
106
+ useEffect(() => {
107
+ return () => {
108
+ if (searchTimeoutRef.current) {
109
+ clearTimeout(searchTimeoutRef.current);
110
+ }
111
+ };
112
+ }, []);
113
+
114
+ const fetchSingleForumData = useCallback(async () => {
115
+ try {
116
+ const response = await levoClient.fetch
117
+ .url(`/v1/community/${forumKey}/get-single`)
118
+ .get()
119
+ .json<IResponseSingle<IForum>>();
120
+
121
+ if (response?.content?.data) {
122
+ setForumData(response.content.data);
123
+ }
124
+
125
+ return response;
126
+ } catch (error) {
127
+ levoClient?.logger?.error("Error fetching single forum data:", error);
128
+ return null;
129
+ }
130
+ }, [forumKey]);
131
+
132
+ const checkMembership = useCallback(async () => {
133
+ if (!forumData?.settings?.invite_only || !isLoggedIn) {
134
+ setIsMember(false);
135
+ setJoinRequest(null);
136
+ hasFetchedMembershipRef.current = true;
137
+ return;
138
+ }
139
+
140
+ setIsLoadingMembership(true);
141
+
142
+ try {
143
+ const response = await levoClient.fetch
144
+ .url(`/v1/community/${forumKey}/member/get-me`)
145
+ .get()
146
+ .json<IResponseSingle<IForumMembershipStatus>>();
147
+
148
+ const data = response?.content?.data;
149
+ setIsMember(!!data?.member);
150
+ setJoinRequest(data?.join_request || null);
151
+ } catch (error) {
152
+ const levoError = getLevoError(error);
153
+ levoClient?.logger?.error("Error checking membership:", levoError);
154
+ setIsMember(false);
155
+ setJoinRequest(null);
156
+ } finally {
157
+ setIsLoadingMembership(false);
158
+ hasFetchedMembershipRef.current = true;
159
+ }
160
+ }, [forumKey, forumData?.settings?.invite_only, isLoggedIn, levoClient]);
161
+
162
+ /**
163
+ * Submit a join request for the current user
164
+ */
165
+ const handleSubmitJoinRequest = useCallback(
166
+ async (message?: string): Promise<boolean> => {
167
+ try {
168
+ const response = await levoClient.fetch
169
+ .url(`/v1/community/${forumKey}/join-request/submit`)
170
+ .post({ message })
171
+ .json<IResponseSingle<IForumJoinRequest>>();
172
+
173
+ if (response?.content?.data) {
174
+ setJoinRequest(response.content.data);
175
+ return true;
176
+ }
177
+ return false;
178
+ } catch (error) {
179
+ const levoError = getLevoError(error);
180
+ levoClient?.logger?.error("Failed to submit join request", levoError);
181
+
182
+ // Throw with user-friendly message
183
+ const errorMessage =
184
+ JOIN_REQUEST_ERROR_MESSAGES[levoError.code as any] || levoError.message;
185
+ throw new Error(errorMessage);
186
+ }
187
+ },
188
+ [forumKey, levoClient],
189
+ );
494
190
 
495
191
  const fetchPolls = useCallback(
496
192
  async (pageNumber: number, append = false) => {
@@ -501,16 +197,28 @@ const Community1: React.FC<ILevoBlockBaseProps<ICommunity1Content>> = ({
501
197
  }
502
198
 
503
199
  try {
504
- const response = await levoClient.fetch
505
- .url("/v1/community/forum/posts/list")
506
- .post({
507
- limit: 10,
508
- page: pageNumber,
509
- sort: {
510
- created_at: "desc",
511
- },
512
- })
200
+ const requestBody: {
201
+ limit: number;
202
+ page: number;
203
+ sort: { created_at: "desc" };
204
+ where?: { categories: { in: string[] } };
205
+ } = {
206
+ limit: 10,
207
+ page: pageNumber,
208
+ sort: {
209
+ created_at: "desc",
210
+ },
211
+ };
513
212
 
213
+ if (selectedCategories.length > 0) {
214
+ requestBody.where = {
215
+ categories: { in: selectedCategories },
216
+ };
217
+ }
218
+
219
+ const response = await levoClient.fetch
220
+ .url(`/v1/community/${forumKey}/post/list`)
221
+ .post(requestBody)
514
222
  .json<IResponseMultiple<IForumPost>>();
515
223
 
516
224
  if (response && response?.content?.data) {
@@ -536,13 +244,133 @@ const Community1: React.FC<ILevoBlockBaseProps<ICommunity1Content>> = ({
536
244
  setIsFetchingMore(false);
537
245
  }
538
246
  },
539
- [levoClient, failedAttempts],
247
+ [levoClient, forumKey, failedAttempts, selectedCategories],
540
248
  );
541
249
 
542
- useEffect(() => {
543
- fetchPolls(1, false);
250
+ const handleSearch = useCallback(
251
+ (query: string) => {
252
+ setSearchQuery(query);
253
+
254
+ if (searchTimeoutRef.current) {
255
+ clearTimeout(searchTimeoutRef.current);
256
+ }
257
+
258
+ if (!query.trim()) {
259
+ fetchPolls(1, false);
260
+ return;
261
+ }
262
+
263
+ searchTimeoutRef.current = setTimeout(async () => {
264
+ setIsSearching(true);
265
+ try {
266
+ const searchRequestBody: {
267
+ limit: number;
268
+ page: number;
269
+ sort: { created_at: "desc" };
270
+ search: string;
271
+ where?: { categories: { in: string[] } };
272
+ } = {
273
+ limit: 10,
274
+ page: 1,
275
+ sort: {
276
+ created_at: "desc",
277
+ },
278
+ search: query,
279
+ };
280
+
281
+ if (selectedCategories.length > 0) {
282
+ searchRequestBody.where = {
283
+ categories: { in: selectedCategories },
284
+ };
285
+ }
286
+
287
+ const response = await levoClient.fetch
288
+ .url(`/v1/community/${forumKey}/post/list`)
289
+ .post(searchRequestBody)
290
+ .json<IResponseMultiple<IForumPost>>();
291
+
292
+ if (response?.content?.data) {
293
+ setForumPosts(response.content.data);
294
+ setHasMore(false);
295
+ }
296
+ } catch (error) {
297
+ const levoError = getLevoError(error);
298
+ console.error("Search failed:", levoError);
299
+ } finally {
300
+ setIsSearching(false);
301
+ }
302
+ }, 500);
303
+ },
304
+ [forumKey, levoClient, fetchPolls, selectedCategories],
305
+ );
306
+
307
+ const handleCategoryToggle = useCallback((categoryId: string) => {
308
+ setSelectedCategories((prev) => {
309
+ const isSelected = prev.includes(categoryId);
310
+ return isSelected ? prev.filter((id) => id !== categoryId) : [...prev, categoryId];
311
+ });
312
+ }, []);
313
+
314
+ const handlePostCreated = useCallback((post: IForumPost) => {
315
+ setForumPosts((prev) => [post, ...prev]);
544
316
  }, []);
545
317
 
318
+ useEffect(() => {
319
+ fetchSingleForumData();
320
+ }, [fetchSingleForumData]);
321
+
322
+ useEffect(() => {
323
+ if (forumData) {
324
+ checkMembership();
325
+ }
326
+ }, [forumData, checkMembership]);
327
+
328
+ const isInviteOnly = forumData?.settings?.invite_only ?? false;
329
+ const allowJoinRequests = forumData?.settings?.allow_join_requests ?? false;
330
+
331
+ /** Whether membership check is still pending for invite-only forums */
332
+ const isMembershipCheckPending = isInviteOnly && !hasFetchedMembershipRef.current;
333
+
334
+ /**
335
+ * Check if posts should be restricted (show overlay):
336
+ * 1. For non-logged-in users: when invite_only=true
337
+ * 2. For logged-in non-members: when invite_only=true AND allow_public_view_for_post=false
338
+ * Note: Only evaluate after membership check is complete to avoid flash
339
+ */
340
+ const shouldRestrictPosts =
341
+ isInviteOnly &&
342
+ hasFetchedMembershipRef.current &&
343
+ !isLoadingMembership &&
344
+ (!isLoggedIn || (!isMember && !forumData?.settings?.allow_public_view_for_post));
345
+
346
+ /**
347
+ * Whether user can interact with posts (upvote/downvote):
348
+ * - Not invite-only: true (login modal will show if not logged in)
349
+ * - Invite-only + member: true
350
+ * - Invite-only + non-member: false (greyed out)
351
+ */
352
+ const canInteractWithPosts = !isInviteOnly || isMember;
353
+
354
+ useEffect(() => {
355
+ // Only fetch posts if we've checked membership and posts are not restricted
356
+ if (forumData && !isLoadingMembership && !shouldRestrictPosts) {
357
+ fetchPolls(1, false);
358
+ }
359
+ }, [forumData, isLoadingMembership, shouldRestrictPosts]);
360
+
361
+ // Re-fetch posts when category filter changes (skip initial mount)
362
+ useEffect(() => {
363
+ if (isInitialMountRef.current) {
364
+ isInitialMountRef.current = false;
365
+ return;
366
+ }
367
+
368
+ if (forumData && !isLoadingMembership && !shouldRestrictPosts) {
369
+ setPage(1);
370
+ fetchPolls(1, false);
371
+ }
372
+ }, [selectedCategories, forumData, isLoadingMembership, shouldRestrictPosts, fetchPolls]);
373
+
546
374
  useEffect(() => {
547
375
  const observer = new IntersectionObserver(
548
376
  (entries) => {
@@ -576,114 +404,153 @@ const Community1: React.FC<ILevoBlockBaseProps<ICommunity1Content>> = ({
576
404
  }, [hasMore, isLoading, isFetchingMore, hasError, page, fetchPolls]);
577
405
 
578
406
  return (
579
- <Section elementKey="layout">
580
- <Container elementKey="container">
581
- <Box elementKey="content_section">
582
- <Box elementKey="left_container">
583
- <Box elementKey="sidebar_links_levoGroup">
584
- {content?.sidebar_links?.map((v, index) => {
585
- return (
586
- <Button
587
- key={`sidebar_links.${index}.sidebar_link`}
588
- elementKey={`sidebar_links.${index}.sidebar_link`}
589
- />
590
- );
591
- })}
407
+ <ForumProvider
408
+ forum={forumData}
409
+ isMember={isMember}
410
+ isLoadingMembership={isLoadingMembership}
411
+ isInviteOnly={isInviteOnly}
412
+ canInteractWithPosts={canInteractWithPosts}
413
+ canInteractWithComments={canInteractWithPosts}
414
+ joinRequest={joinRequest}
415
+ isJoinRequestPending={!!joinRequest}
416
+ allowJoinRequests={allowJoinRequests}
417
+ submitJoinRequest={handleSubmitJoinRequest}
418
+ refreshMembershipStatus={checkMembership}
419
+ >
420
+ <Section elementKey="layout">
421
+ <Container elementKey="container">
422
+ <Box elementKey="content_section">
423
+ <Box elementKey="left_container">
424
+ <Box elementKey="sidebar_links_levoGroup">
425
+ {content?.sidebar_links?.map((v, index) => {
426
+ return (
427
+ <Button
428
+ key={`sidebar_links.${index}.sidebar_link`}
429
+ elementKey={`sidebar_links.${index}.sidebar_link`}
430
+ />
431
+ );
432
+ })}
433
+ </Box>
592
434
  </Box>
593
- </Box>
594
435
 
595
- <Box elementKey="feed_container">
596
- <Box elementKey="header">
597
- <Heading elementKey="title" />
598
- <Typography elementKey="description" />
599
- </Box>
600
- <Box elementKey="posts">
601
- {isLoading ? (
602
- <>
603
- <PostSkeleton />
604
- <PostSkeleton />
605
- <PostSkeleton />
606
- <PostSkeleton />
607
- </>
608
- ) : (
609
- <>
610
- {forumPosts?.map((item, index: number) => (
611
- <ForumPost
612
- key={`polls.${index}`}
613
- post={item}
614
- postDetailPageBaseURL={
615
- content?.post_detail_page_url || ""
616
- }
617
- />
618
- ))}
619
-
620
- {hasMore && !hasError && (
621
- <div ref={observerTarget} className="h-4" />
622
- )}
623
-
624
- {isFetchingMore && (
625
- <>
626
- <PostSkeleton />
627
- <PostSkeleton />
628
- </>
629
- )}
630
-
631
- {hasError && (
632
- <Box
633
- elementKey="error_container"
634
- style={{
635
- display: "flex",
636
- alignItems: "center",
637
- justifyContent: "center",
638
- minHeight: "16rem",
639
- }}
640
- >
641
- <Typography
642
- elementKey="error_text"
643
- className="text-sm font-medium text-red-600"
436
+ <Box elementKey="feed_container">
437
+ <Box elementKey="header">
438
+ <Heading elementKey="title" />
439
+ <Typography elementKey="description" />
440
+ </Box>
441
+ <Box elementKey="search_container">
442
+ <SearchInput
443
+ name="search"
444
+ placeholder="Search..."
445
+ value={searchQuery}
446
+ onChange={(e) => handleSearch(e.target.value)}
447
+ />
448
+ <Button elementKey="new_post_cta" />
449
+ </Box>
450
+
451
+ <CreateForumPost forumKey={forumKey} onPostCreated={handlePostCreated} />
452
+ <Box elementKey="posts">
453
+ {isMembershipCheckPending ||
454
+ isLoadingMembership ||
455
+ (isLoading && !shouldRestrictPosts) ||
456
+ isSearching ? (
457
+ <>
458
+ <PostSkeleton />
459
+ <PostSkeleton />
460
+ <PostSkeleton />
461
+ <PostSkeleton />
462
+ </>
463
+ ) : shouldRestrictPosts ? (
464
+ <>
465
+ {/* Show sample posts behind the overlay */}
466
+ <SamplePostSkeleton opacity={0.6} />
467
+ <SamplePostSkeleton opacity={0.4} />
468
+ <SamplePostSkeleton opacity={0.3} />
469
+ <PostsRestrictionOverlay isOpen communityName={forumData?.name} />
470
+ </>
471
+ ) : (
472
+ <>
473
+ {forumPosts?.map((item, index: number) => (
474
+ <ForumPost
475
+ key={`poll_${forumKey}_${item?._id}_${index}`}
476
+ post={item}
477
+ postDetailPageBaseURL={content?.post_detail_page_url || ""}
478
+ forumKey={forumKey}
479
+ />
480
+ ))}
481
+
482
+ {hasMore && !hasError && <div ref={observerTarget} className="h-4" />}
483
+
484
+ {isFetchingMore && (
485
+ <>
486
+ <PostSkeleton />
487
+ <PostSkeleton />
488
+ </>
489
+ )}
490
+
491
+ {hasError && (
492
+ <Box
493
+ elementKey="error_container"
494
+ style={{
495
+ display: "flex",
496
+ alignItems: "center",
497
+ justifyContent: "center",
498
+ minHeight: "16rem",
499
+ }}
644
500
  >
645
- Failed to load posts after {MAX_RETRY_ATTEMPTS}{" "}
646
- attempts. Please try refreshing the page.
647
- </Typography>
648
- </Box>
649
- )}
650
- </>
651
- )}
501
+ <Typography
502
+ elementKey="error_text"
503
+ className="font-medium text-red-600 text-sm"
504
+ >
505
+ Failed to load posts. Please try refreshing the page.
506
+ </Typography>
507
+ </Box>
508
+ )}
509
+ </>
510
+ )}
511
+ </Box>
652
512
  </Box>
653
- </Box>
654
- <Box elementKey="right_container">
655
- <Box elementKey="edit_profile_block">
656
- <Box elementKey="edit_profile_block_content_wrapper">
657
- <Media
658
- elementKey="edit_profile_block_banner_image"
659
- alt="Banner Image"
660
- />
661
- <Box elementKey="edit_profile_block_content">
662
- <Media
663
- elementKey="edit_profile_block_logo"
664
- alt="Community Logo"
513
+ <Box elementKey="right_container">
514
+ <Box elementKey="category_container">
515
+ <Box elementKey="category_header">
516
+ <Heading elementKey="category_title" />
517
+ <Typography elementKey="category_description" />
518
+ </Box>
519
+ <Box elementKey="category_list_container">
520
+ <CategoryFilter
521
+ forumKey={forumKey}
522
+ selectedCategories={selectedCategories}
523
+ onCategoryToggle={handleCategoryToggle}
665
524
  />
666
- <Box elementKey="edit_profile_block_text_container">
667
- <Heading elementKey="edit_profile_block_title" />
668
- <Typography elementKey="edit_profile_block_description" />
669
- </Box>
670
525
  </Box>
671
526
  </Box>
672
- <Box elementKey="edit_profile_ctas_levoGroup">
673
- {content?.edit_profile_ctas?.map((v, index) => {
674
- return (
675
- <Button
676
- key={`edit_profile_ctas.${index}.edit_profile_cta`}
677
- elementKey={`edit_profile_ctas.${index}.edit_profile_cta`}
678
- />
679
- );
680
- })}
527
+ <Box elementKey="edit_profile_block">
528
+ <Box elementKey="edit_profile_block_content_wrapper">
529
+ <Media elementKey="edit_profile_block_banner_image" alt="Banner Image" />
530
+ <Box elementKey="edit_profile_block_content">
531
+ <Media elementKey="edit_profile_block_logo" alt="Community Logo" />
532
+ <Box elementKey="edit_profile_block_text_container">
533
+ <Heading elementKey="edit_profile_block_title" />
534
+ <Typography elementKey="edit_profile_block_description" />
535
+ </Box>
536
+ </Box>
537
+ </Box>
538
+ <Box elementKey="edit_profile_ctas_levoGroup">
539
+ {content?.edit_profile_ctas?.map((v, index) => {
540
+ return (
541
+ <Button
542
+ key={`edit_profile_ctas.${index}.edit_profile_cta`}
543
+ elementKey={`edit_profile_ctas.${index}.edit_profile_cta`}
544
+ />
545
+ );
546
+ })}
547
+ </Box>
681
548
  </Box>
682
549
  </Box>
683
550
  </Box>
684
- </Box>
685
- </Container>
686
- </Section>
551
+ </Container>
552
+ </Section>
553
+ </ForumProvider>
687
554
  );
688
555
  };
689
556