@tribe-nest/forge 3.60.0 → 3.63.0

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 (220) hide show
  1. package/package.json +5 -2
  2. package/src/contexts/CartContext.tsx +34 -1
  3. package/src/contexts/PublicAuthContext.tsx +15 -3
  4. package/src/data/queries/_tests/metaSignals.spec.tsx +130 -0
  5. package/src/data/queries/useAccount.ts +33 -2
  6. package/src/data/queries/useAnalytics.ts +6 -6
  7. package/src/data/queries/useCallChat.ts +2 -2
  8. package/src/data/queries/useChat.ts +10 -1
  9. package/src/data/queries/useCheckouts.ts +2 -1
  10. package/src/data/queries/useCoachingAvailability.ts +10 -1
  11. package/src/data/queries/useCommunities.ts +2 -0
  12. package/src/data/queries/useCommunity.ts +362 -18
  13. package/src/data/queries/useCommunityAgent.ts +247 -0
  14. package/src/data/queries/useCommunityCalendar.ts +150 -0
  15. package/src/data/queries/useCommunityClassroom.ts +75 -0
  16. package/src/data/queries/useCommunityMembers.ts +108 -0
  17. package/src/data/queries/useCommunityModeration.ts +327 -0
  18. package/src/data/queries/useCommunityWiki.ts +137 -0
  19. package/src/data/queries/useCourses.ts +2 -0
  20. package/src/data/queries/useEmailSignIn.ts +79 -0
  21. package/src/data/queries/useEvents.ts +6 -1
  22. package/src/data/queries/useForms.ts +5 -1
  23. package/src/data/queries/useLeadMagnet.ts +6 -2
  24. package/src/data/queries/useMyBookings.ts +24 -1
  25. package/src/data/queries/useMyTickets.ts +24 -1
  26. package/src/data/queries/useNotifications.ts +6 -2
  27. package/src/data/queries/useOrders.ts +2 -1
  28. package/src/data/queries/usePWA.ts +111 -35
  29. package/src/data/queries/useSiteApp.ts +130 -0
  30. package/src/data/queries/useSubscriptions.ts +6 -1
  31. package/src/i18n/de.json +746 -1
  32. package/src/i18n/en.json +746 -1
  33. package/src/index.ts +20 -0
  34. package/src/runtime/pages.spec.ts +5 -0
  35. package/src/runtime/pages.ts +32 -6
  36. package/src/seo/_tests/seo.spec.ts +326 -0
  37. package/src/seo/applySeoToHead.ts +98 -0
  38. package/src/seo/index.ts +9 -0
  39. package/src/seo/resolve.ts +211 -0
  40. package/src/seo/seoHead.ts +61 -0
  41. package/src/seo/siteSeoHead.ts +97 -0
  42. package/src/seo/types.ts +99 -0
  43. package/src/server/_tests/siteBootstrap.spec.ts +65 -0
  44. package/src/server/index.ts +33 -4
  45. package/src/types/models.ts +11 -0
  46. package/src/ui/analytics/ForgeAnalytics.tsx +134 -34
  47. package/src/ui/analytics/PageMetaPixel.tsx +23 -8
  48. package/src/ui/analytics/_tests/ForgeAnalytics.meta.spec.tsx +129 -0
  49. package/src/ui/analytics/_tests/ForgeAnalytics.webVitals.spec.tsx +129 -0
  50. package/src/ui/analytics/_tests/metaCommerceEvents.spec.tsx +159 -0
  51. package/src/ui/analytics/useMetaEvents.ts +78 -0
  52. package/src/ui/headless/_tests/communityAgent.spec.tsx +519 -0
  53. package/src/ui/headless/_tests/communityCalendar.spec.tsx +195 -0
  54. package/src/ui/headless/_tests/communityClassroom.spec.tsx +120 -0
  55. package/src/ui/headless/_tests/communityHarness.tsx +10 -3
  56. package/src/ui/headless/_tests/communityMembers.spec.tsx +316 -0
  57. package/src/ui/headless/_tests/communityPoll.spec.tsx +389 -0
  58. package/src/ui/headless/_tests/memberHomeBar.spec.ts +84 -0
  59. package/src/ui/headless/_tests/siteAppBar.spec.ts +83 -0
  60. package/src/ui/headless/_tests/siteAppHarness.ts +19 -0
  61. package/src/ui/headless/_tests/siteAppHome.spec.ts +226 -0
  62. package/src/ui/headless/_tests/siteAppPrompts.spec.ts +185 -0
  63. package/src/ui/headless/_tests/useCommunityJoin.spec.tsx +228 -14
  64. package/src/ui/headless/_tests/useCommunityMembership.spec.tsx +29 -1
  65. package/src/ui/headless/_tests/useCommunityModeration.spec.tsx +420 -0
  66. package/src/ui/headless/_tests/useCommunityReads.spec.tsx +11 -2
  67. package/src/ui/headless/_tests/useCommunityWiki.spec.tsx +224 -0
  68. package/src/ui/headless/_tests/useSiteApp.spec.tsx +52 -0
  69. package/src/ui/headless/_tests/wikiMediaImage.spec.tsx +132 -0
  70. package/src/ui/headless/auth/useEmailCodeSignIn.ts +186 -0
  71. package/src/ui/headless/auth/useSignupForm.ts +20 -1
  72. package/src/ui/headless/chat/useChatAttachmentUpload.ts +9 -6
  73. package/src/ui/headless/chat/useChatRoom.ts +35 -1
  74. package/src/ui/headless/communities/index.ts +41 -0
  75. package/src/ui/headless/communities/useCommunityCalendar.ts +156 -0
  76. package/src/ui/headless/communities/useCommunityClassroom.ts +84 -0
  77. package/src/ui/headless/communities/useCommunityHome.ts +3 -0
  78. package/src/ui/headless/communities/useCommunityJoin.ts +73 -23
  79. package/src/ui/headless/communities/useCommunityMembership.ts +48 -2
  80. package/src/ui/headless/communities/useCommunityWiki.ts +266 -0
  81. package/src/ui/headless/community/communityPoll.ts +297 -0
  82. package/src/ui/headless/community/countries.ts +123 -0
  83. package/src/ui/headless/community/index.ts +81 -1
  84. package/src/ui/headless/community/linkPreview.ts +71 -0
  85. package/src/ui/headless/community/richLexical.ts +27 -2
  86. package/src/ui/headless/community/useCommunityAgent.ts +285 -0
  87. package/src/ui/headless/community/useCommunityMembers.ts +175 -0
  88. package/src/ui/headless/community/useCommunityModeration.ts +300 -0
  89. package/src/ui/headless/community/useCommunitySpaceFeed.ts +22 -4
  90. package/src/ui/headless/community/useCommunityThread.ts +16 -3
  91. package/src/ui/headless/community/useMentionComposer.ts +17 -9
  92. package/src/ui/headless/community/useRichMentionComposer.ts +21 -5
  93. package/src/ui/headless/downloads/index.ts +8 -0
  94. package/src/ui/headless/downloads/useDownloadPage.ts +134 -0
  95. package/src/ui/headless/index.ts +13 -0
  96. package/src/ui/headless/memberHome/index.ts +10 -0
  97. package/src/ui/headless/memberHome/memberHomeBar.ts +91 -0
  98. package/src/ui/headless/memberHome/useMemberHomeNav.ts +56 -18
  99. package/src/ui/headless/portal/index.ts +57 -0
  100. package/src/ui/headless/portal/usePortal.ts +274 -0
  101. package/src/ui/headless/portal/usePortalFiles.ts +68 -0
  102. package/src/ui/headless/portal/usePortalMessages.ts +189 -0
  103. package/src/ui/headless/portal/usePortalPreview.ts +142 -0
  104. package/src/ui/headless/siteApp/appBar.ts +102 -0
  105. package/src/ui/headless/siteApp/appHome.ts +344 -0
  106. package/src/ui/headless/siteApp/appPrompts.ts +229 -0
  107. package/src/ui/headless/siteApp/index.ts +58 -0
  108. package/src/ui/headless/siteApp/useAppHome.ts +210 -0
  109. package/src/ui/headless/siteApp/useAppInstallRequest.ts +16 -0
  110. package/src/ui/headless/work/index.ts +2 -0
  111. package/src/ui/headless/work/useWorkPortal.ts +37 -26
  112. package/src/ui/index.ts +11 -0
  113. package/src/ui/styled/AccountDashboard.tsx +712 -352
  114. package/src/ui/styled/AiAgentWidget.tsx +13 -1
  115. package/src/ui/styled/ChatRoom.tsx +126 -16
  116. package/src/ui/styled/Checkout.tsx +8 -0
  117. package/src/ui/styled/CheckoutConfirmation.tsx +8 -0
  118. package/src/ui/styled/CoachingConfirmation.tsx +8 -0
  119. package/src/ui/styled/CoachingDetail.tsx +2 -0
  120. package/src/ui/styled/CourseConfirmation.tsx +5 -0
  121. package/src/ui/styled/CourseDetail.tsx +2 -0
  122. package/src/ui/styled/DownloadPage.tsx +263 -0
  123. package/src/ui/styled/EventConfirmation.tsx +10 -0
  124. package/src/ui/styled/EventDetail.tsx +10 -0
  125. package/src/ui/styled/FilmLibrary.tsx +22 -9
  126. package/src/ui/styled/MusicLinkPage.tsx +5 -5
  127. package/src/ui/styled/ProductDetail.tsx +12 -0
  128. package/src/ui/styled/PwaRegistration.tsx +5 -1
  129. package/src/ui/styled/SignupForm.tsx +18 -3
  130. package/src/ui/styled/_tests/AccountDashboardBookingCall.spec.tsx +5 -5
  131. package/src/ui/styled/_tests/AccountNotifications.spec.tsx +135 -0
  132. package/src/ui/styled/_tests/DownloadPage.spec.tsx +68 -0
  133. package/src/ui/styled/app/AppBar.tsx +298 -0
  134. package/src/ui/styled/app/AppHome.tsx +1139 -0
  135. package/src/ui/styled/app/AppHomeMark.tsx +66 -0
  136. package/src/ui/styled/app/AppPrompts.tsx +464 -0
  137. package/src/ui/styled/app/_tests/AppBar.spec.tsx +96 -0
  138. package/src/ui/styled/app/_tests/AppHome.spec.tsx +209 -0
  139. package/src/ui/styled/app/_tests/AppPrompts.spec.tsx +232 -0
  140. package/src/ui/styled/app/index.ts +7 -0
  141. package/src/ui/styled/chat/ChatThreadPanel.tsx +29 -4
  142. package/src/ui/styled/chat/MessageBubble.tsx +43 -6
  143. package/src/ui/styled/communities/CommunityAbout.tsx +6 -1
  144. package/src/ui/styled/communities/CommunityCalendar.tsx +484 -0
  145. package/src/ui/styled/communities/CommunityClassroom.tsx +302 -0
  146. package/src/ui/styled/communities/CommunityHome.tsx +134 -11
  147. package/src/ui/styled/communities/CommunityJoinSheet.tsx +119 -19
  148. package/src/ui/styled/communities/CommunityMembershipMenu.tsx +168 -52
  149. package/src/ui/styled/communities/CommunityPublicWiki.tsx +75 -0
  150. package/src/ui/styled/communities/CommunityWiki.tsx +289 -0
  151. package/src/ui/styled/communities/MyMembershipsList.tsx +31 -5
  152. package/src/ui/styled/communities/_tests/CommunityAppHome.spec.tsx +92 -0
  153. package/src/ui/styled/communities/_tests/CommunityClassroomCalendar.spec.tsx +453 -0
  154. package/src/ui/styled/communities/_tests/CommunityMobile.spec.tsx +399 -0
  155. package/src/ui/styled/communities/_tests/CommunityWiki.spec.tsx +223 -0
  156. package/src/ui/styled/communities/index.ts +9 -0
  157. package/src/ui/styled/communities/shared.tsx +39 -11
  158. package/src/ui/styled/community/CommunityAgent.tsx +286 -0
  159. package/src/ui/styled/community/CommunityComposer.tsx +266 -15
  160. package/src/ui/styled/community/CommunityFeed.tsx +50 -1
  161. package/src/ui/styled/community/CommunityLinkPreview.tsx +159 -0
  162. package/src/ui/styled/community/CommunityMemberGlobe.tsx +171 -0
  163. package/src/ui/styled/community/CommunityMemberProfile.tsx +137 -4
  164. package/src/ui/styled/community/CommunityMembers.tsx +387 -0
  165. package/src/ui/styled/community/CommunityModeration.tsx +587 -0
  166. package/src/ui/styled/community/CommunityModerationState.tsx +127 -0
  167. package/src/ui/styled/community/CommunityModeratorMenu.tsx +317 -0
  168. package/src/ui/styled/community/CommunityNotifications.tsx +52 -6
  169. package/src/ui/styled/community/CommunityPollCard.tsx +250 -0
  170. package/src/ui/styled/community/CommunityPostCard.tsx +110 -34
  171. package/src/ui/styled/community/CommunityPostDetail.tsx +157 -71
  172. package/src/ui/styled/community/CommunityRichText.tsx +177 -32
  173. package/src/ui/styled/community/CommunitySpaces.tsx +49 -14
  174. package/src/ui/styled/community/_tests/CommunityAgent.spec.tsx +571 -0
  175. package/src/ui/styled/community/_tests/CommunityLinkPreview.spec.tsx +167 -0
  176. package/src/ui/styled/community/_tests/CommunityMembers.spec.tsx +137 -0
  177. package/src/ui/styled/community/_tests/CommunityModeration.spec.tsx +291 -0
  178. package/src/ui/styled/community/_tests/CommunityPoll.spec.tsx +386 -0
  179. package/src/ui/styled/community/index.ts +37 -2
  180. package/src/ui/styled/forge-utilities.css +234 -0
  181. package/src/ui/styled/members/CallRecordingActions.tsx +182 -0
  182. package/src/ui/styled/members/MemberCallsCalendar.tsx +13 -158
  183. package/src/ui/styled/members/MemberHomeLayout.tsx +295 -15
  184. package/src/ui/styled/members/MemberPostsFeed.tsx +3 -2
  185. package/src/ui/styled/members/index.ts +8 -0
  186. package/src/ui/styled/portal/PortalAccountSection.tsx +61 -0
  187. package/src/ui/styled/portal/PortalActivity.tsx +167 -0
  188. package/src/ui/styled/portal/PortalBilling.tsx +197 -0
  189. package/src/ui/styled/portal/PortalFiles.tsx +279 -0
  190. package/src/ui/styled/portal/PortalHome.tsx +401 -0
  191. package/src/ui/styled/portal/PortalMessages.tsx +300 -0
  192. package/src/ui/styled/portal/PortalPreviewBanner.tsx +75 -0
  193. package/src/ui/styled/portal/PortalProjects.tsx +407 -0
  194. package/src/ui/styled/portal/PortalShell.tsx +397 -0
  195. package/src/ui/styled/portal/_tests/PortalAppHome.spec.tsx +90 -0
  196. package/src/ui/styled/portal/_tests/PortalFiles.spec.tsx +159 -0
  197. package/src/ui/styled/portal/_tests/PortalHome.spec.tsx +148 -0
  198. package/src/ui/styled/portal/_tests/PortalMessages.spec.tsx +168 -0
  199. package/src/ui/styled/portal/_tests/PortalPreviewBanner.spec.tsx +132 -0
  200. package/src/ui/styled/portal/_tests/portalNav.spec.ts +123 -0
  201. package/src/ui/styled/portal/index.ts +13 -0
  202. package/src/ui/styled/portal/portalNav.ts +260 -0
  203. package/src/ui/styled/portal/shared.tsx +206 -0
  204. package/src/ui/styled/work/WorkInviteAccept.tsx +228 -168
  205. package/src/ui/styled/work/WorkPortalProjects.tsx +68 -120
  206. package/src/ui/styled/work/WorkProjectDocuments.tsx +99 -128
  207. package/src/ui/styled/work/WorkProjectInvoices.tsx +91 -86
  208. package/src/ui/styled/work/WorkProjectReport.tsx +11 -17
  209. package/src/ui/styled/work/WorkReportBody.tsx +223 -248
  210. package/src/ui/styled/work/WorkTaskAttachments.tsx +23 -29
  211. package/src/ui/styled/work/WorkTaskComments.tsx +75 -102
  212. package/src/ui/styled/work/WorkTaskDetail.tsx +48 -59
  213. package/src/ui/styled/work/WorkTokenReport.tsx +8 -16
  214. package/src/ui/styled/work/index.ts +1 -1
  215. package/src/utils/_tests/landing.spec.ts +39 -0
  216. package/src/utils/_tests/metaEvents.spec.ts +264 -0
  217. package/src/utils/_tests/structuredDataSeo.spec.ts +247 -0
  218. package/src/utils/landing.ts +36 -10
  219. package/src/utils/metaEvents.ts +302 -0
  220. package/src/utils/structuredData.ts +339 -1
