@tribe-nest/forge 3.29.0 → 3.34.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 (58) hide show
  1. package/package.json +11 -3
  2. package/src/_tests/publishedResolvability.spec.ts +184 -0
  3. package/src/_tests/specsRunWorkspaceSource.spec.ts +116 -0
  4. package/src/_tests/workspaceAliases.ts +40 -0
  5. package/src/contexts/CartContext.tsx +17 -1
  6. package/src/contexts/PublicAuthContext.tsx +34 -5
  7. package/src/contexts/_tests/CartContext.spec.tsx +36 -0
  8. package/src/contexts/_tests/PublicAuthRefetch.spec.tsx +147 -0
  9. package/src/data/queries/useBroadcasts.ts +151 -0
  10. package/src/data/queries/useMyBookings.ts +18 -1
  11. package/src/i18n/_tests/translationKeys.spec.ts +15 -0
  12. package/src/i18n/de.json +97 -0
  13. package/src/i18n/en.json +97 -0
  14. package/src/ui/format/_tests/membershipPwyw.spec.ts +185 -0
  15. package/src/ui/format/_tests/pwyw.spec.ts +65 -8
  16. package/src/ui/format/membershipPwyw.ts +164 -0
  17. package/src/ui/format/pwyw.ts +37 -0
  18. package/src/ui/headless/broadcast/_tests/broadcastState.spec.ts +235 -0
  19. package/src/ui/headless/broadcast/broadcastState.ts +158 -0
  20. package/src/ui/headless/broadcast/useBroadcastWatch.ts +174 -21
  21. package/src/ui/headless/event/useEventCheckout.ts +8 -13
  22. package/src/ui/headless/index.ts +14 -0
  23. package/src/ui/headless/membership/useMembershipCheckout.ts +160 -32
  24. package/src/ui/index.ts +61 -0
  25. package/src/ui/media/BookingCallScreen.tsx +33 -0
  26. package/src/ui/media/CallHelpHint.tsx +87 -0
  27. package/src/ui/media/CallStage.tsx +633 -0
  28. package/src/ui/media/_tests/CallStage.spec.tsx +931 -0
  29. package/src/ui/media/_tests/bookingSession.spec.tsx +227 -0
  30. package/src/ui/media/_tests/callState.spec.ts +499 -0
  31. package/src/ui/media/_tests/fakeNode.ts +178 -0
  32. package/src/ui/media/bookingSession.tsx +182 -0
  33. package/src/ui/media/bookingWindow.ts +81 -0
  34. package/src/ui/media/callState.ts +360 -0
  35. package/src/ui/media/index.ts +135 -0
  36. package/src/ui/styled/AccountDashboard.tsx +193 -4
  37. package/src/ui/styled/BroadcastWatch.tsx +107 -0
  38. package/src/ui/styled/ForgotPasswordForm.tsx +5 -0
  39. package/src/ui/styled/LiveBroadcastList.tsx +171 -0
  40. package/src/ui/styled/LoginForm.tsx +10 -0
  41. package/src/ui/styled/MembershipCheckout.tsx +318 -45
  42. package/src/ui/styled/MembershipTiers.tsx +10 -3
  43. package/src/ui/styled/ResetPasswordForm.tsx +5 -0
  44. package/src/ui/styled/SignupForm.tsx +5 -0
  45. package/src/ui/styled/_tests/AccountDashboardBookingCall.spec.tsx +166 -0
  46. package/src/ui/styled/_tests/AccountDashboardCommunity.spec.tsx +134 -0
  47. package/src/ui/styled/_tests/BroadcastPassValidation.spec.tsx +125 -0
  48. package/src/ui/styled/_tests/MembershipCheckout.spec.tsx +364 -0
  49. package/src/ui/styled/broadcast/BroadcastPassValidation.tsx +187 -0
  50. package/src/ui/styled/broadcast/BroadcastPlayer.tsx +536 -0
  51. package/src/ui/styled/broadcast/BroadcastTicketPurchase.tsx +74 -0
  52. package/src/ui/styled/broadcast/EndedBroadcast.tsx +103 -0
  53. package/src/ui/styled/community/CommunityComposer.tsx +182 -3
  54. package/src/ui/styled/community/CommunityFeed.tsx +36 -51
  55. package/src/ui/styled/community/CommunityPostDetail.tsx +16 -2
  56. package/src/ui/styled/community/_tests/CommunityComposer.spec.tsx +281 -0
  57. package/src/ui/styled/community/_tests/CommunityPostDetail.spec.tsx +175 -0
  58. package/src/ui/styled/forge-utilities.css +832 -0
