@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.
- package/package.json +11 -3
- package/src/_tests/publishedResolvability.spec.ts +184 -0
- package/src/_tests/specsRunWorkspaceSource.spec.ts +116 -0
- package/src/_tests/workspaceAliases.ts +40 -0
- package/src/contexts/CartContext.tsx +17 -1
- package/src/contexts/PublicAuthContext.tsx +34 -5
- package/src/contexts/_tests/CartContext.spec.tsx +36 -0
- package/src/contexts/_tests/PublicAuthRefetch.spec.tsx +147 -0
- package/src/data/queries/useBroadcasts.ts +151 -0
- package/src/data/queries/useMyBookings.ts +18 -1
- package/src/i18n/_tests/translationKeys.spec.ts +15 -0
- package/src/i18n/de.json +97 -0
- package/src/i18n/en.json +97 -0
- package/src/ui/format/_tests/membershipPwyw.spec.ts +185 -0
- package/src/ui/format/_tests/pwyw.spec.ts +65 -8
- package/src/ui/format/membershipPwyw.ts +164 -0
- package/src/ui/format/pwyw.ts +37 -0
- package/src/ui/headless/broadcast/_tests/broadcastState.spec.ts +235 -0
- package/src/ui/headless/broadcast/broadcastState.ts +158 -0
- package/src/ui/headless/broadcast/useBroadcastWatch.ts +174 -21
- package/src/ui/headless/event/useEventCheckout.ts +8 -13
- package/src/ui/headless/index.ts +14 -0
- package/src/ui/headless/membership/useMembershipCheckout.ts +160 -32
- package/src/ui/index.ts +61 -0
- package/src/ui/media/BookingCallScreen.tsx +33 -0
- package/src/ui/media/CallHelpHint.tsx +87 -0
- package/src/ui/media/CallStage.tsx +633 -0
- package/src/ui/media/_tests/CallStage.spec.tsx +931 -0
- package/src/ui/media/_tests/bookingSession.spec.tsx +227 -0
- package/src/ui/media/_tests/callState.spec.ts +499 -0
- package/src/ui/media/_tests/fakeNode.ts +178 -0
- package/src/ui/media/bookingSession.tsx +182 -0
- package/src/ui/media/bookingWindow.ts +81 -0
- package/src/ui/media/callState.ts +360 -0
- package/src/ui/media/index.ts +135 -0
- package/src/ui/styled/AccountDashboard.tsx +193 -4
- package/src/ui/styled/BroadcastWatch.tsx +107 -0
- package/src/ui/styled/ForgotPasswordForm.tsx +5 -0
- package/src/ui/styled/LiveBroadcastList.tsx +171 -0
- package/src/ui/styled/LoginForm.tsx +10 -0
- package/src/ui/styled/MembershipCheckout.tsx +318 -45
- package/src/ui/styled/MembershipTiers.tsx +10 -3
- package/src/ui/styled/ResetPasswordForm.tsx +5 -0
- package/src/ui/styled/SignupForm.tsx +5 -0
- package/src/ui/styled/_tests/AccountDashboardBookingCall.spec.tsx +166 -0
- package/src/ui/styled/_tests/AccountDashboardCommunity.spec.tsx +134 -0
- package/src/ui/styled/_tests/BroadcastPassValidation.spec.tsx +125 -0
- package/src/ui/styled/_tests/MembershipCheckout.spec.tsx +364 -0
- package/src/ui/styled/broadcast/BroadcastPassValidation.tsx +187 -0
- package/src/ui/styled/broadcast/BroadcastPlayer.tsx +536 -0
- package/src/ui/styled/broadcast/BroadcastTicketPurchase.tsx +74 -0
- package/src/ui/styled/broadcast/EndedBroadcast.tsx +103 -0
- package/src/ui/styled/community/CommunityComposer.tsx +182 -3
- package/src/ui/styled/community/CommunityFeed.tsx +36 -51
- package/src/ui/styled/community/CommunityPostDetail.tsx +16 -2
- package/src/ui/styled/community/_tests/CommunityComposer.spec.tsx +281 -0
- package/src/ui/styled/community/_tests/CommunityPostDetail.spec.tsx +175 -0
- 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,
|