@levo-so/blocks 0.1.60 → 0.1.62

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
@@ -4,6 +4,7 @@ const DEFAULT_CONTENT = {
4
4
  layout: null,
5
5
  container: null,
6
6
  post: "{context.forum}",
7
+ forum_key: "forum",
7
8
  sidebar_links_levoGroup: null,
8
9
  sidebar_links: [
9
10
  {
@@ -43,43 +44,28 @@ const DEFAULT_CONTENT = {
43
44
  kind: "image",
44
45
  location: "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq.png",
45
46
  srcset: {
46
- "1080w":
47
- "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-1080w.webp",
48
- "1200w":
49
- "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-1200w.webp",
50
- "1920w":
51
- "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-1920w.webp",
52
- "320w":
53
- "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-320w.webp",
54
- "640w":
55
- "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-640w.webp",
56
- "750w":
57
- "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-750w.webp",
58
- "828w":
59
- "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-828w.webp",
47
+ "1080w": "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-1080w.webp",
48
+ "1200w": "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-1200w.webp",
49
+ "1920w": "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-1920w.webp",
50
+ "320w": "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-320w.webp",
51
+ "640w": "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-640w.webp",
52
+ "750w": "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-750w.webp",
53
+ "828w": "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-828w.webp",
60
54
  path: "https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-320w.webp 320w, https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-640w.webp 640w, https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-750w.webp 750w, https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-828w.webp 828w, https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-1080w.webp 1080w, https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-1200w.webp 1200w, https://static.levostg.online/WZZKMUBH/banner-HXVr5tikleqvq-1920w.webp 1920w",
61
55
  },
62
56
  },
63
57
  edit_profile_block_logo: {
64
58
  id: "zucZ9wWTU6qEL",
65
59
  kind: "image",
66
- location:
67
- "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw.jpeg",
60
+ location: "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw.jpeg",
68
61
  srcset: {
69
- "1080w":
70
- "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-1080w.webp",
71
- "1200w":
72
- "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-1200w.webp",
73
- "1920w":
74
- "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-1920w.webp",
75
- "320w":
76
- "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-320w.webp",
77
- "640w":
78
- "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-640w.webp",
79
- "750w":
80
- "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-750w.webp",
81
- "828w":
82
- "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-828w.webp",
62
+ "1080w": "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-1080w.webp",
63
+ "1200w": "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-1200w.webp",
64
+ "1920w": "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-1920w.webp",
65
+ "320w": "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-320w.webp",
66
+ "640w": "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-640w.webp",
67
+ "750w": "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-750w.webp",
68
+ "828w": "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-828w.webp",
83
69
  path: "https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-1200w.webp 1200w, https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-1920w.webp 1920w, https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-320w.webp 320w, https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-640w.webp 640w, https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-750w.webp 750w, https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-828w.webp 828w, https://static.levostg.online/WZZKMUBH/netflix-FUGaJXflpOINw-1080w.webp 1080w",
84
70
  },
85
71
  },
@@ -105,6 +91,11 @@ export const CommunityPost1: IBlock = {
105
91
  label: "Container",
106
92
  field_interface: "LayoutWidget",
107
93
  },
94
+ {
95
+ key: "forum_key",
96
+ label: "Forum Key",
97
+ field_interface: "HeadingWidget",
98
+ },
108
99
  {
109
100
  key: "left_container",
110
101
  label: "Left Container",
@@ -1,47 +1,210 @@
1
1
  import {
2
- type ILevoBlockBaseProps,
3
- Container,
4
- Section,
2
+ getLevoError,
3
+ type IForum,
4
+ type IForumJoinRequest,
5
+ type IForumMember,
6
+ type IForumMembershipStatus,
7
+ type IForumPost,
8
+ type IResponseSingle,
9
+ } from "@levo-so/core";
10
+ import { useLevo } from "@levo-so/react";
11
+ import {
5
12
  Box,
6
- ForumPost,
7
- Comments,
8
13
  Button,
9
- Media,
14
+ Comments,
15
+ CommentsRestrictionOverlay,
16
+ Container,
17
+ ForumPost,
18
+ ForumProvider,
10
19
  Heading,
20
+ type ILevoBlockBaseProps,
21
+ Media,
22
+ PostsRestrictionOverlay,
23
+ SamplePostSkeleton,
24
+ Section,
25
+ Skeleton,
11
26
  Typography,
27
+ useAuth,
12
28
  } from "@levo-so/studio";
13
- import { Spinner, useLevo } from "@levo-so/react";
14
- import {
15
- type IForumPost,
16
- type IResponseSingle,
17
- getLevoError,
18
- } from "@levo-so/core";
19
- import { useEffect, useRef, useState } from "react";
29
+ import { useCallback, useEffect, useRef, useState } from "react";
20
30
 
21
31
  import type { ICommunityPost1Content } from "./community-post-1.schema";
22
32
 
23
- const CommunityPost1: React.FC<ILevoBlockBaseProps<ICommunityPost1Content>> = ({
24
- content,
25
- }) => {
33
+ /** Map API error codes to user-friendly messages */
34
+ const JOIN_REQUEST_ERROR_MESSAGES: { [key: number]: string } = {
35
+ 12111: "This forum doesn't require join requests.",
36
+ 12112: "Join requests are not enabled for this forum.",
37
+ 12113: "You are already a member of this forum.",
38
+ 12114: "You have already submitted a join request.",
39
+ };
40
+
41
+ /** Skeleton for post detail page loading state */
42
+ const PostDetailSkeleton: React.FC = () => (
43
+ <div className="flex flex-col gap-4">
44
+ {/* Post skeleton */}
45
+ <div className="flex flex-col gap-3 rounded-lg border border-[#E5E5E5] bg-white p-4">
46
+ <div className="flex items-center gap-2">
47
+ <Skeleton className="h-10 w-10 rounded-full" />
48
+ <div className="flex flex-col gap-1">
49
+ <Skeleton className="h-4 w-28" />
50
+ <Skeleton className="h-3 w-20" />
51
+ </div>
52
+ </div>
53
+ <div className="space-y-2">
54
+ <Skeleton className="h-6 w-3/4" />
55
+ <Skeleton className="h-4 w-full" />
56
+ <Skeleton className="h-4 w-full" />
57
+ <Skeleton className="h-4 w-2/3" />
58
+ </div>
59
+ <div className="mt-2 flex items-center gap-4">
60
+ <Skeleton className="h-8 w-20 rounded-lg" />
61
+ <Skeleton className="h-8 w-16 rounded-lg" />
62
+ <Skeleton className="h-8 w-20 rounded-lg" />
63
+ </div>
64
+ </div>
65
+ {/* Comments skeleton */}
66
+ <div className="flex flex-col gap-4">
67
+ <Skeleton className="h-10 w-full rounded-lg" />
68
+ <div className="flex gap-3">
69
+ <Skeleton className="h-10 w-10 rounded-full" />
70
+ <div className="flex-1 space-y-2">
71
+ <Skeleton className="h-4 w-32" />
72
+ <Skeleton className="h-4 w-full" />
73
+ <Skeleton className="h-4 w-3/4" />
74
+ </div>
75
+ </div>
76
+ <div className="flex gap-3">
77
+ <Skeleton className="h-10 w-10 rounded-full" />
78
+ <div className="flex-1 space-y-2">
79
+ <Skeleton className="h-4 w-28" />
80
+ <Skeleton className="h-4 w-full" />
81
+ <Skeleton className="h-4 w-1/2" />
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ );
87
+
88
+ const CommunityPost1: React.FC<ILevoBlockBaseProps<ICommunityPost1Content>> = ({ content }) => {
26
89
  const { fetch, logger } = useLevo();
27
- const [fetchedPostData, setFetchedPostData] =
28
- useState<Partial<IForumPost> | null>(null);
90
+ const { isLoggedIn } = useAuth();
91
+ const [fetchedPostData, setFetchedPostData] = useState<Partial<IForumPost> | null>(null);
29
92
  const [commentCount, setCommentCount] = useState(
30
93
  (content?.post as unknown as IForumPost)?.comments_count ?? 0,
31
94
  );
32
95
  const hasFetchedRef = useRef(false);
33
96
  const failedAttemptsRef = useRef(0);
97
+ const hasFetchedForumRef = useRef(false);
98
+ const failedForumAttemptsRef = useRef(0);
99
+ const hasFetchedMembershipRef = useRef(false);
100
+ const failedMembershipAttemptsRef = useRef(0);
101
+ const [forumData, setForumData] = useState<IForum | null>(null);
102
+ const [isMember, setIsMember] = useState<boolean>(false);
103
+ const [isLoadingMembership, setIsLoadingMembership] = useState(false);
104
+ const [joinRequest, setJoinRequest] = useState<IForumJoinRequest | null>(null);
34
105
 
35
106
  const postSlug = (content?.post as unknown as IForumPost)?.slug;
107
+ const forumKey = content?.forum_key as unknown as string;
108
+
109
+ const fetchForumData = useCallback(async () => {
110
+ if (!forumKey || hasFetchedForumRef.current || failedForumAttemptsRef.current >= 3) return;
111
+
112
+ try {
113
+ const response = await fetch
114
+ .url(`/v1/community/${forumKey}/get-single`)
115
+ .get()
116
+ .json<IResponseSingle<IForum>>();
117
+
118
+ if (response?.content?.data) {
119
+ setForumData(response.content.data);
120
+ hasFetchedForumRef.current = true;
121
+ }
122
+ } catch (error) {
123
+ const levoError = getLevoError(error);
124
+ logger?.error("Failed to fetch forum data", levoError);
125
+ failedForumAttemptsRef.current += 1;
126
+ }
127
+ }, [forumKey, fetch, logger]);
128
+
129
+ const checkMembership = useCallback(async () => {
130
+ if (!forumData?.settings?.invite_only || !isLoggedIn) {
131
+ setIsMember(false);
132
+ setJoinRequest(null);
133
+ hasFetchedMembershipRef.current = true;
134
+ return;
135
+ }
136
+
137
+ if (hasFetchedMembershipRef.current || failedMembershipAttemptsRef.current >= 3) {
138
+ return;
139
+ }
140
+
141
+ setIsLoadingMembership(true);
142
+
143
+ try {
144
+ const response = await fetch
145
+ .url(`/v1/community/${forumKey}/member/get-me`)
146
+ .get()
147
+ .json<IResponseSingle<IForumMembershipStatus>>();
148
+
149
+ const data = response?.content?.data;
150
+ setIsMember(!!data?.member);
151
+ setJoinRequest(data?.join_request || null);
152
+ hasFetchedMembershipRef.current = true;
153
+ } catch (error) {
154
+ const levoError = getLevoError(error);
155
+ logger?.error("Failed to check membership", levoError);
156
+ setIsMember(false);
157
+ setJoinRequest(null);
158
+ failedMembershipAttemptsRef.current += 1;
159
+ } finally {
160
+ setIsLoadingMembership(false);
161
+ }
162
+ }, [forumKey, forumData?.settings?.invite_only, isLoggedIn, fetch, logger]);
163
+
164
+ /** Submit a join request for the current user */
165
+ const handleSubmitJoinRequest = useCallback(
166
+ async (message?: string): Promise<boolean> => {
167
+ try {
168
+ const response = await 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
+ 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, fetch, logger],
189
+ );
190
+
191
+ useEffect(() => {
192
+ fetchForumData();
193
+ }, [fetchForumData]);
194
+
195
+ useEffect(() => {
196
+ if (forumData) {
197
+ checkMembership();
198
+ }
199
+ }, [forumData, checkMembership]);
36
200
 
37
201
  useEffect(() => {
38
202
  const fetchPostVotes = async () => {
39
- if (!postSlug || hasFetchedRef.current || failedAttemptsRef.current >= 3)
40
- return;
203
+ if (!postSlug || hasFetchedRef.current || failedAttemptsRef.current >= 3) return;
41
204
 
42
205
  try {
43
206
  const response = await fetch
44
- .url(`/v1/community/forum/posts/${postSlug}/get-single`)
207
+ .url(`/v1/community/${forumKey}/post/${postSlug}/get-single`)
45
208
  .get()
46
209
  .json<IResponseSingle<IForumPost>>();
47
210
 
@@ -78,77 +241,164 @@ const CommunityPost1: React.FC<ILevoBlockBaseProps<ICommunityPost1Content>> = ({
78
241
  setCommentCount((prev) => prev + delta);
79
242
  };
80
243
 
81
- const isLoadingPostData =
82
- !hasFetchedRef.current && failedAttemptsRef.current < 3;
244
+ const isLoadingPostData = !hasFetchedRef.current && failedAttemptsRef.current < 3;
245
+
246
+ /** Check if we're still loading forum data */
247
+ const isLoadingForumData = !hasFetchedForumRef.current && failedForumAttemptsRef.current < 3;
248
+
249
+ /** Check if we're still loading membership (only relevant for invite-only forums) */
250
+ const isCheckingMembership =
251
+ forumData?.settings?.invite_only &&
252
+ !hasFetchedMembershipRef.current &&
253
+ failedMembershipAttemptsRef.current < 3;
254
+
255
+ /**
256
+ * Show skeleton until all required data is fetched:
257
+ * - Forum data must be loaded
258
+ * - Membership check must be complete (for invite-only forums)
259
+ * - Post data should be available
260
+ */
261
+ const isInitialLoading = isLoadingForumData || isCheckingMembership || isLoadingMembership;
262
+
263
+ const isInviteOnly = forumData?.settings?.invite_only ?? false;
264
+ const allowJoinRequests = forumData?.settings?.allow_join_requests ?? false;
265
+
266
+ /**
267
+ * Check if posts should be restricted (show overlay):
268
+ * 1. For non-logged-in users: when invite_only=true
269
+ * 2. For logged-in non-members: when invite_only=true AND allow_public_view_for_post=false
270
+ * Note: Only evaluate after membership check is complete to avoid flash
271
+ */
272
+ const shouldRestrictPosts =
273
+ isInviteOnly &&
274
+ hasFetchedMembershipRef.current &&
275
+ !isLoadingMembership &&
276
+ (!isLoggedIn || (!isMember && !forumData?.settings?.allow_public_view_for_post));
277
+
278
+ /**
279
+ * Check if comments should be restricted (show overlay):
280
+ * 1. For non-logged-in users: when invite_only=true
281
+ * 2. For logged-in non-members: when invite_only=true AND allow_public_view_for_comment=false
282
+ * This is a secondary check - only relevant if posts are viewable
283
+ * Note: Only evaluate after membership check is complete to avoid flash
284
+ */
285
+ const shouldRestrictComments =
286
+ isInviteOnly &&
287
+ hasFetchedMembershipRef.current &&
288
+ !isLoadingMembership &&
289
+ (!isLoggedIn || (!isMember && !forumData?.settings?.allow_public_view_for_comment));
290
+
291
+ /**
292
+ * Whether user can interact with posts (upvote/downvote):
293
+ * - Not invite-only: true (login modal will show if not logged in)
294
+ * - Invite-only + member: true
295
+ * - Invite-only + non-member: false (greyed out)
296
+ */
297
+ const canInteractWithPosts = !isInviteOnly || isMember;
298
+
299
+ /**
300
+ * Whether user can interact with comments (reply/vote):
301
+ * - Not invite-only: true (login modal will show if not logged in)
302
+ * - Invite-only + member: true
303
+ * - Invite-only + non-member: false (greyed out)
304
+ */
305
+ const canInteractWithComments = !isInviteOnly || isMember;
306
+
83
307
  return (
84
- <Section elementKey="layout">
85
- <Container elementKey="container">
86
- <Box elementKey="left_container">
87
- <Box elementKey="sidebar_links_levoGroup">
88
- {content?.sidebar_links?.map((v, index) => {
89
- return (
90
- <Button
91
- key={`sidebar_links.${index}.sidebar_link`}
92
- elementKey={`sidebar_links.${index}.sidebar_link`}
93
- />
94
- );
95
- })}
96
- </Box>
97
- </Box>
98
- <Box elementKey="content_container">
99
- {postWithVotes ? (
100
- <Box elementKey="post_container">
101
- <ForumPost
102
- post={postWithVotes}
103
- isRenderedSolo
104
- postDetailPageBaseURL={content?.post_detail_page_url || ""}
105
- actionsLoadingState={{
106
- comments: isLoadingPostData,
107
- }}
108
- />
109
- <Comments
110
- post={postWithVotes}
111
- onCommentCountChange={handleCommentCountUpdate}
112
- />
113
- </Box>
114
- ) : (
115
- <Box elementKey="loading_container">
116
- <Spinner className="text-brand" />
117
- </Box>
118
- )}
119
- </Box>
120
- <Box elementKey="right_container">
121
- <Box elementKey="edit_profile_block">
122
- <Box elementKey="edit_profile_block_content_wrapper">
123
- <Media
124
- elementKey="edit_profile_block_banner_image"
125
- alt="Banner Image"
126
- />
127
- <Box elementKey="edit_profile_block_content">
128
- <Media
129
- elementKey="edit_profile_block_logo"
130
- alt="Community Logo"
131
- />
132
- <Box elementKey="edit_profile_block_text_container">
133
- <Heading elementKey="edit_profile_block_title" />
134
- <Typography elementKey="edit_profile_block_description" />
135
- </Box>
136
- </Box>
137
- </Box>
138
- <Box elementKey="edit_profile_ctas_levoGroup">
139
- {content?.edit_profile_ctas?.map((v, index) => {
308
+ <ForumProvider
309
+ forum={forumData}
310
+ isMember={isMember}
311
+ isLoadingMembership={isLoadingMembership}
312
+ isInviteOnly={isInviteOnly}
313
+ canInteractWithPosts={canInteractWithPosts}
314
+ canInteractWithComments={canInteractWithComments}
315
+ joinRequest={joinRequest}
316
+ isJoinRequestPending={!!joinRequest}
317
+ allowJoinRequests={allowJoinRequests}
318
+ submitJoinRequest={handleSubmitJoinRequest}
319
+ refreshMembershipStatus={checkMembership}
320
+ >
321
+ <Section elementKey="layout">
322
+ <Container elementKey="container">
323
+ <Box elementKey="left_container">
324
+ <Box elementKey="sidebar_links_levoGroup">
325
+ {content?.sidebar_links?.map((v, index) => {
140
326
  return (
141
327
  <Button
142
- key={`edit_profile_ctas.${index}.edit_profile_cta`}
143
- elementKey={`edit_profile_ctas.${index}.edit_profile_cta`}
328
+ key={`sidebar_links.${index}.sidebar_link`}
329
+ elementKey={`sidebar_links.${index}.sidebar_link`}
144
330
  />
145
331
  );
146
332
  })}
147
333
  </Box>
148
334
  </Box>
149
- </Box>
150
- </Container>
151
- </Section>
335
+ <Box elementKey="content_container">
336
+ {isInitialLoading ? (
337
+ <Box elementKey="loading_container">
338
+ <PostDetailSkeleton />
339
+ </Box>
340
+ ) : shouldRestrictPosts ? (
341
+ <>
342
+ {/* Show sample posts behind the overlay */}
343
+ <SamplePostSkeleton opacity={0.6} />
344
+ <SamplePostSkeleton opacity={0.4} />
345
+ <SamplePostSkeleton opacity={0.3} />
346
+ <PostsRestrictionOverlay isOpen communityName={forumData?.name} />
347
+ </>
348
+ ) : postWithVotes ? (
349
+ <Box elementKey="post_container">
350
+ <ForumPost
351
+ post={postWithVotes}
352
+ isRenderedSolo
353
+ postDetailPageBaseURL={content?.post_detail_page_url || ""}
354
+ forumKey={forumKey}
355
+ actionsLoadingState={{
356
+ comments: isLoadingPostData,
357
+ }}
358
+ />
359
+ {shouldRestrictComments ? (
360
+ <CommentsRestrictionOverlay communityName={forumData?.name} />
361
+ ) : (
362
+ <Comments
363
+ forumKey={forumKey}
364
+ post={postWithVotes}
365
+ onCommentCountChange={handleCommentCountUpdate}
366
+ />
367
+ )}
368
+ </Box>
369
+ ) : (
370
+ <Box elementKey="loading_container">
371
+ <PostDetailSkeleton />
372
+ </Box>
373
+ )}
374
+ </Box>
375
+ <Box elementKey="right_container">
376
+ <Box elementKey="edit_profile_block">
377
+ <Box elementKey="edit_profile_block_content_wrapper">
378
+ <Media elementKey="edit_profile_block_banner_image" alt="Banner Image" />
379
+ <Box elementKey="edit_profile_block_content">
380
+ <Media elementKey="edit_profile_block_logo" alt="Community Logo" />
381
+ <Box elementKey="edit_profile_block_text_container">
382
+ <Heading elementKey="edit_profile_block_title" />
383
+ <Typography elementKey="edit_profile_block_description" />
384
+ </Box>
385
+ </Box>
386
+ </Box>
387
+ <Box elementKey="edit_profile_ctas_levoGroup">
388
+ {content?.edit_profile_ctas?.map((v, index) => {
389
+ return (
390
+ <Button
391
+ key={`edit_profile_ctas.${index}.edit_profile_cta`}
392
+ elementKey={`edit_profile_ctas.${index}.edit_profile_cta`}
393
+ />
394
+ );
395
+ })}
396
+ </Box>
397
+ </Box>
398
+ </Box>
399
+ </Container>
400
+ </Section>
401
+ </ForumProvider>
152
402
  );
153
403
  };
154
404
 
@@ -4,23 +4,15 @@ const MEDIA = {
4
4
  id: "zucZWwW4cQj9F",
5
5
  filename: "contact-us-1.png",
6
6
  kind: "image",
7
- location:
8
- "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC.png",
7
+ location: "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC.png",
9
8
  srcset: {
10
- "1080w":
11
- "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-1080w.webp",
12
- "1200w":
13
- "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-1200w.webp",
14
- "1920w":
15
- "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-1920w.webp",
16
- "320w":
17
- "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-320w.webp",
18
- "640w":
19
- "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-640w.webp",
20
- "750w":
21
- "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-750w.webp",
22
- "828w":
23
- "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-828w.webp",
9
+ "1080w": "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-1080w.webp",
10
+ "1200w": "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-1200w.webp",
11
+ "1920w": "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-1920w.webp",
12
+ "320w": "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-320w.webp",
13
+ "640w": "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-640w.webp",
14
+ "750w": "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-750w.webp",
15
+ "828w": "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-828w.webp",
24
16
  path: "https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-1920w.webp 1920w, https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-320w.webp 320w, https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-640w.webp 640w, https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-750w.webp 750w, https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-828w.webp 828w, https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-1080w.webp 1080w, https://static.levocdn.com/WLZ1TXTD/contact-us-1-OaJcGLqfj5VDC-1200w.webp 1200w",
25
17
  },
26
18
  mimetype: "image/png",
@@ -1,19 +1,9 @@
1
1
  import type { ILevoBlockBaseProps } from "@levo-so/studio";
2
- import {
3
- Box,
4
- Button,
5
- Heading,
6
- Media,
7
- Section,
8
- Typography,
9
- Container,
10
- } from "@levo-so/studio";
2
+ import { Box, Button, Container, Heading, Media, Section, Typography } from "@levo-so/studio";
11
3
 
12
4
  import type { IContactUs1Content } from "./contact-us-1.schema";
13
5
 
14
- const ContactUs1: React.FC<ILevoBlockBaseProps<IContactUs1Content>> = ({
15
- content,
16
- }) => (
6
+ const ContactUs1: React.FC<ILevoBlockBaseProps<IContactUs1Content>> = ({ content }) => (
17
7
  <Section elementKey="layout">
18
8
  <Container elementKey="container">
19
9
  <Box elementKey="contentWrapper">
@@ -1,20 +1,18 @@
1
1
  import {
2
- type ILevoBlockBaseProps,
2
+ Box,
3
+ Container,
4
+ Form,
3
5
  Heading,
4
6
  Icon,
5
- Typography,
6
- Container,
7
- Section,
8
- Box,
7
+ type ILevoBlockBaseProps,
9
8
  Media,
10
- Form,
9
+ Section,
10
+ Typography,
11
11
  } from "@levo-so/studio";
12
12
 
13
13
  import type { IContactUs2Content } from "./contact-us-2.schema";
14
14
 
15
- const ContactUs2: React.FC<ILevoBlockBaseProps<IContactUs2Content>> = ({
16
- content,
17
- }) => (
15
+ const ContactUs2: React.FC<ILevoBlockBaseProps<IContactUs2Content>> = ({ content }) => (
18
16
  <Section elementKey="layout">
19
17
  <Container elementKey="container">
20
18
  <Box elementKey="layoutContentWrapper">
@@ -30,32 +28,23 @@ const ContactUs2: React.FC<ILevoBlockBaseProps<IContactUs2Content>> = ({
30
28
  </Box>
31
29
  <Box data-levo_group elementKey="contact_options_levoGroup">
32
30
  {(content?.contact_options ?? []).map((_, index) => (
33
- <Box
34
- data-levo_group_item
35
- elementKey={`contact_options.${index}.wrapper`}
36
- key={index}
37
- >
31
+ <Box data-levo_group_item elementKey={`contact_options.${index}.wrapper`} key={index}>
38
32
  <Icon elementKey={`contact_options.${index}.icon`} />
39
33
  <Box elementKey={`contact_options.${index}.text_box`}>
40
34
  <Heading elementKey={`contact_options.${index}.label`} />
41
35
  <Typography elementKey={`contact_options.${index}.value`} />
42
- <Box
43
- data-levo_group
44
- elementKey={`contact_options.${index}.icon_links_levoGroup`}
45
- >
46
- {(content?.contact_options?.[index]?.icon_links ?? []).map(
47
- (_, linkIndex) => (
48
- <Box
49
- data-levo_group_item
50
- elementKey={`contact_options.${index}.icon_links.${linkIndex}.wrapper`}
51
- key={linkIndex}
52
- >
53
- <Icon
54
- elementKey={`contact_options.${index}.icon_links.${linkIndex}.icon`}
55
- />
56
- </Box>
57
- ),
58
- )}
36
+ <Box data-levo_group elementKey={`contact_options.${index}.icon_links_levoGroup`}>
37
+ {(content?.contact_options?.[index]?.icon_links ?? []).map((_, linkIndex) => (
38
+ <Box
39
+ data-levo_group_item
40
+ elementKey={`contact_options.${index}.icon_links.${linkIndex}.wrapper`}
41
+ key={linkIndex}
42
+ >
43
+ <Icon
44
+ elementKey={`contact_options.${index}.icon_links.${linkIndex}.icon`}
45
+ />
46
+ </Box>
47
+ ))}
59
48
  </Box>
60
49
  </Box>
61
50
  </Box>