@tribe-nest/forge 3.34.0 → 3.35.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 +3 -2
- package/src/client/_tests/tenantHeaders.spec.ts +77 -0
- package/src/client/createForgeClient.ts +37 -0
- package/src/data/queries/useFilmPlaybackSession.ts +224 -0
- package/src/data/queries/useFilms.ts +561 -0
- package/src/i18n/de.json +128 -28
- package/src/i18n/en.json +128 -28
- package/src/index.ts +5 -0
- package/src/provider/ForgeAppProvider.tsx +10 -0
- package/src/provider/ForgeProvider.tsx +18 -3
- package/src/server/index.ts +76 -17
- package/src/ui/headless/film/FilmWatermark.tsx +180 -0
- package/src/ui/headless/film/_tests/filmRules.spec.ts +531 -0
- package/src/ui/headless/film/_tests/useStageFullscreen.spec.ts +167 -0
- package/src/ui/headless/film/index.ts +36 -0
- package/src/ui/headless/film/useFilmCatalog.ts +66 -0
- package/src/ui/headless/film/useFilmPlayback.ts +497 -0
- package/src/ui/headless/film/useFilmRentalFlow.ts +277 -0
- package/src/ui/headless/film/useStageFullscreen.ts +156 -0
- package/src/ui/headless/index.ts +6 -9
- package/src/ui/index.ts +18 -16
- package/src/ui/media/CallStage.tsx +59 -1
- package/src/ui/media/CallWindowNotice.tsx +94 -0
- package/src/ui/media/_tests/CallWindowNotice.spec.tsx +83 -0
- package/src/ui/media/index.ts +9 -0
- package/src/ui/styled/AccountDashboard.tsx +28 -36
- package/src/ui/styled/FilmCatalog.tsx +278 -0
- package/src/ui/styled/FilmDetail.tsx +661 -0
- package/src/ui/styled/FilmLibrary.tsx +254 -0
- package/src/ui/styled/FilmWatch.tsx +701 -0
- package/src/ui/styled/_tests/AccountDashboardBookingCall.spec.tsx +28 -0
- package/src/ui/styled/forge-utilities.css +3 -0
- package/src/utils/structuredData.ts +88 -17
package/src/i18n/de.json
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"forge.account_dashboard.booking_cancel_error": "Wir konnten diese Session nicht stornieren. Bitte versuche es erneut.",
|
|
5
5
|
"forge.account_dashboard.booking_duration": "{minutes} Minuten",
|
|
6
6
|
"forge.account_dashboard.booking_join_label": "Teilnehmen:",
|
|
7
|
-
"forge.account_dashboard.booking_video_call": "Videoanruf auf dieser Website",
|
|
8
7
|
"forge.account_dashboard.booking_join_video": "Videoanruf beitreten",
|
|
9
|
-
"forge.account_dashboard.
|
|
8
|
+
"forge.account_dashboard.booking_video_call": "Videoanruf auf dieser Website",
|
|
10
9
|
"forge.account_dashboard.booking_video_closed": "Videoanruf: Der Raum dieser Session ist geschlossen",
|
|
10
|
+
"forge.account_dashboard.booking_video_opens_at": "Videoanruf: Der Raum öffnet {datetime}",
|
|
11
11
|
"forge.account_dashboard.booking_where_label": "Wo:",
|
|
12
12
|
"forge.account_dashboard.cancel": "Abbrechen",
|
|
13
13
|
"forge.account_dashboard.cancel_confirm_confirm": "Mitgliedschaft kündigen",
|
|
@@ -187,41 +187,45 @@
|
|
|
187
187
|
"forge.bundle_confirmation.tickets_emailed": "Tickets per E-Mail an dich unterwegs",
|
|
188
188
|
"forge.bundle_confirmation.title": "Alles erledigt",
|
|
189
189
|
"forge.bundle_confirmation.view_orders": "Bestellungen ansehen",
|
|
190
|
-
"forge.call_stage.
|
|
190
|
+
"forge.call_stage.camera_start": "Kamera starten",
|
|
191
|
+
"forge.call_stage.camera_stop": "Kamera stoppen",
|
|
192
|
+
"forge.call_stage.head_count": "In diesem Anruf: {count}",
|
|
193
|
+
"forge.call_stage.help_camera_stop": "Dein Bild wird nicht mehr gesendet: Die anderen sehen stattdessen deinen Namen. Der Browser hält die Kamera geöffnet, damit das erneute Starten sofort geht und nicht ein zweites Mal um Erlaubnis fragt. Deshalb kann die Kameraleuchte anbleiben, bis du den Anruf verlässt.",
|
|
194
|
+
"forge.call_stage.help_camera_stop_label": "Was macht „Kamera stoppen“?",
|
|
195
|
+
"forge.call_stage.help_hear_not_see": "Eine Kachel mit Namen, aber ohne Bild bedeutet, dass diese Person ihre Kamera ausgeschaltet hat oder dass der Anruf so voll ist, dass nur die gerade sprechenden Personen in voller Größe übertragen werden. Beides ist normal, und der Ton ist davon nicht betroffen.",
|
|
196
|
+
"forge.call_stage.help_hear_not_see_label": "Warum höre ich jemanden, sehe ihn aber nicht?",
|
|
197
|
+
"forge.call_stage.help_screen": "Was auch immer du im Browser-Dialog auswählst, live an alle im Anruf. Deine Kamera läuft dabei weiter, statt ersetzt zu werden, sodass dich die anderen weiterhin sehen können, während du präsentierst.",
|
|
198
|
+
"forge.call_stage.help_screen_label": "Was wird beim Teilen meines Bildschirms gesendet?",
|
|
199
|
+
"forge.call_stage.leave": "Verlassen",
|
|
200
|
+
"forge.call_stage.listening": "(hört zu)",
|
|
201
|
+
"forge.call_stage.lost_camera": "Deine Kamera wurde ausgeschaltet, als die Verbindung abbrach. Starte sie erneut, um wieder gesehen zu werden.",
|
|
202
|
+
"forge.call_stage.lost_microphone": "Dein Mikrofon wurde ausgeschaltet, als die Verbindung abbrach. Hebe die Stummschaltung auf, um wieder gehört zu werden.",
|
|
203
|
+
"forge.call_stage.lost_screen": "Deine Bildschirmfreigabe wurde beendet, als die Verbindung abbrach. Teile ihn erneut, um fortzufahren.",
|
|
204
|
+
"forge.call_stage.mute": "Stummschalten",
|
|
205
|
+
"forge.call_stage.recording": "Dieser Anruf wird aufgezeichnet",
|
|
206
|
+
"forge.call_stage.retry": "Erneut versuchen",
|
|
207
|
+
"forge.call_stage.screen_start": "Bildschirm teilen",
|
|
208
|
+
"forge.call_stage.screen_stop": "Teilen beenden",
|
|
191
209
|
"forge.call_stage.status_connected": "Verbunden",
|
|
192
210
|
"forge.call_stage.status_connecting": "Verbindung zum Anruf wird hergestellt",
|
|
211
|
+
"forge.call_stage.status_ended": "Der Anruf ist beendet",
|
|
193
212
|
"forge.call_stage.status_left": "Du hast den Anruf verlassen",
|
|
194
|
-
"forge.call_stage.
|
|
195
|
-
"forge.call_stage.
|
|
196
|
-
"forge.call_stage.status_moving_detail": "Das dauert ein paar Sekunden, und niemand sonst wird getrennt.",
|
|
213
|
+
"forge.call_stage.status_lost": "Verbindung unterbrochen",
|
|
214
|
+
"forge.call_stage.status_lost_detail": "Alle anderen sind weiterhin im Anruf. Versuche es erneut, um wieder beizutreten.",
|
|
197
215
|
"forge.call_stage.status_move_failed": "Du konntest nicht auf einen anderen Server verschoben werden",
|
|
198
216
|
"forge.call_stage.status_move_failed_detail": "Niemand sonst wurde getrennt. Versuche es erneut, um wieder beizutreten.",
|
|
217
|
+
"forge.call_stage.status_moving": "Du wirst auf einen anderen Server verschoben",
|
|
218
|
+
"forge.call_stage.status_moving_detail": "Das dauert ein paar Sekunden, und niemand sonst wird getrennt.",
|
|
199
219
|
"forge.call_stage.status_reconnecting": "Verbindung unterbrochen, wird wiederhergestellt",
|
|
200
220
|
"forge.call_stage.status_reconnecting_detail": "Alle anderen sind weiterhin im Anruf.",
|
|
201
|
-
"forge.call_stage.
|
|
202
|
-
"forge.call_stage.
|
|
203
|
-
"forge.call_stage.retry": "Erneut versuchen",
|
|
204
|
-
"forge.call_stage.recording": "Dieser Anruf wird aufgezeichnet",
|
|
205
|
-
"forge.call_stage.head_count": "In diesem Anruf: {count}",
|
|
206
|
-
"forge.call_stage.help_hear_not_see_label": "Warum höre ich jemanden, sehe ihn aber nicht?",
|
|
207
|
-
"forge.call_stage.help_hear_not_see": "Eine Kachel mit Namen, aber ohne Bild bedeutet, dass diese Person ihre Kamera ausgeschaltet hat oder dass der Anruf so voll ist, dass nur die gerade sprechenden Personen in voller Größe übertragen werden. Beides ist normal, und der Ton ist davon nicht betroffen.",
|
|
208
|
-
"forge.call_stage.listening": "(hört zu)",
|
|
221
|
+
"forge.call_stage.status_refused": "Beitritt zu diesem Anruf nicht möglich",
|
|
222
|
+
"forge.call_stage.unmute": "Stummschaltung aufheben",
|
|
209
223
|
"forge.call_stage.you": "Du",
|
|
210
224
|
"forge.call_stage.you_not_connected": "Du (noch nicht verbunden)",
|
|
211
|
-
"forge.
|
|
212
|
-
"forge.
|
|
213
|
-
"forge.
|
|
214
|
-
"forge.
|
|
215
|
-
"forge.call_stage.help_camera_stop_label": "Was macht „Kamera stoppen“?",
|
|
216
|
-
"forge.call_stage.help_camera_stop": "Dein Bild wird nicht mehr gesendet: Die anderen sehen stattdessen deinen Namen. Der Browser hält die Kamera geöffnet, damit das erneute Starten sofort geht und nicht ein zweites Mal um Erlaubnis fragt. Deshalb kann die Kameraleuchte anbleiben, bis du den Anruf verlässt.",
|
|
217
|
-
"forge.call_stage.screen_start": "Bildschirm teilen",
|
|
218
|
-
"forge.call_stage.screen_stop": "Teilen beenden",
|
|
219
|
-
"forge.call_stage.help_screen_label": "Was wird beim Teilen meines Bildschirms gesendet?",
|
|
220
|
-
"forge.call_stage.help_screen": "Was auch immer du im Browser-Dialog auswählst, live an alle im Anruf. Deine Kamera läuft dabei weiter, statt ersetzt zu werden, sodass dich die anderen weiterhin sehen können, während du präsentierst.",
|
|
221
|
-
"forge.call_stage.leave": "Verlassen",
|
|
222
|
-
"forge.call_stage.lost_microphone": "Dein Mikrofon wurde ausgeschaltet, als die Verbindung abbrach. Hebe die Stummschaltung auf, um wieder gehört zu werden.",
|
|
223
|
-
"forge.call_stage.lost_camera": "Deine Kamera wurde ausgeschaltet, als die Verbindung abbrach. Starte sie erneut, um wieder gesehen zu werden.",
|
|
224
|
-
"forge.call_stage.lost_screen": "Deine Bildschirmfreigabe wurde beendet, als die Verbindung abbrach. Teile ihn erneut, um fortzufahren.",
|
|
225
|
+
"forge.call_window.ended": "Dieser Anruf ist beendet",
|
|
226
|
+
"forge.call_window.ended_detail": "Der Raum für diese Session ist geschlossen. Wenn das nicht stimmt, wenden Sie sich an die Person, bei der Sie gebucht haben.",
|
|
227
|
+
"forge.call_window.not_started": "Dieser Anruf hat noch nicht begonnen",
|
|
228
|
+
"forge.call_window.opens_at": "Sie können ab {datetime} beitreten. Kommen Sie dann zurück, und diese Seite lässt Sie hinein.",
|
|
225
229
|
"forge.cancellation_terms.title": "Stornobedingungen",
|
|
226
230
|
"forge.cart.aria_close": "Warenkorb schließen",
|
|
227
231
|
"forge.cart.aria_open": "Warenkorb öffnen",
|
|
@@ -728,6 +732,102 @@
|
|
|
728
732
|
"forge.event_waitlist.title": "Warteliste",
|
|
729
733
|
"forge.events_list.empty_title": "Keine kommenden Events.",
|
|
730
734
|
"forge.events_list.virtual": "Online",
|
|
735
|
+
"forge.film_catalog.all_kinds": "Filme und Serien",
|
|
736
|
+
"forge.film_catalog.badge_film": "Film",
|
|
737
|
+
"forge.film_catalog.badge_series": "Serie",
|
|
738
|
+
"forge.film_catalog.empty": "Hier gibt es noch nichts zu sehen.",
|
|
739
|
+
"forge.film_catalog.episodes_available": "{count} Folgen verfügbar",
|
|
740
|
+
"forge.film_catalog.filter_kind": "Nach Art filtern",
|
|
741
|
+
"forge.film_catalog.free_preview": "Gratis-Folge",
|
|
742
|
+
"forge.film_catalog.kind_films": "Filme",
|
|
743
|
+
"forge.film_catalog.kind_series": "Serien",
|
|
744
|
+
"forge.film_catalog.load_error": "Diese Titel konnten nicht geladen werden. Bitte versuche es erneut.",
|
|
745
|
+
"forge.film_catalog.next": "Weiter",
|
|
746
|
+
"forge.film_catalog.not_enabled": "Leihen ist auf dieser Seite nicht aktiviert.",
|
|
747
|
+
"forge.film_catalog.not_on_sale": "Nicht im Angebot",
|
|
748
|
+
"forge.film_catalog.page_of": "Seite {page} von {total}",
|
|
749
|
+
"forge.film_catalog.previous": "Zurück",
|
|
750
|
+
"forge.film_catalog.rent_from_price": "Leihen ab {price}",
|
|
751
|
+
"forge.film_catalog.rent_price": "Leihen {price}",
|
|
752
|
+
"forge.film_catalog.search_placeholder": "Titel suchen",
|
|
753
|
+
"forge.film_detail.cancel_payment": "Abbrechen",
|
|
754
|
+
"forge.film_detail.choose_window": "Wähle deine Leihdauer",
|
|
755
|
+
"forge.film_detail.clock_help_film": "Die Zeit läuft ab dem ersten Mal, wenn du auf Play drückst, nicht ab dem Kauf. Du kannst heute kaufen und am Wochenende schauen.",
|
|
756
|
+
"forge.film_detail.clock_help_label": "Wann startet meine Leihdauer?",
|
|
757
|
+
"forge.film_detail.clock_help_series": "Eine Leihdauer gilt für die ganze Serie. Sie startet, sobald du eine beliebige Folge zum ersten Mal abspielst, und beginnt bei der nächsten Folge nicht von vorn.",
|
|
758
|
+
"forge.film_detail.confirming_body": "Das dauert ein paar Sekunden. Bitte schließe oder lade diese Seite nicht neu.",
|
|
759
|
+
"forge.film_detail.confirming_title": "Zahlung wird bestätigt",
|
|
760
|
+
"forge.film_detail.episode_unreleased": "Ab {date}",
|
|
761
|
+
"forge.film_detail.episodes": "Folgen",
|
|
762
|
+
"forge.film_detail.free_preview": "Gratis",
|
|
763
|
+
"forge.film_detail.not_found": "Dieser Titel ist nicht verfügbar.",
|
|
764
|
+
"forge.film_detail.not_on_sale": "Dieser Titel ist derzeit nicht im Angebot.",
|
|
765
|
+
"forge.film_detail.rent_film_cta": "Leihen - {price}, {window}",
|
|
766
|
+
"forge.film_detail.rent_series_cta": "Ganze Serie leihen - {price}, {window}",
|
|
767
|
+
"forge.film_detail.rental_expired": "Dein Zeitfenster ist abgelaufen.",
|
|
768
|
+
"forge.film_detail.rental_not_started": "Dein Zeitfenster beginnt, sobald du auf Play drückst.",
|
|
769
|
+
"forge.film_detail.rental_owned": "Du hast das geliehen.",
|
|
770
|
+
"forge.film_detail.rental_remaining_days": "Noch {days} T {hours} Std.",
|
|
771
|
+
"forge.film_detail.rental_remaining_hours": "Noch {hours} Std. {minutes} Min.",
|
|
772
|
+
"forge.film_detail.rental_remaining_minutes": "Noch {minutes} Min.",
|
|
773
|
+
"forge.film_detail.runtime_hm": "{hours} Std. {minutes} Min.",
|
|
774
|
+
"forge.film_detail.runtime_m": "{minutes} Min.",
|
|
775
|
+
"forge.film_detail.season": "Staffel {number}",
|
|
776
|
+
"forge.film_detail.sign_in_action": "Anmelden",
|
|
777
|
+
"forge.film_detail.sign_in_lead": "Zum Leihen brauchst du ein Konto.",
|
|
778
|
+
"forge.film_detail.trailer": "Trailer",
|
|
779
|
+
"forge.film_detail.untitled_episode": "Folge {number}",
|
|
780
|
+
"forge.film_detail.watch": "Ansehen",
|
|
781
|
+
"forge.film_detail.watch_free": "Gratis ansehen",
|
|
782
|
+
"forge.film_detail.watch_now": "Jetzt ansehen",
|
|
783
|
+
"forge.film_detail.window_days": "Leihdauer {days} Tage",
|
|
784
|
+
"forge.film_detail.window_hours": "Leihdauer {hours} Stunden",
|
|
785
|
+
"forge.film_library.browse": "Titel ansehen",
|
|
786
|
+
"forge.film_library.empty": "Du hast noch nichts geliehen.",
|
|
787
|
+
"forge.film_library.heading": "Meine Ausleihen",
|
|
788
|
+
"forge.film_library.rent_again": "Erneut leihen",
|
|
789
|
+
"forge.film_library.resume": "Fortsetzen",
|
|
790
|
+
"forge.film_library.watch": "Ansehen",
|
|
791
|
+
"forge.film_library.window_days_left": "Noch {days} T {hours} Std.",
|
|
792
|
+
"forge.film_library.window_expired": "Dein Zeitfenster ist abgelaufen.",
|
|
793
|
+
"forge.film_library.window_hours_left": "Noch {hours} Std. {minutes} Min.",
|
|
794
|
+
"forge.film_library.window_minutes_left": "Noch {minutes} Min.",
|
|
795
|
+
"forge.film_library.window_not_started": "Noch nicht gestartet. Deine {window} beginnt, sobald du auf Play drückst.",
|
|
796
|
+
"forge.film_library.window_refunded": "Erstattet.",
|
|
797
|
+
"forge.film_watch.clock_started": "Dein Zeitfenster von {hours} Stunden läuft. Es gilt für alle Folgen, bis es abläuft.",
|
|
798
|
+
"forge.film_watch.enter_fullscreen": "Vollbild",
|
|
799
|
+
"forge.film_watch.episode_label": "Staffel {season}, Folge {number}",
|
|
800
|
+
"forge.film_watch.episode_short": "F {number}",
|
|
801
|
+
"forge.film_watch.episodes": "Folgen",
|
|
802
|
+
"forge.film_watch.exit_fullscreen": "Vollbild beenden",
|
|
803
|
+
"forge.film_watch.free": "Gratis",
|
|
804
|
+
"forge.film_watch.gate_expired_action": "Erneut leihen",
|
|
805
|
+
"forge.film_watch.gate_expired_body": "Diese Ausleihe ist beendet. Du kannst sie auf der Titelseite erneut leihen.",
|
|
806
|
+
"forge.film_watch.gate_expired_title": "Dein Zeitfenster ist abgelaufen",
|
|
807
|
+
"forge.film_watch.gate_no_rental_action": "Leihoptionen ansehen",
|
|
808
|
+
"forge.film_watch.gate_no_rental_body": "Nichts, was du besitzt, deckt diese Folge ab. Sie ist möglicherweise auch noch nicht erschienen.",
|
|
809
|
+
"forge.film_watch.gate_no_rental_title": "Du hast das nicht geliehen",
|
|
810
|
+
"forge.film_watch.gate_sign_in_action": "Anmelden",
|
|
811
|
+
"forge.film_watch.gate_sign_in_body": "Ausleihen sind an dein Konto gebunden. Melde dich an, danach landest du direkt wieder hier.",
|
|
812
|
+
"forge.film_watch.gate_sign_in_title": "Zum Ansehen anmelden",
|
|
813
|
+
"forge.film_watch.gate_unavailable_action": "Zurück zum Titel",
|
|
814
|
+
"forge.film_watch.gate_unavailable_body": "Auf dem Weg zum Stream ist etwas schiefgelaufen. Bitte versuche es gleich noch einmal.",
|
|
815
|
+
"forge.film_watch.gate_unavailable_title": "Das kann gerade nicht abgespielt werden",
|
|
816
|
+
"forge.film_watch.gate_wrong_account_action": "Abmelden und Konto wechseln",
|
|
817
|
+
"forge.film_watch.gate_wrong_account_body": "Melde dich ab und wieder mit dem Konto an, das diesen Titel geliehen hat.",
|
|
818
|
+
"forge.film_watch.gate_wrong_account_title": "Diese Ausleihe gehört zu einem anderen Konto",
|
|
819
|
+
"forge.film_watch.play_next": "Nächste Folge abspielen",
|
|
820
|
+
"forge.film_watch.season": "Staffel {number}",
|
|
821
|
+
"forge.film_watch.signed_in_as": "Angemeldet als",
|
|
822
|
+
"forge.film_watch.taken_over_action": "Stattdessen hier ansehen",
|
|
823
|
+
"forge.film_watch.taken_over_body": "Diese Ausleihe wird gerade auf einem anderen Gerät oder Tab angesehen. Nur ein Stream gleichzeitig.",
|
|
824
|
+
"forge.film_watch.taken_over_title": "Läuft woanders",
|
|
825
|
+
"forge.film_watch.time_left_days": "Noch {days} T {hours} Std.",
|
|
826
|
+
"forge.film_watch.time_left_hours": "Noch {hours} Std. {minutes} Min.",
|
|
827
|
+
"forge.film_watch.time_left_minutes": "Noch {minutes} Min.",
|
|
828
|
+
"forge.film_watch.unavailable": "Diese Folge kann gerade nicht abgespielt werden.",
|
|
829
|
+
"forge.film_watch.up_next": "Als Nächstes: {title}",
|
|
830
|
+
"forge.film_watch.window_not_started": "Dein Zeitfenster startet jetzt.",
|
|
731
831
|
"forge.forgot_password_form.back_to_login": "Zurück zur Anmeldung",
|
|
732
832
|
"forge.forgot_password_form.email_label": "E-Mail",
|
|
733
833
|
"forge.forgot_password_form.email_placeholder": "E-Mail",
|
package/src/i18n/en.json
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"forge.account_dashboard.booking_cancel_error": "We could not cancel this session. Please try again.",
|
|
5
5
|
"forge.account_dashboard.booking_duration": "{minutes} minutes",
|
|
6
6
|
"forge.account_dashboard.booking_join_label": "Join:",
|
|
7
|
-
"forge.account_dashboard.booking_video_call": "Video call on this site",
|
|
8
7
|
"forge.account_dashboard.booking_join_video": "Join video call",
|
|
9
|
-
"forge.account_dashboard.
|
|
8
|
+
"forge.account_dashboard.booking_video_call": "Video call on this site",
|
|
10
9
|
"forge.account_dashboard.booking_video_closed": "Video call: this session's room has closed",
|
|
10
|
+
"forge.account_dashboard.booking_video_opens_at": "Video call: the room opens {datetime}",
|
|
11
11
|
"forge.account_dashboard.booking_where_label": "Where:",
|
|
12
12
|
"forge.account_dashboard.cancel": "Cancel",
|
|
13
13
|
"forge.account_dashboard.cancel_confirm_confirm": "Cancel membership",
|
|
@@ -187,41 +187,45 @@
|
|
|
187
187
|
"forge.bundle_confirmation.tickets_emailed": "Tickets emailed to you",
|
|
188
188
|
"forge.bundle_confirmation.title": "You're all set",
|
|
189
189
|
"forge.bundle_confirmation.view_orders": "View your orders",
|
|
190
|
-
"forge.call_stage.
|
|
190
|
+
"forge.call_stage.camera_start": "Start camera",
|
|
191
|
+
"forge.call_stage.camera_stop": "Stop camera",
|
|
192
|
+
"forge.call_stage.head_count": "In this call: {count}",
|
|
193
|
+
"forge.call_stage.help_camera_stop": "It stops sending your picture: the others see your name instead. The browser keeps the camera open so that starting it again is instant and does not ask for permission a second time, which means its indicator light can stay on until you leave the call.",
|
|
194
|
+
"forge.call_stage.help_camera_stop_label": "What does Stop camera do?",
|
|
195
|
+
"forge.call_stage.help_hear_not_see": "A tile with a name and no picture means that person has their camera off, or the call is busy enough that only the people currently speaking are sent in full. Both are normal, and their audio is unaffected.",
|
|
196
|
+
"forge.call_stage.help_hear_not_see_label": "Why can I hear someone but not see them?",
|
|
197
|
+
"forge.call_stage.help_screen": "Whatever you pick in the browser prompt, live, to everyone in the call. Your camera keeps running alongside it rather than being replaced, so people can still see you while you present.",
|
|
198
|
+
"forge.call_stage.help_screen_label": "What does sharing my screen send?",
|
|
199
|
+
"forge.call_stage.leave": "Leave",
|
|
200
|
+
"forge.call_stage.listening": "(listening)",
|
|
201
|
+
"forge.call_stage.lost_camera": "Your camera was turned off when the connection dropped. Start it again to be seen.",
|
|
202
|
+
"forge.call_stage.lost_microphone": "Your microphone was turned off when the connection dropped. Unmute to be heard again.",
|
|
203
|
+
"forge.call_stage.lost_screen": "Your screen share ended when the connection dropped. Share it again to continue.",
|
|
204
|
+
"forge.call_stage.mute": "Mute",
|
|
205
|
+
"forge.call_stage.recording": "This call is being recorded",
|
|
206
|
+
"forge.call_stage.retry": "Try again",
|
|
207
|
+
"forge.call_stage.screen_start": "Share screen",
|
|
208
|
+
"forge.call_stage.screen_stop": "Stop sharing",
|
|
191
209
|
"forge.call_stage.status_connected": "Connected",
|
|
192
210
|
"forge.call_stage.status_connecting": "Connecting to the call",
|
|
211
|
+
"forge.call_stage.status_ended": "The call has ended",
|
|
193
212
|
"forge.call_stage.status_left": "You have left the call",
|
|
194
|
-
"forge.call_stage.
|
|
195
|
-
"forge.call_stage.
|
|
196
|
-
"forge.call_stage.status_moving_detail": "This takes a few seconds and nobody else is dropped.",
|
|
213
|
+
"forge.call_stage.status_lost": "Connection lost",
|
|
214
|
+
"forge.call_stage.status_lost_detail": "Everyone else is still in the call. Try again to rejoin.",
|
|
197
215
|
"forge.call_stage.status_move_failed": "Could not move you to another server",
|
|
198
216
|
"forge.call_stage.status_move_failed_detail": "Nobody else was dropped. Try again to rejoin.",
|
|
217
|
+
"forge.call_stage.status_moving": "Moving you to another server",
|
|
218
|
+
"forge.call_stage.status_moving_detail": "This takes a few seconds and nobody else is dropped.",
|
|
199
219
|
"forge.call_stage.status_reconnecting": "Connection lost, reconnecting",
|
|
200
220
|
"forge.call_stage.status_reconnecting_detail": "Everyone else is still in the call.",
|
|
201
|
-
"forge.call_stage.
|
|
202
|
-
"forge.call_stage.
|
|
203
|
-
"forge.call_stage.retry": "Try again",
|
|
204
|
-
"forge.call_stage.recording": "This call is being recorded",
|
|
205
|
-
"forge.call_stage.head_count": "In this call: {count}",
|
|
206
|
-
"forge.call_stage.help_hear_not_see_label": "Why can I hear someone but not see them?",
|
|
207
|
-
"forge.call_stage.help_hear_not_see": "A tile with a name and no picture means that person has their camera off, or the call is busy enough that only the people currently speaking are sent in full. Both are normal, and their audio is unaffected.",
|
|
208
|
-
"forge.call_stage.listening": "(listening)",
|
|
221
|
+
"forge.call_stage.status_refused": "Could not join this call",
|
|
222
|
+
"forge.call_stage.unmute": "Unmute",
|
|
209
223
|
"forge.call_stage.you": "You",
|
|
210
224
|
"forge.call_stage.you_not_connected": "You (not connected yet)",
|
|
211
|
-
"forge.
|
|
212
|
-
"forge.
|
|
213
|
-
"forge.
|
|
214
|
-
"forge.
|
|
215
|
-
"forge.call_stage.help_camera_stop_label": "What does Stop camera do?",
|
|
216
|
-
"forge.call_stage.help_camera_stop": "It stops sending your picture: the others see your name instead. The browser keeps the camera open so that starting it again is instant and does not ask for permission a second time, which means its indicator light can stay on until you leave the call.",
|
|
217
|
-
"forge.call_stage.screen_start": "Share screen",
|
|
218
|
-
"forge.call_stage.screen_stop": "Stop sharing",
|
|
219
|
-
"forge.call_stage.help_screen_label": "What does sharing my screen send?",
|
|
220
|
-
"forge.call_stage.help_screen": "Whatever you pick in the browser prompt, live, to everyone in the call. Your camera keeps running alongside it rather than being replaced, so people can still see you while you present.",
|
|
221
|
-
"forge.call_stage.leave": "Leave",
|
|
222
|
-
"forge.call_stage.lost_microphone": "Your microphone was turned off when the connection dropped. Unmute to be heard again.",
|
|
223
|
-
"forge.call_stage.lost_camera": "Your camera was turned off when the connection dropped. Start it again to be seen.",
|
|
224
|
-
"forge.call_stage.lost_screen": "Your screen share ended when the connection dropped. Share it again to continue.",
|
|
225
|
+
"forge.call_window.ended": "This call has ended",
|
|
226
|
+
"forge.call_window.ended_detail": "The room for this session is closed. If you think that is wrong, contact whoever you booked with.",
|
|
227
|
+
"forge.call_window.not_started": "This call has not started yet",
|
|
228
|
+
"forge.call_window.opens_at": "You can join from {datetime}. Come back then and this page will let you in.",
|
|
225
229
|
"forge.cancellation_terms.title": "Cancellation policy",
|
|
226
230
|
"forge.cart.aria_close": "Close cart",
|
|
227
231
|
"forge.cart.aria_open": "Open cart",
|
|
@@ -728,6 +732,102 @@
|
|
|
728
732
|
"forge.event_waitlist.title": "Waitlist",
|
|
729
733
|
"forge.events_list.empty_title": "No upcoming events.",
|
|
730
734
|
"forge.events_list.virtual": "Virtual",
|
|
735
|
+
"forge.film_catalog.all_kinds": "Films and series",
|
|
736
|
+
"forge.film_catalog.badge_film": "Film",
|
|
737
|
+
"forge.film_catalog.badge_series": "Series",
|
|
738
|
+
"forge.film_catalog.empty": "Nothing to watch here yet.",
|
|
739
|
+
"forge.film_catalog.episodes_available": "{count} episodes available",
|
|
740
|
+
"forge.film_catalog.filter_kind": "Filter by type",
|
|
741
|
+
"forge.film_catalog.free_preview": "Free episode",
|
|
742
|
+
"forge.film_catalog.kind_films": "Films",
|
|
743
|
+
"forge.film_catalog.kind_series": "Series",
|
|
744
|
+
"forge.film_catalog.load_error": "These titles could not be loaded. Please try again.",
|
|
745
|
+
"forge.film_catalog.next": "Next",
|
|
746
|
+
"forge.film_catalog.not_enabled": "Rentals are not switched on for this site.",
|
|
747
|
+
"forge.film_catalog.not_on_sale": "Not on sale",
|
|
748
|
+
"forge.film_catalog.page_of": "Page {page} of {total}",
|
|
749
|
+
"forge.film_catalog.previous": "Previous",
|
|
750
|
+
"forge.film_catalog.rent_from_price": "Rent from {price}",
|
|
751
|
+
"forge.film_catalog.rent_price": "Rent {price}",
|
|
752
|
+
"forge.film_catalog.search_placeholder": "Search titles",
|
|
753
|
+
"forge.film_detail.cancel_payment": "Cancel",
|
|
754
|
+
"forge.film_detail.choose_window": "Choose your rental window",
|
|
755
|
+
"forge.film_detail.clock_help_film": "The clock starts the first time you press play, not when you pay. You can buy today and watch at the weekend.",
|
|
756
|
+
"forge.film_detail.clock_help_label": "When does my rental clock start?",
|
|
757
|
+
"forge.film_detail.clock_help_series": "One clock covers the whole series. It starts the first time you press play on any episode and it does not restart when you move to the next one.",
|
|
758
|
+
"forge.film_detail.confirming_body": "This takes a few seconds. Please do not close or reload this page.",
|
|
759
|
+
"forge.film_detail.confirming_title": "Confirming your payment",
|
|
760
|
+
"forge.film_detail.episode_unreleased": "Out {date}",
|
|
761
|
+
"forge.film_detail.episodes": "Episodes",
|
|
762
|
+
"forge.film_detail.free_preview": "Free",
|
|
763
|
+
"forge.film_detail.not_found": "This title is not available.",
|
|
764
|
+
"forge.film_detail.not_on_sale": "This title is not on sale right now.",
|
|
765
|
+
"forge.film_detail.rent_film_cta": "Rent - {price}, {window}",
|
|
766
|
+
"forge.film_detail.rent_series_cta": "Rent the whole series - {price}, {window}",
|
|
767
|
+
"forge.film_detail.rental_expired": "Your window has closed.",
|
|
768
|
+
"forge.film_detail.rental_not_started": "Your window starts when you press play.",
|
|
769
|
+
"forge.film_detail.rental_owned": "You rented this.",
|
|
770
|
+
"forge.film_detail.rental_remaining_days": "{days}d {hours}h left.",
|
|
771
|
+
"forge.film_detail.rental_remaining_hours": "{hours}h {minutes}m left.",
|
|
772
|
+
"forge.film_detail.rental_remaining_minutes": "{minutes}m left.",
|
|
773
|
+
"forge.film_detail.runtime_hm": "{hours}h {minutes}m",
|
|
774
|
+
"forge.film_detail.runtime_m": "{minutes}m",
|
|
775
|
+
"forge.film_detail.season": "Season {number}",
|
|
776
|
+
"forge.film_detail.sign_in_action": "Sign in",
|
|
777
|
+
"forge.film_detail.sign_in_lead": "Renting needs an account.",
|
|
778
|
+
"forge.film_detail.trailer": "Trailer",
|
|
779
|
+
"forge.film_detail.untitled_episode": "Episode {number}",
|
|
780
|
+
"forge.film_detail.watch": "Watch",
|
|
781
|
+
"forge.film_detail.watch_free": "Watch free",
|
|
782
|
+
"forge.film_detail.watch_now": "Watch now",
|
|
783
|
+
"forge.film_detail.window_days": "{days}-day rental",
|
|
784
|
+
"forge.film_detail.window_hours": "{hours}-hour rental",
|
|
785
|
+
"forge.film_library.browse": "Browse titles",
|
|
786
|
+
"forge.film_library.empty": "You have not rented anything yet.",
|
|
787
|
+
"forge.film_library.heading": "My rentals",
|
|
788
|
+
"forge.film_library.rent_again": "Rent again",
|
|
789
|
+
"forge.film_library.resume": "Resume",
|
|
790
|
+
"forge.film_library.watch": "Watch",
|
|
791
|
+
"forge.film_library.window_days_left": "{days}d {hours}h left",
|
|
792
|
+
"forge.film_library.window_expired": "Your window has closed.",
|
|
793
|
+
"forge.film_library.window_hours_left": "{hours}h {minutes}m left",
|
|
794
|
+
"forge.film_library.window_minutes_left": "{minutes}m left",
|
|
795
|
+
"forge.film_library.window_not_started": "Not started. Your {window} begins when you press play.",
|
|
796
|
+
"forge.film_library.window_refunded": "Refunded.",
|
|
797
|
+
"forge.film_watch.clock_started": "Your {hours}-hour window has started. It covers every episode until it runs out.",
|
|
798
|
+
"forge.film_watch.enter_fullscreen": "Fullscreen",
|
|
799
|
+
"forge.film_watch.episode_label": "Season {season}, episode {number}",
|
|
800
|
+
"forge.film_watch.episode_short": "Ep {number}",
|
|
801
|
+
"forge.film_watch.episodes": "Episodes",
|
|
802
|
+
"forge.film_watch.exit_fullscreen": "Exit fullscreen",
|
|
803
|
+
"forge.film_watch.free": "Free",
|
|
804
|
+
"forge.film_watch.gate_expired_action": "Rent again",
|
|
805
|
+
"forge.film_watch.gate_expired_body": "This rental has run out. You can rent it again from the title page.",
|
|
806
|
+
"forge.film_watch.gate_expired_title": "Your window has closed",
|
|
807
|
+
"forge.film_watch.gate_no_rental_action": "See rental options",
|
|
808
|
+
"forge.film_watch.gate_no_rental_body": "Nothing you hold covers this episode. It may also not have been released yet.",
|
|
809
|
+
"forge.film_watch.gate_no_rental_title": "You have not rented this",
|
|
810
|
+
"forge.film_watch.gate_sign_in_action": "Sign in",
|
|
811
|
+
"forge.film_watch.gate_sign_in_body": "Rentals are tied to your account. Sign in and you will come straight back here.",
|
|
812
|
+
"forge.film_watch.gate_sign_in_title": "Sign in to watch",
|
|
813
|
+
"forge.film_watch.gate_unavailable_action": "Back to the title",
|
|
814
|
+
"forge.film_watch.gate_unavailable_body": "Something went wrong on the way to the stream. Please try again in a moment.",
|
|
815
|
+
"forge.film_watch.gate_unavailable_title": "This cannot be played right now",
|
|
816
|
+
"forge.film_watch.gate_wrong_account_action": "Sign out and switch account",
|
|
817
|
+
"forge.film_watch.gate_wrong_account_body": "Sign out and sign back in with the account that rented this.",
|
|
818
|
+
"forge.film_watch.gate_wrong_account_title": "This rental belongs to a different account",
|
|
819
|
+
"forge.film_watch.play_next": "Play next episode",
|
|
820
|
+
"forge.film_watch.season": "Season {number}",
|
|
821
|
+
"forge.film_watch.signed_in_as": "Signed in as",
|
|
822
|
+
"forge.film_watch.taken_over_action": "Watch here instead",
|
|
823
|
+
"forge.film_watch.taken_over_body": "This rental is being watched on another device or tab. One stream at a time.",
|
|
824
|
+
"forge.film_watch.taken_over_title": "Playing somewhere else",
|
|
825
|
+
"forge.film_watch.time_left_days": "{days}d {hours}h left",
|
|
826
|
+
"forge.film_watch.time_left_hours": "{hours}h {minutes}m left",
|
|
827
|
+
"forge.film_watch.time_left_minutes": "{minutes}m left",
|
|
828
|
+
"forge.film_watch.unavailable": "This episode cannot be played right now.",
|
|
829
|
+
"forge.film_watch.up_next": "Up next: {title}",
|
|
830
|
+
"forge.film_watch.window_not_started": "Your window starts now.",
|
|
731
831
|
"forge.forgot_password_form.back_to_login": "Back to login",
|
|
732
832
|
"forge.forgot_password_form.email_label": "Email",
|
|
733
833
|
"forge.forgot_password_form.email_placeholder": "Email",
|
package/src/index.ts
CHANGED
|
@@ -110,6 +110,11 @@ export * from "./data/queries/useReplay";
|
|
|
110
110
|
export * from "./data/queries/useCoachingAvailability";
|
|
111
111
|
export * from "./data/queries/useCohortPage";
|
|
112
112
|
export * from "./data/queries/useCourseAccess";
|
|
113
|
+
// Films and series: the catalogue, one title, the buyer's library, the rental
|
|
114
|
+
// money legs, and the three playback-session calls. Everything a custom viewing
|
|
115
|
+
// experience needs, so nobody has to invent an HTTP call to build one.
|
|
116
|
+
export * from "./data/queries/useFilms";
|
|
117
|
+
export * from "./data/queries/useFilmPlaybackSession";
|
|
113
118
|
export * from "./data/queries/useBroadcasts";
|
|
114
119
|
export * from "./data/queries/useAccount";
|
|
115
120
|
export * from "./data/queries/useMyTickets";
|
|
@@ -63,6 +63,14 @@ export interface ForgeProviderProps {
|
|
|
63
63
|
apiUrl: string;
|
|
64
64
|
/** The tenant this deploy is pinned to. */
|
|
65
65
|
profileId?: string;
|
|
66
|
+
/**
|
|
67
|
+
* WHICH of the tenant's websites this deploy is.
|
|
68
|
+
*
|
|
69
|
+
* A profile can own several, so `profileId` cannot answer "which site am I",
|
|
70
|
+
* and `subdomain` is the profile's primary rather than this deploy's. Rides
|
|
71
|
+
* every request as `x-website-id`.
|
|
72
|
+
*/
|
|
73
|
+
websiteId?: string;
|
|
66
74
|
/** The app this deploy is the frontend for (mini-apps only; baked at build). */
|
|
67
75
|
appId?: string;
|
|
68
76
|
/** The tenant subdomain (also used to fetch site config). */
|
|
@@ -104,6 +112,7 @@ export interface ForgeProviderProps {
|
|
|
104
112
|
export function ForgeProvider({
|
|
105
113
|
apiUrl,
|
|
106
114
|
profileId,
|
|
115
|
+
websiteId,
|
|
107
116
|
appId,
|
|
108
117
|
subdomain,
|
|
109
118
|
rootDomain,
|
|
@@ -125,6 +134,7 @@ export function ForgeProvider({
|
|
|
125
134
|
apiUrl={apiUrl}
|
|
126
135
|
rootDomain={rootDomain}
|
|
127
136
|
profileId={profileId}
|
|
137
|
+
websiteId={websiteId}
|
|
128
138
|
appId={appId}
|
|
129
139
|
subdomain={subdomain}
|
|
130
140
|
publishableKey={publishableKey}
|
|
@@ -16,6 +16,14 @@ export interface ForgeContextValue {
|
|
|
16
16
|
rootDomain: string;
|
|
17
17
|
/** The tenant this site is pinned to (one deploy = one profileId). */
|
|
18
18
|
profileId?: string;
|
|
19
|
+
/**
|
|
20
|
+
* WHICH of the tenant's websites this deploy is.
|
|
21
|
+
*
|
|
22
|
+
* A profile can own several, so `profileId` alone cannot answer "which site
|
|
23
|
+
* am I". Anything a site derives about ITSELF - a canonical URL, a share
|
|
24
|
+
* link - needs this rather than the profile's primary subdomain.
|
|
25
|
+
*/
|
|
26
|
+
websiteId?: string;
|
|
19
27
|
/** The app this deploy is the frontend for (mini-apps only; baked at build). */
|
|
20
28
|
appId?: string;
|
|
21
29
|
/** The tenant subdomain, when resolved. */
|
|
@@ -36,6 +44,7 @@ export interface ForgeClientProviderProps {
|
|
|
36
44
|
apiUrl: string;
|
|
37
45
|
rootDomain?: string;
|
|
38
46
|
profileId?: string;
|
|
47
|
+
websiteId?: string;
|
|
39
48
|
appId?: string;
|
|
40
49
|
subdomain?: string;
|
|
41
50
|
publishableKey?: string;
|
|
@@ -57,6 +66,7 @@ export const ForgeClientProvider = ({
|
|
|
57
66
|
apiUrl,
|
|
58
67
|
rootDomain = "",
|
|
59
68
|
profileId,
|
|
69
|
+
websiteId,
|
|
60
70
|
appId,
|
|
61
71
|
subdomain,
|
|
62
72
|
publishableKey,
|
|
@@ -80,6 +90,8 @@ export const ForgeClientProvider = ({
|
|
|
80
90
|
createForgeClient({
|
|
81
91
|
baseURL: apiUrl,
|
|
82
92
|
publishableKey,
|
|
93
|
+
websiteId,
|
|
94
|
+
profileId,
|
|
83
95
|
getToken: () => tokenRef.current,
|
|
84
96
|
// Fan/member lane (backend §7): 15-minute access tokens, refresh token
|
|
85
97
|
// rotated per exchange. On a dead chain the stored pair is cleared so
|
|
@@ -99,12 +111,15 @@ export const ForgeClientProvider = ({
|
|
|
99
111
|
},
|
|
100
112
|
},
|
|
101
113
|
}),
|
|
102
|
-
|
|
114
|
+
// `websiteId` and `profileId` are baked at build and never change at
|
|
115
|
+
// runtime, but they are READ inside the factory, so leaving them out is a
|
|
116
|
+
// stale closure the first time that stops being true.
|
|
117
|
+
[apiUrl, publishableKey, websiteId, profileId],
|
|
103
118
|
);
|
|
104
119
|
|
|
105
120
|
const value = useMemo<ForgeContextValue>(
|
|
106
|
-
() => ({ apiUrl, rootDomain, profileId, appId, subdomain, publishableKey, client, token, setToken }),
|
|
107
|
-
[apiUrl, rootDomain, profileId, appId, subdomain, publishableKey, client, token, setToken],
|
|
121
|
+
() => ({ apiUrl, rootDomain, profileId, websiteId, appId, subdomain, publishableKey, client, token, setToken }),
|
|
122
|
+
[apiUrl, rootDomain, profileId, websiteId, appId, subdomain, publishableKey, client, token, setToken],
|
|
108
123
|
);
|
|
109
124
|
|
|
110
125
|
return <ForgeContext.Provider value={value}>{children}</ForgeContext.Provider>;
|
package/src/server/index.ts
CHANGED
|
@@ -50,16 +50,17 @@ import type {
|
|
|
50
50
|
import { collectionParamsToQuery, splitCollectionQuery } from "../data/collectionParams";
|
|
51
51
|
// SEO context for structured data. Pure functions over plain data, so this is
|
|
52
52
|
// safe in the React-free server entry.
|
|
53
|
-
import {
|
|
54
|
-
seoContextFromSiteConfig,
|
|
55
|
-
type ReviewSchemaReview,
|
|
56
|
-
type SiteSeoContext,
|
|
57
|
-
} from "../utils/structuredData";
|
|
53
|
+
import { seoContextFromSiteConfig, type ReviewSchemaReview, type SiteSeoContext } from "../utils/structuredData";
|
|
58
54
|
// Also React-free, so it belongs in this entry rather than being re-derived by
|
|
59
55
|
// each route that needs share tags.
|
|
60
56
|
import { buildHeadMeta } from "../utils/headMeta";
|
|
61
57
|
export type { SiteSeoContext };
|
|
62
58
|
import type { SiteConfig } from "../data/queries/useWebsite";
|
|
59
|
+
// Type-only, so nothing from the React data layer reaches this React-free entry.
|
|
60
|
+
// Re-exported so a route loader can name the shape it got back without importing
|
|
61
|
+
// past `@tribe-nest/forge/server`.
|
|
62
|
+
import type { FilmCatalogPage, PublicFilmDetail, PublicFilmEpisode, PublicFilmSummary } from "../data/queries/useFilms";
|
|
63
|
+
export type { FilmCatalogPage, PublicFilmDetail, PublicFilmEpisode, PublicFilmSummary };
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
66
|
* One public-API GET, KEEPING the failure reason. `getJson` (below) throws that
|
|
@@ -87,7 +88,11 @@ async function getJsonResult<T>(
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
/** GET a public endpoint and parse JSON, returning `null` on any failure. */
|
|
90
|
-
async function getJson<T>(
|
|
91
|
+
async function getJson<T>(
|
|
92
|
+
apiUrl: string,
|
|
93
|
+
path: string,
|
|
94
|
+
params?: Record<string, string | undefined>,
|
|
95
|
+
): Promise<T | null> {
|
|
91
96
|
const res = await getJsonResult<T>(apiUrl, path, params);
|
|
92
97
|
return res.ok ? res.data : null;
|
|
93
98
|
}
|
|
@@ -170,10 +175,7 @@ export async function fetchEntityReviewsServer(opts: {
|
|
|
170
175
|
* site config is invisible there. Pair it with the entity fetch in one
|
|
171
176
|
* `Promise.all` and the extra round trip costs nothing wall-clock.
|
|
172
177
|
*/
|
|
173
|
-
export async function fetchSeoContextServer(opts: {
|
|
174
|
-
apiUrl: string;
|
|
175
|
-
profileId?: string;
|
|
176
|
-
}): Promise<SiteSeoContext> {
|
|
178
|
+
export async function fetchSeoContextServer(opts: { apiUrl: string; profileId?: string }): Promise<SiteSeoContext> {
|
|
177
179
|
return seoContextFromSiteConfig(await fetchSiteConfig(opts));
|
|
178
180
|
}
|
|
179
181
|
|
|
@@ -272,7 +274,11 @@ export async function fetchSiteBootstrap(opts: {
|
|
|
272
274
|
}
|
|
273
275
|
|
|
274
276
|
/** Fetch a single event by id or slug for SSR. */
|
|
275
|
-
export function fetchEventServer(opts: {
|
|
277
|
+
export function fetchEventServer(opts: {
|
|
278
|
+
apiUrl: string;
|
|
279
|
+
profileId?: string;
|
|
280
|
+
idOrSlug: string;
|
|
281
|
+
}): Promise<IEvent | null> {
|
|
276
282
|
return getJson<IEvent>(opts.apiUrl, `/public/events/${encodeURIComponent(opts.idOrSlug)}`, {
|
|
277
283
|
profileId: opts.profileId,
|
|
278
284
|
});
|
|
@@ -366,6 +372,57 @@ export function fetchCourseServer(opts: {
|
|
|
366
372
|
});
|
|
367
373
|
}
|
|
368
374
|
|
|
375
|
+
// --- Films and series (SSR) ---
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* The published film/series catalogue, for SSR of `/i/films`.
|
|
379
|
+
*
|
|
380
|
+
* Returns the whole page envelope rather than just the rows, because `enabled`
|
|
381
|
+
* is load-bearing: with the film-rentals switch off the API answers 200 with an
|
|
382
|
+
* empty list and `enabled: false`, and a catalogue page that cannot tell that
|
|
383
|
+
* apart from "this creator has no films yet" would draw the wrong empty state.
|
|
384
|
+
* A network failure degrades to `enabled: false` with an empty list, which is
|
|
385
|
+
* the same safe nothing.
|
|
386
|
+
*/
|
|
387
|
+
export async function fetchFilmsServer(opts: {
|
|
388
|
+
apiUrl: string;
|
|
389
|
+
profileId?: string;
|
|
390
|
+
page?: number;
|
|
391
|
+
limit?: number;
|
|
392
|
+
kind?: "film" | "series";
|
|
393
|
+
}): Promise<FilmCatalogPage> {
|
|
394
|
+
const res = await getJson<FilmCatalogPage>(opts.apiUrl, "/public/films", {
|
|
395
|
+
profileId: opts.profileId,
|
|
396
|
+
page: String(opts.page ?? 1),
|
|
397
|
+
limit: String(opts.limit ?? 20),
|
|
398
|
+
kind: opts.kind,
|
|
399
|
+
});
|
|
400
|
+
return res ?? { data: [], total: 0, page: opts.page ?? 1, limit: opts.limit ?? 20, enabled: false };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* One title by slug, for SSR of `/i/films/$slug`.
|
|
405
|
+
*
|
|
406
|
+
* Deliberately UNAUTHENTICATED, and that is the paywall boundary: the loader
|
|
407
|
+
* runs on the server with no fan session, so `viewerRental` is always null here
|
|
408
|
+
* and no manifest URL, key URL or `hlsPrefix` can reach the SSR payload. The
|
|
409
|
+
* signed-in view is fetched from the browser by `useFilm`, which is the only
|
|
410
|
+
* place a rental is ever resolved. Do not "improve" this by forwarding a
|
|
411
|
+
* cookie: a leak the UI happens not to render is still a leak.
|
|
412
|
+
*
|
|
413
|
+
* `null` for a draft, an archived title, an unknown slug, another tenant's slug,
|
|
414
|
+
* or while the feature switch is off - all four indistinguishable on purpose.
|
|
415
|
+
*/
|
|
416
|
+
export function fetchFilmServer(opts: {
|
|
417
|
+
apiUrl: string;
|
|
418
|
+
profileId?: string;
|
|
419
|
+
slug: string;
|
|
420
|
+
}): Promise<PublicFilmDetail | null> {
|
|
421
|
+
return getJson<PublicFilmDetail>(opts.apiUrl, `/public/films/${encodeURIComponent(opts.slug)}`, {
|
|
422
|
+
profileId: opts.profileId,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
369
426
|
/** Fetch a single coaching product by id or slug for SSR. */
|
|
370
427
|
export function fetchCoachingProductServer(opts: {
|
|
371
428
|
apiUrl: string;
|
|
@@ -403,7 +460,9 @@ export function fetchBlogCategoryServer(opts: {
|
|
|
403
460
|
|
|
404
461
|
/** All podcast shows for the profile, for SSR of the podcasts index. */
|
|
405
462
|
export async function fetchPodcastsServer(opts: { apiUrl: string; profileId?: string }): Promise<PodcastShow[]> {
|
|
406
|
-
return (
|
|
463
|
+
return (
|
|
464
|
+
(await getJson<PodcastShow[]>(opts.apiUrl, `/public/blog/posts/podcasts`, { profileId: opts.profileId })) ?? []
|
|
465
|
+
);
|
|
407
466
|
}
|
|
408
467
|
|
|
409
468
|
/** A show's episodes (audio posts) in feed order, for SSR of a show/episode page. */
|
|
@@ -459,11 +518,11 @@ export function fetchCollectionQueryServer(opts: {
|
|
|
459
518
|
query?: CollectionQuery;
|
|
460
519
|
}): Promise<CollectionSearchResult | null> {
|
|
461
520
|
const { scope, query } = splitCollectionQuery(opts.query);
|
|
462
|
-
return postJson<CollectionSearchResult>(
|
|
463
|
-
opts.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
);
|
|
521
|
+
return postJson<CollectionSearchResult>(opts.apiUrl, `/public/collections/${encodeURIComponent(opts.slug)}/query`, {
|
|
522
|
+
profileId: opts.profileId,
|
|
523
|
+
scope,
|
|
524
|
+
query,
|
|
525
|
+
});
|
|
467
526
|
}
|
|
468
527
|
|
|
469
528
|
/** Fetch a single published entry by slug for SSR. */
|