@tribe-nest/forge 3.48.0 → 3.52.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 +2 -2
- package/src/data/queries/useCohortEnrolment.ts +87 -0
- package/src/data/queries/useMembershipCalls.ts +58 -8
- package/src/i18n/de.json +46 -0
- package/src/i18n/en.json +46 -0
- package/src/index.ts +1 -0
- package/src/types/models.ts +78 -5
- package/src/ui/headless/cohort/useCohortCheckout.ts +180 -0
- package/src/ui/headless/memberHome/useMemberCalls.ts +13 -0
- package/src/ui/index.ts +10 -0
- package/src/ui/styled/BlogPost.tsx +1 -1
- package/src/ui/styled/ChatRoom.tsx +2 -2
- package/src/ui/styled/Checkout.tsx +125 -125
- package/src/ui/styled/CoachingBooking.tsx +1 -1
- package/src/ui/styled/CoachingDetail.tsx +4 -4
- package/src/ui/styled/CohortCheckout.tsx +450 -0
- package/src/ui/styled/CohortConfirmation.tsx +259 -0
- package/src/ui/styled/CohortPage.tsx +31 -1
- package/src/ui/styled/CourseAccess.tsx +19 -6
- package/src/ui/styled/CourseCallsSection.tsx +99 -0
- package/src/ui/styled/CourseDetail.tsx +4 -4
- package/src/ui/styled/EventDetail.tsx +4 -4
- package/src/ui/styled/FormRenderer.tsx +14 -4
- package/src/ui/styled/PaystackPayButton.tsx +1 -1
- package/src/ui/styled/PodcastEpisode.tsx +1 -1
- package/src/ui/styled/ProductDetail.tsx +4 -4
- package/src/ui/styled/_tests/CohortCheckout.spec.tsx +232 -0
- package/src/ui/styled/_tests/CourseCallsSection.spec.tsx +163 -0
- package/src/ui/styled/_tests/packagedCssIsCurrent.spec.ts +43 -0
- package/src/ui/styled/_tests/utilityPrefix.spec.ts +156 -0
- package/src/ui/styled/broadcast/BroadcastMoqStage.tsx +1 -1
- package/src/ui/styled/chat/ChatThreadPanel.tsx +1 -1
- package/src/ui/styled/forge-utilities.css +463 -731
- package/src/ui/styled/members/MemberCallsCalendar.tsx +21 -3
- package/src/ui/styled/members/MemberHomeLayout.tsx +4 -4
- package/src/ui/styled/members/MemberPostsFeed.tsx +1 -1
- package/src/ui/styled/members/PostCollectionsGrid.tsx +1 -1
- package/src/ui/styled/musicLinkStyles.tsx +4 -4
|
@@ -26,7 +26,16 @@ export interface MemberCallsCalendarProps extends UseMemberCallsOptions {
|
|
|
26
26
|
getCallHref?: (publicId: string) => string;
|
|
27
27
|
/** SPA navigation adapter. Defaults to a full-page visit. */
|
|
28
28
|
navigate?: (href: string) => void;
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Where a viewer with nothing at all is sent to see what a membership
|
|
31
|
+
* includes. Shown only in the empty state.
|
|
32
|
+
*
|
|
33
|
+
* OPTIONAL, and omitting it is how a non-membership surface drops the upsell.
|
|
34
|
+
* A course-only buyer standing in their own classroom with no session this
|
|
35
|
+
* month must not be sold a membership they did not come for, so
|
|
36
|
+
* `CourseCallsSection` passes nothing here. The members calendar passes its
|
|
37
|
+
* path and is unchanged.
|
|
38
|
+
*/
|
|
30
39
|
membershipHref?: string;
|
|
31
40
|
className?: string;
|
|
32
41
|
style?: CSSProperties;
|
|
@@ -85,6 +94,15 @@ const WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
|
85
94
|
* computed from the same window the join enforces. A client re-deriving it from
|
|
86
95
|
* the clock would give two answers to one question, and the wrong one is the one
|
|
87
96
|
* the member acts on.
|
|
97
|
+
*
|
|
98
|
+
* ## It is not a MEMBERSHIP calendar, only a member-area one
|
|
99
|
+
*
|
|
100
|
+
* The rows come from `listForAttendee`, which resolves every audience kind, so
|
|
101
|
+
* this same grid renders a course's office hours and a cohort's week-3 session
|
|
102
|
+
* without knowing what either is. `courseId` / `cohortId` (through
|
|
103
|
+
* `UseMemberCallsOptions`) narrow it to one of them, which is what lets the
|
|
104
|
+
* classroom and the cohort page reuse this component instead of growing a
|
|
105
|
+
* second month grid. See `CourseCallsSection`.
|
|
88
106
|
*/
|
|
89
107
|
export function MemberCallsCalendar({
|
|
90
108
|
getCallHref = (publicId) => `/i/video-calls/${publicId}`,
|
|
@@ -153,7 +171,7 @@ export function MemberCallsCalendar({
|
|
|
153
171
|
(below). Both open the same details modal on tap. `minmax(0, 1fr)`
|
|
154
172
|
rather than `1fr` so a long title in a cell cannot force the grid
|
|
155
173
|
wider than its container. */}
|
|
156
|
-
<div className="hidden sm:block">
|
|
174
|
+
<div className="fg:hidden fg:sm:block">
|
|
157
175
|
<div style={{ display: "grid", gridTemplateColumns: "repeat(7, minmax(0, 1fr))", gap: 4 }}>
|
|
158
176
|
{WEEKDAYS.map((day) => (
|
|
159
177
|
<div key={day} style={{ fontSize: 11, fontWeight: 700, color: t.muted, padding: "4px 6px" }}>
|
|
@@ -214,7 +232,7 @@ export function MemberCallsCalendar({
|
|
|
214
232
|
{/* The phone face: an agenda, not a grid. A vertical list cannot overflow
|
|
215
233
|
a narrow screen sideways, and every row is a comfortable tap target
|
|
216
234
|
that opens the same details modal. Only days WITH a call appear. */}
|
|
217
|
-
<div className="sm:hidden">
|
|
235
|
+
<div className="fg:sm:hidden">
|
|
218
236
|
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
|
219
237
|
{calendar.days
|
|
220
238
|
.filter((day) => day.inMonth && day.calls.length > 0)
|
|
@@ -66,13 +66,13 @@ export function MemberHomeLayout({
|
|
|
66
66
|
|
|
67
67
|
return (
|
|
68
68
|
<div
|
|
69
|
-
className={`flex w-full flex-col lg:flex-row lg:items-start lg:gap-8 ${className ?? ""}`}
|
|
69
|
+
className={`fg:flex fg:w-full fg:flex-col fg:lg:flex-row fg:lg:items-start fg:lg:gap-8 ${className ?? ""}`}
|
|
70
70
|
style={{ color: t.text, fontFamily: t.fontFamily, ...style }}
|
|
71
71
|
>
|
|
72
72
|
{/* Top tab bar — small screens (horizontally scrollable, no burger). */}
|
|
73
73
|
<nav
|
|
74
74
|
aria-label="Member home"
|
|
75
|
-
className="flex w-full gap-1 overflow-x-auto lg:hidden"
|
|
75
|
+
className="fg:flex fg:w-full fg:gap-1 fg:overflow-x-auto fg:lg:hidden"
|
|
76
76
|
style={{ borderBottom: `1px solid ${t.border}`, paddingBottom: 8, marginBottom: 16 }}
|
|
77
77
|
>
|
|
78
78
|
{isReady ? visibleItems.map(renderItem) : null}
|
|
@@ -81,7 +81,7 @@ export function MemberHomeLayout({
|
|
|
81
81
|
{/* Side nav — lg and up. */}
|
|
82
82
|
<nav
|
|
83
83
|
aria-label="Member home"
|
|
84
|
-
className="sticky top-4 hidden w-52 shrink-0 flex-col gap-1 lg:flex"
|
|
84
|
+
className="fg:sticky fg:top-4 fg:hidden fg:w-52 fg:shrink-0 fg:flex-col fg:gap-1 fg:lg:flex"
|
|
85
85
|
style={{
|
|
86
86
|
border: `1px solid ${t.border}`,
|
|
87
87
|
borderRadius: t.cornerRadius,
|
|
@@ -92,7 +92,7 @@ export function MemberHomeLayout({
|
|
|
92
92
|
{isReady ? visibleItems.map(renderItem) : null}
|
|
93
93
|
</nav>
|
|
94
94
|
|
|
95
|
-
<main className="min-w-0 w-full flex-1">{children}</main>
|
|
95
|
+
<main className="fg:min-w-0 fg:w-full fg:flex-1">{children}</main>
|
|
96
96
|
</div>
|
|
97
97
|
);
|
|
98
98
|
}
|
|
@@ -114,7 +114,7 @@ export function MemberPostsFeed({
|
|
|
114
114
|
</div>
|
|
115
115
|
|
|
116
116
|
{/* Filters: type chips + tier select + saved toggle */}
|
|
117
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
117
|
+
<div className="fg:flex fg:flex-wrap fg:items-center fg:gap-2">
|
|
118
118
|
{chip("All", !feed.type && !feed.savedOnly, () => {
|
|
119
119
|
feed.setType("");
|
|
120
120
|
feed.setSavedOnly(false);
|
|
@@ -97,7 +97,7 @@ export function PostCollectionsGrid({
|
|
|
97
97
|
{list.hasActiveFilters ? "No collections found matching your filters" : "No collections yet"}
|
|
98
98
|
</p>
|
|
99
99
|
) : (
|
|
100
|
-
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
100
|
+
<div className="fg:grid fg:grid-cols-1 fg:gap-4 fg:sm:grid-cols-2">
|
|
101
101
|
{list.collections.map((collection) => (
|
|
102
102
|
<CollectionCard
|
|
103
103
|
key={collection.id}
|
|
@@ -153,7 +153,7 @@ const anchorProps = (platform: string, url: string, onClick: LayoutParts["onDest
|
|
|
153
153
|
function ServiceRows({ parts, surface }: { parts: LayoutParts; surface: string }) {
|
|
154
154
|
const { t, destinations, metaFor, onDestinationClick } = parts;
|
|
155
155
|
return (
|
|
156
|
-
<div className="tn-ml-rows" style={{ width: "100%", display: "flex", flexDirection: "column", gap: 10 }}>
|
|
156
|
+
<div className="fg:tn-ml-rows" style={{ width: "100%", display: "flex", flexDirection: "column", gap: 10 }}>
|
|
157
157
|
{destinations.map((d) => {
|
|
158
158
|
const meta = metaFor(d.platform);
|
|
159
159
|
return (
|
|
@@ -415,11 +415,11 @@ function SplitLayout({ parts }: { parts: LayoutParts }) {
|
|
|
415
415
|
return (
|
|
416
416
|
<>
|
|
417
417
|
<style>{SPLIT_CSS}</style>
|
|
418
|
-
<div className="tn-ml-split" style={column(430)}>
|
|
419
|
-
<div className="tn-ml-split-art" style={{ display: "flex", justifyContent: "center", width: "100%" }}>
|
|
418
|
+
<div className="fg:tn-ml-split" style={column(430)}>
|
|
419
|
+
<div className="fg:tn-ml-split-art" style={{ display: "flex", justifyContent: "center", width: "100%" }}>
|
|
420
420
|
{parts.artwork(250)}
|
|
421
421
|
</div>
|
|
422
|
-
<div className="tn-ml-split-text" style={{ width: "100%", display: "flex", flexDirection: "column" }}>
|
|
422
|
+
<div className="fg:tn-ml-split-text" style={{ width: "100%", display: "flex", flexDirection: "column" }}>
|
|
423
423
|
<Heading parts={parts} align="left" size={27} />
|
|
424
424
|
<ListHeading parts={parts} />
|
|
425
425
|
<ServiceRows parts={parts} surface={parts.t.surface} />
|