@@ -50,6 +50,17 @@
50
50
  .fg\:pointer-events-none {
51
51
  pointer-events: none;
52
52
  }
53
+ .fg\:sr-only {
54
+ position: absolute;
55
+ width: 1px;
56
+ height: 1px;
57
+ padding: 0;
58
+ margin: -1px;
59
+ overflow: hidden;
60
+ clip: rect(0, 0, 0, 0);
61
+ white-space: nowrap;
62
+ border-width: 0;
63
+ }
53
64
  .fg\:absolute {
54
65
  position: absolute;
55
66
  }
@@ -65,6 +76,9 @@
65
76
  .fg\:inset-0 {
66
77
  inset: calc(var(--fg-spacing) * 0);
67
78
  }
79
+ .fg\:inset-x-0 {
80
+ inset-inline: calc(var(--fg-spacing) * 0);
81
+ }
68
82
  .fg\:top-2 {
69
83
  top: calc(var(--fg-spacing) * 2);
70
84
  }
@@ -134,6 +148,12 @@
134
148
  .fg\:mt-8 {
135
149
  margin-top: calc(var(--fg-spacing) * 8);
136
150
  }
151
+ .fg\:mt-10 {
152
+ margin-top: calc(var(--fg-spacing) * 10);
153
+ }
154
+ .fg\:-mr-4 {
155
+ margin-right: calc(var(--fg-spacing) * -4);
156
+ }
137
157
  .fg\:mb-1 {
138
158
  margin-bottom: calc(var(--fg-spacing) * 1);
139
159
  }