@@ -0,0 +1,171 @@
1
+ import { Calendar, Play } from "lucide-react";
2
+ import { useLiveBroadcasts } from "../../data/queries/useBroadcasts";
3
+ import type { ILiveBroadcast } from "../../types/models";
4
+ import { formatDateTime } from "../../utils/formatDateTime";
5
+ import { useForgeT } from "../../i18n";
6
+ import { useForgeTheme } from "../theme/ForgeThemeProvider";
7
+ import { broadcastListStatus } from "../headless/broadcast/broadcastState";
8
+ import { Loading } from "./Loading";
9
+
10
+ export interface LiveBroadcastListProps {
11
+ /** Host-owned navigation. Falls back to a full page load. */
12
+ navigate?: (href: string) => void;
13
+ /** Where a card leads. Default `/i/live/:broadcastId`. */
14
+ broadcastPath?: (broadcastId: string) => string;
15
+ }
16
+
17
+ /**
18
+ * What is on now: the tenant's live, upcoming and just-ended broadcasts.
19
+ *
20
+ * The list itself is public. The paywall is on the WATCH page, where a gated
21
+ * broadcast asks for a ticket before a single frame is mounted, so a card here
22
+ * gives away nothing but a title and a start time.
23
+ */
24
+ export function LiveBroadcastList({ navigate, broadcastPath }: LiveBroadcastListProps) {
25
+ const t = useForgeT();
26
+ const theme = useForgeTheme();
27
+ const { data: broadcasts, isLoading, error } = useLiveBroadcasts();
28
+
29
+ const href = (id: string) => (broadcastPath ? broadcastPath(id) : `/i/live/${id}`);
30
+ const go = (id: string) => {
31
+ const path = href(id);
32
+ if (navigate) navigate(path);
33
+ else if (typeof window !== "undefined") window.location.assign(path);
34
+ };
35
+
36
+ const centered = (title: string, body: string) => (
37
+ <div
38
+ style={{
39
+ display: "flex",
40
+ alignItems: "center",
41
+ justifyContent: "center",
42
+ minHeight: 400,
43
+ color: theme.colors.text,
44
+ fontFamily: theme.fontFamily,
45
+ }}
46
+ >
47
+ <div style={{ textAlign: "center" }}>
48
+ <h3 style={{ fontSize: 18, fontWeight: 600, marginBottom: 8 }}>{title}</h3>
49
+ <p style={{ opacity: 0.8 }}>{body}</p>
50
+ </div>
51
+ </div>
52
+ );
53
+
54
+ if (isLoading) return <Loading fullPage />;
55
+
56
+ if (error) {
57
+ return centered(t("forge.live_broadcast_list.error_title"), t("forge.live_broadcast_list.error_body"));
58
+ }
59
+
60
+ if (!broadcasts || broadcasts.length === 0) {
61
+ return centered(t("forge.live_broadcast_list.empty_title"), t("forge.live_broadcast_list.empty_body"));
62
+ }
63
+
64
+ return (
65
+ <div
66
+ style={{
67
+ width: "100%",
68
+ padding: "24px 16px",
69
+ backgroundColor: theme.colors.background,
70
+ color: theme.colors.text,
71
+ fontFamily: theme.fontFamily,
72
+ }}
73
+ >
74
+ <div style={{ maxWidth: 1280, margin: "0 auto" }}>
75
+ <div style={{ marginBottom: 32 }}>
76
+ <h1 style={{ fontSize: 32, fontWeight: 700, marginBottom: 8, fontFamily: theme.headingFontFamily }}>
77
+ {t("forge.live_broadcast_list.title")}
78
+ </h1>
79
+ <p style={{ opacity: 0.8 }}>{t("forge.live_broadcast_list.subtitle")}</p>
80
+ </div>
81
+
82
+ <div
83
+ style={{
84
+ display: "grid",
85
+ gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
86
+ gap: 24,
87
+ }}
88
+ >
89
+ {broadcasts.map((broadcast: ILiveBroadcast) => {
90
+ const status = broadcastListStatus(broadcast);
91
+ const thumb = broadcast.thumbnailUrl || broadcast.generatedThumbnailUrl;
92
+
93
+ return (
94
+ <button
95
+ key={broadcast.id}
96
+ type="button"
97
+ onClick={() => go(broadcast.id)}
98
+ data-testid="live-broadcast-card"
99
+ style={{
100
+ textAlign: "left",
101
+ padding: 0,
102
+ cursor: "pointer",
103
+ overflow: "hidden",
104
+ backgroundColor: theme.colors.background,
105
+ color: theme.colors.text,
106
+ borderRadius: theme.cornerRadius,
107
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
108
+ border: `1px solid ${theme.colors.primary}30`,
109
+ }}
110
+ >
111
+ <div style={{ position: "relative" }}>
112
+ <div
113
+ style={{
114
+ aspectRatio: "16/9",
115
+ display: "flex",
116
+ alignItems: "center",
117
+ justifyContent: "center",
118
+ position: "relative",
119
+ background: `linear-gradient(135deg, ${theme.colors.primary}20, ${theme.colors.primary}10)`,
120
+ }}
121
+ >
122
+ {thumb && (
123
+ <img
124
+ src={thumb}
125
+ alt={broadcast.title}
126
+ style={{
127
+ width: "100%",
128
+ height: "100%",
129
+ objectFit: "cover",
130
+ position: "absolute",
131
+ top: 0,
132
+ left: 0,
133
+ }}
134
+ />
135
+ )}
136
+ <Play size={40} style={{ color: theme.colors.primary, position: "relative", zIndex: 1 }} />
137
+ </div>
138
+ <div
139
+ style={{
140
+ position: "absolute",
141
+ top: 8,
142
+ left: 8,
143
+ padding: "4px 8px",
144
+ borderRadius: 999,
145
+ fontSize: 12,
146
+ fontWeight: 600,
147
+ background: status.background,
148
+ color: status.color,
149
+ }}
150
+ >
151
+ {status.text}
152
+ </div>
153
+ </div>
154
+
155
+ <div style={{ padding: 16 }}>
156
+ <h3 style={{ fontWeight: 600, marginBottom: 8 }}>{broadcast.title}</h3>
157
+ {broadcast.startDate && (
158
+ <div style={{ display: "flex", alignItems: "center", gap: 4, fontSize: 13, opacity: 0.8 }}>
159
+ <Calendar size={14} />
160
+ <span>{formatDateTime(broadcast.startDate)}</span>
161
+ </div>
162
+ )}
163
+ </div>
164
+ </button>
165
+ );
166
+ })}
167
+ </div>
168
+ </div>
169
+ </div>
170
+ );
171
+ }
@@ -18,6 +18,16 @@ export function LoginForm({ onSuccess, signupHref, forgotHref }: LoginFormProps)
18
18
  const flow = useLoginFlow({ onSuccess });
19
19
 
20
20
  const card: React.CSSProperties = {
21
+ // `width: 100%` is what stops this collapsing to its content.
22
+ //
23
+ // The host layout is a COLUMN flex container, and an auto cross-axis margin
24
+ // disables `align-items: stretch`. So `maxWidth` alone left the card
25
+ // shrink-to-fit and the auto margins centred whatever that came to. Sign-in
26
+ // has the least content of the auth forms (two fields, short labels), so it
27
+ // collapsed the furthest and looked broken next to signup.
28
+ //
29
+ // The cap is unchanged: this fills the container UP TO 420 and no further.
30
+ width: "100%",
21
31
  maxWidth: 420,
22
32
  margin: "40px auto 0",
23
33
  padding: 24,