@@ -161,6 +181,9 @@
161
181
  .fg\:ml-2 {
162
182
  margin-left: calc(var(--fg-spacing) * 2);
163
183
  }
184
+ .fg\:ml-auto {
185
+ margin-left: auto;
186
+ }
164
187
  .fg\:line-clamp-3 {
165
188
  overflow: hidden;
166
189
  display: -webkit-box;
@@ -185,6 +208,9 @@
185
208
  .fg\:inline-flex {
186
209
  display: inline-flex;
187
210
  }
211
+ .fg\:aspect-square {
212
+ aspect-ratio: 1 / 1;
213
+ }
188
214
  .fg\:h-0\.5 {
189
215
  height: calc(var(--fg-spacing) * 0.5);
190
216
  }
@@ -203,6 +229,9 @@
203
229
  .fg\:h-12 {
204
230
  height: calc(var(--fg-spacing) * 12);
205
231
  }
232
+ .fg\:h-16 {
233
+ height: calc(var(--fg-spacing) * 16);
234
+ }
206
235
  .fg\:h-\[70vh\] {
207
236
  height: 70vh;
208
237
  }
@@ -212,6 +241,9 @@
212
241
  .fg\:h-full {
213
242
  height: 100%;
214
243
  }
244
+ .fg\:max-h-\[90dvh\] {
245
+ max-height: 90dvh;
246
+ }
215
247
  .fg\:min-h-\[320px\] {
216
248
  min-height: 320px;
217
249
  }
@@ -230,6 +262,12 @@
230
262
  .fg\:w-10 {
231
263
  width: calc(var(--fg-spacing) * 10);
232
264
  }
265
+ .fg\:w-11 {
266
+ width: calc(var(--fg-spacing) * 11);
267
+ }
268
+ .fg\:w-16 {
269
+ width: calc(var(--fg-spacing) * 16);
270
+ }
233
271
  .fg\:w-52 {
234
272
  width: calc(var(--fg-spacing) * 52);
235
273
  }
@@ -248,6 +286,21 @@
248
286
  .fg\:max-w-6xl {
249
287
  max-width: var(--fg-container-6xl);
250
288
  }
289
+ .fg\:max-w-\[85\%\] {
290
+ max-width: 85%;
291
+ }
292
+ .fg\:max-w-\[220px\] {
293
+ max-width: 220px;
294
+ }
295
+ .fg\:max-w-\[420px\] {
296
+ max-width: 420px;
297
+ }
298
+ .fg\:max-w-\[720px\] {
299
+ max-width: 720px;
300
+ }
301
+ .fg\:max-w-full {
302
+ max-width: 100%;
303
+ }
251
304
  .fg\:max-w-md {
252
305
  max-width: var(--fg-container-md);
253
306
  }
@@ -257,12 +310,18 @@
257
310
  .fg\:min-w-0 {
258
311
  min-width: calc(var(--fg-spacing) * 0);
259
312
  }
313
+ .fg\:min-w-1 {
314
+ min-width: calc(var(--fg-spacing) * 1);
315
+ }
260
316
  .fg\:flex-1 {
261
317
  flex: 1;
262
318
  }
263
319
  .fg\:shrink-0 {
264
320
  flex-shrink: 0;
265
321
  }
322
+ .fg\:cursor-grab {
323
+ cursor: grab;
324
+ }
266
325
  .fg\:cursor-pointer {
267
326
  cursor: pointer;
268
327
  }
@@ -281,6 +340,9 @@
281
340
  .fg\:grid-cols-2 {
282
341
  grid-template-columns: repeat(2, minmax(0, 1fr));
283
342
  }
343
+ .fg\:grid-cols-4 {
344
+ grid-template-columns: repeat(4, minmax(0, 1fr));
345
+ }
284
346
  .fg\:flex-col {
285
347
  flex-direction: column;
286
348
  }
@@ -290,6 +352,9 @@
290
352
  .fg\:flex-wrap {
291
353
  flex-wrap: wrap;
292
354
  }
355
+ .fg\:items-baseline {
356
+ align-items: baseline;
357
+ }
293
358
  .fg\:items-center {
294
359
  align-items: center;
295
360
  }
@@ -335,9 +400,15 @@
335
400
  .fg\:gap-6 {
336
401
  gap: calc(var(--fg-spacing) * 6);
337
402
  }
403
+ .fg\:gap-7 {
404
+ gap: calc(var(--fg-spacing) * 7);
405
+ }
338
406
  .fg\:gap-8 {
339
407
  gap: calc(var(--fg-spacing) * 8);
340
408
  }
409
+ .fg\:gap-10 {
410
+ gap: calc(var(--fg-spacing) * 10);
411
+ }
341
412
  .fg\:space-y-2 {
342
413
  :where(& > :not(:last-child)) {
343
414
  --tw-space-y-reverse: 0;
@@ -359,9 +430,15 @@
359
430
  margin-block-end: calc(calc(var(--fg-spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
360
431
  }
361
432
  }
433
+ .fg\:gap-x-3 {
434
+ column-gap: calc(var(--fg-spacing) * 3);
435
+ }
362
436
  .fg\:gap-x-4 {
363
437
  column-gap: calc(var(--fg-spacing) * 4);
364
438
  }
439
+ .fg\:gap-x-5 {
440
+ column-gap: calc(var(--fg-spacing) * 5);
441
+ }
365
442
  .fg\:space-x-4 {
366
443
  :where(& > :not(:last-child)) {
367
444
  --tw-space-x-reverse: 0;
@@ -383,6 +460,9 @@
383
460
  .fg\:overflow-x-auto {
384
461
  overflow-x: auto;
385
462
  }
463
+ .fg\:overflow-y-auto {
464
+ overflow-y: auto;
465
+ }
386
466
  .fg\:rounded {
387
467
  border-radius: 0.25rem;
388
468
  }
@@ -398,6 +478,10 @@
398
478
  .fg\:rounded-xl {
399
479
  border-radius: var(--fg-radius-xl);
400
480
  }
481
+ .fg\:rounded-t-\[var\(--cs-radius\)\] {
482
+ border-top-left-radius: var(--cs-radius);
483
+ border-top-right-radius: var(--cs-radius);
484
+ }
401
485
  .fg\:border {
402
486
  border-style: var(--tw-border-style);
403
487
  border-width: 1px;
@@ -474,6 +558,9 @@
474
558
  .fg\:p-8 {
475
559
  padding: calc(var(--fg-spacing) * 8);
476
560
  }
561
+ .fg\:px-0 {
562
+ padding-inline: calc(var(--fg-spacing) * 0);
563
+ }
477
564
  .fg\:px-2 {
478
565
  padding-inline: calc(var(--fg-spacing) * 2);
479
566
  }
@@ -483,6 +570,9 @@
483
570
  .fg\:px-4 {
484
571
  padding-inline: calc(var(--fg-spacing) * 4);
485
572
  }
573
+ .fg\:px-5 {
574
+ padding-inline: calc(var(--fg-spacing) * 5);
575
+ }
486
576
  .fg\:py-0\.5 {
487
577
  padding-block: calc(var(--fg-spacing) * 0.5);
488
578
  }
@@ -498,6 +588,9 @@
498
588
  .fg\:py-3 {
499
589
  padding-block: calc(var(--fg-spacing) * 3);
500
590
  }
591
+ .fg\:py-4 {
592
+ padding-block: calc(var(--fg-spacing) * 4);
593
+ }
501
594
  .fg\:py-5 {
502
595
  padding-block: calc(var(--fg-spacing) * 5);
503
596
  }
@@ -507,6 +600,9 @@
507
600
  .fg\:py-8 {
508
601
  padding-block: calc(var(--fg-spacing) * 8);
509
602
  }
603
+ .fg\:py-10 {
604
+ padding-block: calc(var(--fg-spacing) * 10);
605
+ }
510
606
  .fg\:py-12 {
511
607
  padding-block: calc(var(--fg-spacing) * 12);
512
608
  }
@@ -516,21 +612,33 @@
516
612
  .fg\:pt-2 {
517
613
  padding-top: calc(var(--fg-spacing) * 2);
518
614
  }
615
+ .fg\:pt-3 {
616
+ padding-top: calc(var(--fg-spacing) * 3);
617
+ }
519
618
  .fg\:pt-4 {
520
619
  padding-top: calc(var(--fg-spacing) * 4);
521
620
  }
522
621
  .fg\:pt-6 {
523
622
  padding-top: calc(var(--fg-spacing) * 6);
524
623
  }
624
+ .fg\:pr-4 {
625
+ padding-right: calc(var(--fg-spacing) * 4);
626
+ }
525
627
  .fg\:pb-4 {
526
628
  padding-bottom: calc(var(--fg-spacing) * 4);
527
629
  }
528
630
  .fg\:pb-6 {
529
631
  padding-bottom: calc(var(--fg-spacing) * 6);
530
632
  }
633
+ .fg\:pb-28 {
634
+ padding-bottom: calc(var(--fg-spacing) * 28);
635
+ }
531
636
  .fg\:pb-32 {
532
637
  padding-bottom: calc(var(--fg-spacing) * 32);
533
638
  }
639
+ .fg\:pb-\[calc\(72px\+env\(safe-area-inset-bottom\)\)\] {
640
+ padding-bottom: calc(72px + env(safe-area-inset-bottom));
641
+ }
534
642
  .fg\:text-center {
535
643
  text-align: center;
536
644
  }
@@ -600,9 +708,15 @@
600
708
  --tw-tracking: var(--fg-tracking-wide);
601
709
  letter-spacing: var(--fg-tracking-wide);
602
710
  }
711
+ .fg\:break-words {
712
+ overflow-wrap: break-word;
713
+ }
603
714
  .fg\:whitespace-pre-line {
604
715
  white-space: pre-line;
605
716
  }
717
+ .fg\:whitespace-pre-wrap {
718
+ white-space: pre-wrap;
719
+ }
606
720
  .fg\:text-muted {
607
721
  color: var(--fg-color-muted);
608
722
  }
@@ -659,6 +773,11 @@
659
773
  -webkit-user-select: none;
660
774
  user-select: none;
661
775
  }
776
+ .fg\:first\:mt-0 {
777
+ &:first-child {
778
+ margin-top: calc(var(--fg-spacing) * 0);
779
+ }
780
+ }
662
781
  .fg\:focus\:border-primary {
663
782
  &:focus {
664
783
  border-color: var(--fg-color-primary);
@@ -694,6 +813,11 @@
694
813
  flex-direction: row;
695
814
  }
696
815
  }
816
+ .fg\:sm\:flex-wrap {
817
+ @media (width >= 40rem) {
818
+ flex-wrap: wrap;
819
+ }
820
+ }
697
821
  .fg\:sm\:items-center {
698
822
  @media (width >= 40rem) {
699
823
  align-items: center;
@@ -764,6 +888,11 @@
764
888
  width: calc(var(--fg-spacing) * 64);
765
889
  }
766
890
  }
891
+ .fg\:md\:w-\[240px\] {
892
+ @media (width >= 48rem) {
893
+ width: 240px;
894
+ }
895
+ }
767
896
  .fg\:md\:w-\[260px\] {
768
897
  @media (width >= 48rem) {
769
898
  width: 260px;
@@ -784,6 +913,11 @@
784
913
  width: 360px;
785
914
  }
786
915
  }
916
+ .fg\:md\:max-w-\[70\%\] {
917
+ @media (width >= 48rem) {
918
+ max-width: 70%;
919
+ }
920
+ }
787
921
  .fg\:md\:shrink-0 {
788
922
  @media (width >= 48rem) {
789
923
  flex-shrink: 0;
@@ -809,6 +943,41 @@
809
943
  align-items: flex-start;
810
944
  }
811
945
  }
946
+ .fg\:md\:py-16 {
947
+ @media (width >= 48rem) {
948
+ padding-block: calc(var(--fg-spacing) * 16);
949
+ }
950
+ }
951
+ .fg\:lg\:sticky {
952
+ @media (width >= 64rem) {
953
+ position: sticky;
954
+ }
955
+ }
956
+ .fg\:lg\:inset-x-auto {
957
+ @media (width >= 64rem) {
958
+ inset-inline: auto;
959
+ }
960
+ }
961
+ .fg\:lg\:top-1\/2 {
962
+ @media (width >= 64rem) {
963
+ top: calc(1/2 * 100%);
964
+ }
965
+ }
966
+ .fg\:lg\:top-6 {
967
+ @media (width >= 64rem) {
968
+ top: calc(var(--fg-spacing) * 6);
969
+ }
970
+ }
971
+ .fg\:lg\:bottom-auto {
972
+ @media (width >= 64rem) {
973
+ bottom: auto;
974
+ }
975
+ }
976
+ .fg\:lg\:left-1\/2 {
977
+ @media (width >= 64rem) {
978
+ left: calc(1/2 * 100%);
979
+ }
980
+ }
812
981
  .fg\:lg\:col-span-1 {
813
982
  @media (width >= 64rem) {
814
983
  grid-column: span 1 / span 1;
@@ -819,6 +988,11 @@
819
988
  grid-column: span 2 / span 2;
820
989
  }
821
990
  }
991
+ .fg\:lg\:block {
992
+ @media (width >= 64rem) {
993
+ display: block;
994
+ }
995
+ }
822
996
  .fg\:lg\:flex {
823
997
  @media (width >= 64rem) {
824
998
  display: flex;
@@ -829,6 +1003,28 @@
829
1003
  display: none;
830
1004
  }
831
1005
  }
1006
+ .fg\:lg\:max-h-\[88vh\] {
1007
+ @media (width >= 64rem) {
1008
+ max-height: 88vh;
1009
+ }
1010
+ }
1011
+ .fg\:lg\:w-\[min\(560px\,calc\(100vw-32px\)\)\] {
1012
+ @media (width >= 64rem) {
1013
+ width: min(560px, calc(100vw - 32px));
1014
+ }
1015
+ }
1016
+ .fg\:lg\:-translate-x-1\/2 {
1017
+ @media (width >= 64rem) {
1018
+ --tw-translate-x: calc(calc(1/2 * 100%) * -1);
1019
+ translate: var(--tw-translate-x) var(--tw-translate-y);
1020
+ }
1021
+ }
1022
+ .fg\:lg\:-translate-y-1\/2 {
1023
+ @media (width >= 64rem) {
1024
+ --tw-translate-y: calc(calc(1/2 * 100%) * -1);
1025
+ translate: var(--tw-translate-x) var(--tw-translate-y);
1026
+ }
1027
+ }
832
1028
  .fg\:lg\:grid-cols-3 {
833
1029
  @media (width >= 64rem) {
834
1030
  grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -849,6 +1045,21 @@
849
1045
  gap: calc(var(--fg-spacing) * 8);
850
1046
  }
851
1047
  }
1048
+ .fg\:lg\:gap-12 {
1049
+ @media (width >= 64rem) {
1050
+ gap: calc(var(--fg-spacing) * 12);
1051
+ }
1052
+ }
1053
+ .fg\:lg\:rounded-\[var\(--cs-radius-lg\)\] {
1054
+ @media (width >= 64rem) {
1055
+ border-radius: var(--cs-radius-lg);
1056
+ }
1057
+ }
1058
+ .fg\:lg\:p-6 {
1059
+ @media (width >= 64rem) {
1060
+ padding: calc(var(--fg-spacing) * 6);
1061
+ }
1062
+ }
852
1063
  .fg\:lg\:p-8 {
853
1064
  @media (width >= 64rem) {
854
1065
  padding: calc(var(--fg-spacing) * 8);
@@ -859,6 +1070,11 @@
859
1070
  padding-block: calc(var(--fg-spacing) * 12);
860
1071
  }
861
1072
  }
1073
+ .fg\:lg\:pb-0 {
1074
+ @media (width >= 64rem) {
1075
+ padding-bottom: calc(var(--fg-spacing) * 0);
1076
+ }
1077
+ }
862
1078
  .fg\:lg\:text-3xl {
863
1079
  @media (width >= 64rem) {
864
1080
  font-size: var(--fg-text-3xl);
@@ -958,6 +1174,21 @@
958
1174
  inherits: false;
959
1175
  initial-value: 0 0 #0000;
960
1176
  }
1177
+ @property --tw-translate-x {
1178
+ syntax: "*";
1179
+ inherits: false;
1180
+ initial-value: 0;
1181
+ }
1182
+ @property --tw-translate-y {
1183
+ syntax: "*";
1184
+ inherits: false;
1185
+ initial-value: 0;
1186
+ }
1187
+ @property --tw-translate-z {
1188
+ syntax: "*";
1189
+ inherits: false;
1190
+ initial-value: 0;
1191
+ }
961
1192
  @layer properties {
962
1193
  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
963
1194
  *, ::before, ::after, ::backdrop {
@@ -981,6 +1212,9 @@
981
1212
  --tw-ring-offset-width: 0px;
982
1213
  --tw-ring-offset-color: #fff;
983
1214
  --tw-ring-offset-shadow: 0 0 #0000;
1215
+ --tw-translate-x: 0;
1216
+ --tw-translate-y: 0;
1217
+ --tw-translate-z: 0;
984
1218
  }
985
1219
  }
986
1220
  }
@@ -0,0 +1,182 @@
1
+ import { useState, type CSSProperties } from "react";
2
+ import { Download, FileText, Loader2, PlayCircle } from "lucide-react";
3
+ import { useQueryClient } from "@tanstack/react-query";
4
+ import { useForgeT } from "../../../i18n";
5
+ import { useThemeTokens } from "../../theme/ForgeThemeProvider";
6
+ import { useForge } from "../../../provider/ForgeProvider";
7
+ import {
8
+ fetchMemberCallTranscript,
9
+ memberCallTranscriptKey,
10
+ type MemberCallTranscript,
11
+ type MyMembershipCall,
12
+ } from "../../../data/queries/useMembershipCalls";
13
+ import { downloadTranscript } from "./callTranscriptText";
14
+
15
+ /**
16
+ * The rewatch pieces of a past call, shared by every surface that lists calls
17
+ * a member attended: the members calendar's details modal and the community
18
+ * calendar's agenda. One set of pieces, so a recording reads the same wherever
19
+ * it is found.
20
+ */
21
+
22
+ /** A call's recording as the attendee reads return it. */
23
+ export type CallRecording = NonNullable<MyMembershipCall["recording"]>;
24
+
25
+ /** Whether a recording can be watched now. */
26
+ export function callRecordingWatchable(recording: CallRecording | null | undefined): boolean {
27
+ return recording?.status === "ready" && !!recording.watchUrl;
28
+ }
29
+
30
+ export interface CallRecordingWatchLinkProps {
31
+ recording: CallRecording;
32
+ /** Drawn as a secondary action, when a stronger one (Join) sits beside it. */
33
+ secondary?: boolean;
34
+ }
35
+
36
+ /** "Watch recording", opening the signed video address in a new tab. Renders nothing until it is ready. */
37
+ export function CallRecordingWatchLink({ recording, secondary }: CallRecordingWatchLinkProps) {
38
+ const t = useThemeTokens();
39
+ const tr = useForgeT();
40
+ if (!callRecordingWatchable(recording)) return null;
41
+ const minutes = recording.durationSeconds ? Math.round(recording.durationSeconds / 60) : null;
42
+ return (
43
+ <a
44
+ data-testid="call-recording-watch"
45
+ href={recording.watchUrl!}
46
+ target="_blank"
47
+ rel="noreferrer"
48
+ style={{
49
+ display: "flex",
50
+ alignItems: "center",
51
+ gap: 6,
52
+ padding: "10px 20px",
53
+ borderRadius: t.cornerRadius,
54
+ background: secondary ? "transparent" : t.primary,
55
+ border: secondary ? `1px solid ${t.border}` : "none",
56
+ color: secondary ? t.text : t.textPrimary,
57
+ fontSize: 14,
58
+ fontWeight: 700,
59
+ textDecoration: "none",
60
+ }}
61
+ >
62
+ <PlayCircle size={16} />
63
+ {minutes ? tr("forge.call_recording_actions.watch_minutes", { minutes }) : tr("forge.call_recording_actions.watch")}
64
+ </a>
65
+ );
66
+ }
67
+
68
+ /** The recording is still being put together: come back shortly, not an error. */
69
+ export function CallRecordingProcessing() {
70
+ const t = useThemeTokens();
71
+ const tr = useForgeT();
72
+ return (
73
+ <span
74
+ data-testid="call-recording-processing"
75
+ style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 14, color: t.muted, fontWeight: 600 }}
76
+ >
77
+ <Loader2 size={15} />
78
+ {tr("forge.call_recording_actions.processing")}
79
+ </span>
80
+ );
81
+ }
82
+
83
+ /**
84
+ * The call's summary, and the way into its transcript.
85
+ *
86
+ * The lines are NOT here: a two-hour meeting is thousands of them, which is
87
+ * unusable in a modal or a list. "Read transcript" opens them beside the video
88
+ * (`onRead`), and Download reads them once without opening anything. A member
89
+ * sees the finished thing or that it is being prepared, never a failure: why
90
+ * one could not be made is the business's problem, not its audience's.
91
+ */
92
+ export function CallTranscriptPanel({
93
+ recordingId,
94
+ transcript,
95
+ onRead,
96
+ }: {
97
+ recordingId: string;
98
+ transcript: MemberCallTranscript;
99
+ onRead: () => void;
100
+ }) {
101
+ const t = useThemeTokens();
102
+ const tr = useForgeT();
103
+ const { client, profileId } = useForge();
104
+ const queryClient = useQueryClient();
105
+ const [downloading, setDownloading] = useState(false);
106
+ const heading: CSSProperties = { display: "flex", alignItems: "center", gap: 8, fontSize: 14, fontWeight: 700 };
107
+ const button: CSSProperties = {
108
+ display: "inline-flex",
109
+ alignItems: "center",
110
+ gap: 6,
111
+ padding: "8px 14px",
112
+ borderRadius: t.cornerRadius,
113
+ border: `1px solid ${t.border}`,
114
+ background: "transparent",
115
+ color: t.text,
116
+ fontFamily: t.fontFamily,
117
+ fontSize: 14,
118
+ fontWeight: 600,
119
+ cursor: "pointer",
120
+ };
121
+
122
+ if (transcript.status === "processing") {
123
+ return (
124
+ <div style={{ display: "flex", flexDirection: "column", gap: 6 }} data-testid="member-call-transcript">
125
+ <span style={heading}>
126
+ <FileText size={16} />
127
+ {tr("forge.call_recording_actions.transcript")}
128
+ </span>
129
+ <span style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 14, color: t.muted }} role="status">
130
+ <Loader2 size={15} />
131
+ {tr("forge.call_recording_actions.transcript_processing")}
132
+ </span>
133
+ </div>
134
+ );
135
+ }
136
+
137
+ const download = async () => {
138
+ setDownloading(true);
139
+ try {
140
+ const detail = await queryClient.fetchQuery({
141
+ queryKey: memberCallTranscriptKey(profileId, recordingId),
142
+ queryFn: () => fetchMemberCallTranscript(client, profileId, recordingId),
143
+ });
144
+ downloadTranscript(detail);
145
+ } finally {
146
+ setDownloading(false);
147
+ }
148
+ };
149
+
150
+ return (
151
+ <div style={{ display: "flex", flexDirection: "column", gap: 10 }} data-testid="member-call-transcript">
152
+ <span style={heading}>
153
+ <FileText size={16} />
154
+ {tr("forge.call_recording_actions.summary")}
155
+ </span>
156
+ <p
157
+ style={{
158
+ margin: 0,
159
+ fontSize: 14,
160
+ lineHeight: 1.5,
161
+ display: "-webkit-box",
162
+ WebkitLineClamp: 4,
163
+ WebkitBoxOrient: "vertical",
164
+ overflow: "hidden",
165
+ color: transcript.summary ? t.text : t.muted,
166
+ }}
167
+ >
168
+ {transcript.summary ? transcript.summary.summary : tr("forge.call_recording_actions.transcript_ready")}
169
+ </p>
170
+ <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
171
+ <button type="button" onClick={onRead} style={button}>
172
+ <FileText size={15} />
173
+ {tr("forge.call_recording_actions.read_transcript")}
174
+ </button>
175
+ <button type="button" onClick={() => void download()} disabled={downloading} style={button}>
176
+ {downloading ? <Loader2 size={15} /> : <Download size={15} />}
177
+ {tr("forge.call_recording_actions.download")}
178
+ </button>
179
+ </div>
180
+ </div>
181
+ );
182
+ }