@burdenoff/microfe-hookmovies 2026.706.2 → 2026.706.3
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/dist/hookmovies/HookMoviesRoutes.js +54 -6
- package/dist/hookmovies/HookMoviesRoutes.js.map +1 -1
- package/dist/hookmovies/components/index.js +1 -1
- package/dist/hookmovies/context/HookMoviesContext.js +64 -40
- package/dist/hookmovies/context/HookMoviesContext.js.map +1 -1
- package/dist/hookmovies/hooks/useHookMoviesApi.js +592 -134
- package/dist/hookmovies/hooks/useHookMoviesApi.js.map +1 -1
- package/dist/hookmovies/pages/ai/ConciergePage.js +330 -0
- package/dist/hookmovies/pages/ai/ConciergePage.js.map +1 -0
- package/dist/hookmovies/pages/analytics/AnalyticsPage.js +450 -0
- package/dist/hookmovies/pages/analytics/AnalyticsPage.js.map +1 -0
- package/dist/hookmovies/pages/analytics/PayoutsPage.js +499 -0
- package/dist/hookmovies/pages/analytics/PayoutsPage.js.map +1 -0
- package/dist/hookmovies/pages/auditions/AuditionsPage.js +3 -3
- package/dist/hookmovies/pages/catalog/PeoplePage.js +3 -3
- package/dist/hookmovies/pages/catalog/StudiosPage.js +3 -3
- package/dist/hookmovies/pages/catalog/TitleDetailPage.js +7 -7
- package/dist/hookmovies/pages/catalog/TitlesPage.js +4 -4
- package/dist/hookmovies/pages/commerce/CheckoutPage.js +430 -0
- package/dist/hookmovies/pages/commerce/CheckoutPage.js.map +1 -0
- package/dist/hookmovies/pages/consumer/DiscoveryPage.js +7 -7
- package/dist/hookmovies/pages/consumer/HooksPage.js +5 -5
- package/dist/hookmovies/pages/consumer/TitleViewPage.js +27 -27
- package/dist/hookmovies/pages/consumer/TitleViewPage.js.map +1 -1
- package/dist/hookmovies/pages/developer/DevPortalPage.js +815 -0
- package/dist/hookmovies/pages/developer/DevPortalPage.js.map +1 -0
- package/dist/hookmovies/pages/distribution/RightsWindowsPage.js +3 -3
- package/dist/hookmovies/pages/engagement/ReviewsPage.js +201 -143
- package/dist/hookmovies/pages/engagement/ReviewsPage.js.map +1 -1
- package/dist/hookmovies/pages/library/EntitlementsPage.js +3 -3
- package/dist/hookmovies/pages/library/EntitlementsPage.js.map +1 -1
- package/dist/hookmovies/pages/library/ListsPage.js +3 -3
- package/dist/hookmovies/pages/live/LivePage.js +608 -0
- package/dist/hookmovies/pages/live/LivePage.js.map +1 -0
- package/dist/hookmovies/pages/localization/LocalizationPage.js +656 -0
- package/dist/hookmovies/pages/localization/LocalizationPage.js.map +1 -0
- package/dist/hookmovies/pages/onboarding/OnboardingPage.js +366 -0
- package/dist/hookmovies/pages/onboarding/OnboardingPage.js.map +1 -0
- package/dist/hookmovies/pages/productions/ProductionsPage.js +3 -3
- package/dist/hookmovies/pages/social/ProfilesPage.js +701 -0
- package/dist/hookmovies/pages/social/ProfilesPage.js.map +1 -0
- package/dist/hookmovies/pages/social/WatchPartyListPage.js +281 -0
- package/dist/hookmovies/pages/social/WatchPartyListPage.js.map +1 -0
- package/dist/hookmovies/pages/social/WatchPartyRoomPage.js +567 -0
- package/dist/hookmovies/pages/social/WatchPartyRoomPage.js.map +1 -0
- package/dist/hookmovies/pages/trust/ModerationPage.js +320 -0
- package/dist/hookmovies/pages/trust/ModerationPage.js.map +1 -0
- package/package.json +1 -1
|
@@ -5,8 +5,8 @@ import { StatusBadge as r } from "../../components/StatusBadge.js";
|
|
|
5
5
|
import { useAddCredit as i, useArchiveTitle as a, useCheckEntitlement as o, useClips as s, useCreateClip as c, useCreateEpisode as l, useCreateSeason as u, useDeleteClip as ee, usePeople as te, useRecordProgress as ne, useReviews as re, useStudios as ie, useTitle as ae, useTransitionTitle as oe, useUpdateTitle as se } from "../../hooks/useHookMoviesApi.js";
|
|
6
6
|
import { formatDate as ce, formatEnumLabel as d } from "../../utils/formatters.js";
|
|
7
7
|
import { FILE_FOLDERS as f, uploadFileToFiles as le, useFileUrl as p } from "../../hooks/useFiles.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { EmptyState as ue } from "../../components/EmptyState.js";
|
|
9
|
+
import { ErrorState as de, LoadingState as fe } from "../../components/StateViews.js";
|
|
10
10
|
import { Pagination as pe } from "../../components/Pagination.js";
|
|
11
11
|
import { RatingDisplay as me } from "../../components/RatingDisplay.js";
|
|
12
12
|
import { useRef as he, useState as m } from "react";
|
|
@@ -269,11 +269,11 @@ var Ie = {
|
|
|
269
269
|
}, Nt), [q, Ht] = m(""), [Ut, Wt] = m(""), [Gt, Kt] = m(""), [qt, Jt] = m(""), [Yt, Xt] = m(""), [J, Zt] = m(""), [Qt, $t] = m(""), [en, tn] = m(""), [nn, rn] = m(""), [an, on] = m(""), [sn, cn] = m(!1);
|
|
270
270
|
if (g) return /* @__PURE__ */ S(n, {
|
|
271
271
|
title: "Title",
|
|
272
|
-
children: /* @__PURE__ */ S(
|
|
272
|
+
children: /* @__PURE__ */ S(fe, { label: "Loading title…" })
|
|
273
273
|
});
|
|
274
274
|
if (v || !h) return /* @__PURE__ */ S(n, {
|
|
275
275
|
title: "Title",
|
|
276
|
-
children: /* @__PURE__ */ S(
|
|
276
|
+
children: /* @__PURE__ */ S(de, {
|
|
277
277
|
title: "Title not found",
|
|
278
278
|
message: "This title may have been archived or you may not have access.",
|
|
279
279
|
onRetry: () => void b()
|
|
@@ -1230,15 +1230,15 @@ var Ie = {
|
|
|
1230
1230
|
})
|
|
1231
1231
|
]
|
|
1232
1232
|
}),
|
|
1233
|
-
N.isLoading ? /* @__PURE__ */ S(
|
|
1233
|
+
N.isLoading ? /* @__PURE__ */ S(fe, {
|
|
1234
1234
|
label: "Loading clips…",
|
|
1235
1235
|
className: "min-h-[20vh]"
|
|
1236
|
-
}) : N.isError ? /* @__PURE__ */ S(
|
|
1236
|
+
}) : N.isError ? /* @__PURE__ */ S(de, {
|
|
1237
1237
|
title: "Could not load clips",
|
|
1238
1238
|
message: "Something went wrong fetching this title's clips.",
|
|
1239
1239
|
onRetry: () => void N.refetch(),
|
|
1240
1240
|
className: "min-h-[20vh]"
|
|
1241
|
-
}) : Q.length === 0 ? /* @__PURE__ */ S(
|
|
1241
|
+
}) : Q.length === 0 ? /* @__PURE__ */ S(ue, {
|
|
1242
1242
|
title: "No clips yet",
|
|
1243
1243
|
description: "Upload a short vertical clip to promote this title."
|
|
1244
1244
|
}) : /* @__PURE__ */ C(Fe, { children: [/* @__PURE__ */ S("div", {
|
|
@@ -4,8 +4,8 @@ import { PageLayout as n } from "../../components/PageLayout.js";
|
|
|
4
4
|
import { StatusBadge as r } from "../../components/StatusBadge.js";
|
|
5
5
|
import { useTitles as i } from "../../hooks/useHookMoviesApi.js";
|
|
6
6
|
import { formatEnumLabel as a } from "../../utils/formatters.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { EmptyState as o } from "../../components/EmptyState.js";
|
|
8
|
+
import { ErrorState as s, LoadingState as c } from "../../components/StateViews.js";
|
|
9
9
|
import { Pagination as l } from "../../components/Pagination.js";
|
|
10
10
|
import { useEffect as u, useState as d } from "react";
|
|
11
11
|
import { Film as f, Plus as p, Search as m } from "lucide-react";
|
|
@@ -113,11 +113,11 @@ var v = [
|
|
|
113
113
|
}),
|
|
114
114
|
/* @__PURE__ */ g("div", {
|
|
115
115
|
className: "mt-6",
|
|
116
|
-
children: [N ? /* @__PURE__ */ h(
|
|
116
|
+
children: [N ? /* @__PURE__ */ h(c, { label: "Loading catalog…" }) : P ? /* @__PURE__ */ h(s, {
|
|
117
117
|
title: "Could not load titles",
|
|
118
118
|
message: "The catalog service did not respond.",
|
|
119
119
|
onRetry: () => void F()
|
|
120
|
-
}) : I.length === 0 ? /* @__PURE__ */ h(
|
|
120
|
+
}) : I.length === 0 ? /* @__PURE__ */ h(o, {
|
|
121
121
|
illustration: "empty-data",
|
|
122
122
|
icon: /* @__PURE__ */ h(f, { className: "h-12 w-12" }),
|
|
123
123
|
title: "No titles found",
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { useHookMovies as e } from "../../context/HookMoviesContext.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { useCheckEntitlement as r, useCreateBillingOrder as i, useGrantEntitlement as a, useMyBillingAccounts as o, useRightsWindows as s, useTitle as c } from "../../hooks/useHookMoviesApi.js";
|
|
5
|
+
import { formatMinorUnits as l } from "../../utils/formatters.js";
|
|
6
|
+
import { useFileUrl as u } from "../../hooks/useFiles.js";
|
|
7
|
+
import { EmptyState as d } from "../../components/EmptyState.js";
|
|
8
|
+
import { ErrorState as f, LoadingState as p } from "../../components/StateViews.js";
|
|
9
|
+
import { useCallback as m, useMemo as h, useState as g } from "react";
|
|
10
|
+
import { useQuery as _ } from "@tanstack/react-query";
|
|
11
|
+
import { AlertCircle as ee, ArrowLeft as te, BadgeCheck as v, CalendarClock as y, CheckCircle2 as b, Clock as x, CreditCard as S, Film as ne, Globe as C, Info as re, Loader2 as ie, Lock as ae, Play as w, ShieldCheck as T, ShoppingBag as E, Ticket as D } from "lucide-react";
|
|
12
|
+
import { useParams as oe } from "react-router-dom";
|
|
13
|
+
import { Fragment as O, jsx as k, jsxs as A } from "react/jsx-runtime";
|
|
14
|
+
import { GlassCard as j } from "@burdenoff/fe-libs/ui";
|
|
15
|
+
import { graphqlFetch as se } from "@burdenoff/fe-libs/shared/graphql";
|
|
16
|
+
//#region src/hookmovies/pages/commerce/CheckoutPage.tsx
|
|
17
|
+
var ce = [
|
|
18
|
+
{
|
|
19
|
+
mode: "TVOD_RENT",
|
|
20
|
+
label: "Rent",
|
|
21
|
+
source: "RENTAL",
|
|
22
|
+
blurb: "Stream anytime within a 48-hour window once you press play.",
|
|
23
|
+
icon: x,
|
|
24
|
+
expiresInHours: 48
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
mode: "TVOD_BUY",
|
|
28
|
+
label: "Buy",
|
|
29
|
+
source: "PURCHASE",
|
|
30
|
+
blurb: "Own it in your library and watch it as often as you like.",
|
|
31
|
+
icon: E
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
mode: "PPV",
|
|
35
|
+
label: "Pay-per-view",
|
|
36
|
+
source: "PURCHASE",
|
|
37
|
+
blurb: "One-time access to this premium event or release.",
|
|
38
|
+
icon: D
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
mode: "SVOD",
|
|
42
|
+
label: "Subscribe",
|
|
43
|
+
source: "SUBSCRIPTION",
|
|
44
|
+
blurb: "Included for 30 days with a subscription pass to this title.",
|
|
45
|
+
icon: y,
|
|
46
|
+
expiresInHours: 720
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
mode: "FREE_AVOD",
|
|
50
|
+
label: "Watch free",
|
|
51
|
+
source: "FREE",
|
|
52
|
+
blurb: "Stream now, free with ad breaks — no payment required.",
|
|
53
|
+
icon: w,
|
|
54
|
+
free: !0
|
|
55
|
+
}
|
|
56
|
+
], le = "ACTIVE";
|
|
57
|
+
function ue(e, t) {
|
|
58
|
+
if (!e || typeof e != "object") return null;
|
|
59
|
+
let n = e[t];
|
|
60
|
+
return typeof n == "number" && Number.isFinite(n) ? String(n) : typeof n == "string" && n.trim() !== "" && Number.isFinite(Number(n)) ? n : null;
|
|
61
|
+
}
|
|
62
|
+
function M(e) {
|
|
63
|
+
return e.length === 0 || e.some((e) => [
|
|
64
|
+
"WW",
|
|
65
|
+
"WORLD",
|
|
66
|
+
"WORLDWIDE",
|
|
67
|
+
"GLOBAL"
|
|
68
|
+
].includes(e.toUpperCase())) ? "Worldwide" : e.join(", ");
|
|
69
|
+
}
|
|
70
|
+
var de = ({ offering: e, selected: n, onSelect: r }) => {
|
|
71
|
+
let { def: i, window: a, priceMinor: o } = e, s = i.icon, c = i.free ? "Free" : o === null ? "Price unavailable" : l(o, a.currency), u = !i.free && o === null;
|
|
72
|
+
return /* @__PURE__ */ A("button", {
|
|
73
|
+
type: "button",
|
|
74
|
+
onClick: r,
|
|
75
|
+
disabled: u,
|
|
76
|
+
"aria-pressed": n,
|
|
77
|
+
className: t("group relative flex w-full flex-col rounded-2xl border p-5 text-left transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", n ? "border-action-primary-bg bg-action-primary-bg/5 shadow-lg" : "border-border-seam bg-bg-surface/60 hover:border-border-strong hover:bg-bg-surface", u && "cursor-not-allowed opacity-60"),
|
|
78
|
+
children: [
|
|
79
|
+
/* @__PURE__ */ A("div", {
|
|
80
|
+
className: "mb-3 flex items-center justify-between gap-3",
|
|
81
|
+
children: [/* @__PURE__ */ k("span", {
|
|
82
|
+
className: t("flex size-10 shrink-0 items-center justify-center rounded-xl", n ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-sunken text-text-secondary"),
|
|
83
|
+
children: /* @__PURE__ */ k(s, {
|
|
84
|
+
className: "h-5 w-5",
|
|
85
|
+
"aria-hidden": "true"
|
|
86
|
+
})
|
|
87
|
+
}), n && /* @__PURE__ */ A("span", {
|
|
88
|
+
className: "inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-action-primary-bg",
|
|
89
|
+
children: [/* @__PURE__ */ k(b, {
|
|
90
|
+
className: "h-3.5 w-3.5",
|
|
91
|
+
"aria-hidden": "true"
|
|
92
|
+
}), "Selected"]
|
|
93
|
+
})]
|
|
94
|
+
}),
|
|
95
|
+
/* @__PURE__ */ k("p", {
|
|
96
|
+
className: "text-sm font-semibold text-text-primary",
|
|
97
|
+
children: i.label
|
|
98
|
+
}),
|
|
99
|
+
/* @__PURE__ */ k("p", {
|
|
100
|
+
className: "mt-1 flex-1 text-xs leading-relaxed text-text-secondary",
|
|
101
|
+
children: i.blurb
|
|
102
|
+
}),
|
|
103
|
+
/* @__PURE__ */ k("p", {
|
|
104
|
+
className: t("mt-4 text-lg font-bold tracking-tight", u ? "text-text-muted" : "text-text-primary"),
|
|
105
|
+
children: c
|
|
106
|
+
}),
|
|
107
|
+
/* @__PURE__ */ A("p", {
|
|
108
|
+
className: "mt-1 inline-flex items-center gap-1 text-[11px] text-text-secondary",
|
|
109
|
+
children: [/* @__PURE__ */ k(C, {
|
|
110
|
+
className: "h-3 w-3",
|
|
111
|
+
"aria-hidden": "true"
|
|
112
|
+
}), M(a.territories)]
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
});
|
|
116
|
+
}, N = () => {
|
|
117
|
+
let { titleId: t } = oe(), { navigateTo: y, currentUser: b, apiGatewayUrl: E } = e(), D = b?.id, { data: N, isLoading: P, isError: F, refetch: I } = c(t), L = u(N?.posterFileId), R = s({ titleId: t }, {
|
|
118
|
+
limit: 50,
|
|
119
|
+
offset: 0
|
|
120
|
+
}), z = _({
|
|
121
|
+
queryKey: [
|
|
122
|
+
"hookmovies",
|
|
123
|
+
"checkout-prices",
|
|
124
|
+
t ?? ""
|
|
125
|
+
],
|
|
126
|
+
enabled: !!t,
|
|
127
|
+
queryFn: async () => {
|
|
128
|
+
let e = await se({
|
|
129
|
+
gateway: "workspace",
|
|
130
|
+
baseUrl: E,
|
|
131
|
+
workspaceToken: !0,
|
|
132
|
+
query: "query HokCheckoutPrices($titleId: ID, $pagination: PaginationInput) {\n hokListRightsWindows(titleId: $titleId, pagination: $pagination) {\n items { id priceMinorUnits }\n }\n }",
|
|
133
|
+
variables: {
|
|
134
|
+
titleId: t,
|
|
135
|
+
pagination: {
|
|
136
|
+
limit: 50,
|
|
137
|
+
offset: 0
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
if (e.errors?.length) throw Error(e.errors[0]?.message ?? "Could not load pricing");
|
|
142
|
+
let n = {};
|
|
143
|
+
for (let t of e.data?.hokListRightsWindows.items ?? []) n[t.id] = t.priceMinorUnits;
|
|
144
|
+
return n;
|
|
145
|
+
}
|
|
146
|
+
}), B = r(t), V = o(), H = h(() => {
|
|
147
|
+
let e = V.data ?? [];
|
|
148
|
+
return e.find((e) => e.isDefault) ?? e[0] ?? null;
|
|
149
|
+
}, [V.data]), U = i(), W = a(), [G, K] = g(null), [q, J] = g("idle"), [Y, X] = g(null), Z = h(() => {
|
|
150
|
+
let e = (R.data?.items ?? []).filter((e) => e.status === le), t = z.data ?? {}, n = [];
|
|
151
|
+
for (let r of ce) {
|
|
152
|
+
let i = e.find((e) => e.modes.includes(r.mode));
|
|
153
|
+
i && n.push({
|
|
154
|
+
def: r,
|
|
155
|
+
window: i,
|
|
156
|
+
priceMinor: r.free ? null : ue(t[i.id], r.mode)
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return n;
|
|
160
|
+
}, [R.data, z.data]), Q = h(() => Z.length === 0 ? null : (G ? Z.find((e) => e.def.mode === G) : void 0) ?? Z[0], [Z, G]), fe = B.data?.entitled ?? !1, pe = m(async () => {
|
|
161
|
+
if (!t || !Q) return;
|
|
162
|
+
if (!D) {
|
|
163
|
+
J("error"), X("We could not identify your account. Please sign in again and retry.");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
let { def: e, window: n, priceMinor: r } = Q, i = n.territories[0] ?? void 0, a = e.expiresInHours ? new Date(Date.now() + e.expiresInHours * 36e5).toISOString() : void 0;
|
|
167
|
+
J("processing"), X(null);
|
|
168
|
+
try {
|
|
169
|
+
if (!e.free) {
|
|
170
|
+
if (r === null) throw Error("This option is not priced yet. Please choose another.");
|
|
171
|
+
if (!H) throw Error("You need a billing account before you can rent or buy. Add one from Billing, then retry.");
|
|
172
|
+
let i = Number(r) / 100, a = await U.mutateAsync({
|
|
173
|
+
billingAccountId: H.id,
|
|
174
|
+
currency: n.currency,
|
|
175
|
+
externalOrderId: typeof crypto < "u" && "randomUUID" in crypto ? crypto.randomUUID() : `hok-${t}-${e.mode}-${Date.now()}`,
|
|
176
|
+
externalService: "hookmovies",
|
|
177
|
+
subtotal: i,
|
|
178
|
+
description: `${e.label} · ${N?.title ?? "HookMovies title"}`,
|
|
179
|
+
items: [{
|
|
180
|
+
name: `${N?.title ?? "Title"} — ${e.label}`,
|
|
181
|
+
quantity: 1,
|
|
182
|
+
unitPrice: i,
|
|
183
|
+
description: `HookMovies ${e.label} (${e.mode})`
|
|
184
|
+
}]
|
|
185
|
+
});
|
|
186
|
+
if (!a.success || !a.order) throw Error(a.error ?? "The payment could not be completed.");
|
|
187
|
+
}
|
|
188
|
+
await W.mutateAsync({
|
|
189
|
+
titleId: t,
|
|
190
|
+
userId: D,
|
|
191
|
+
mode: e.mode,
|
|
192
|
+
source: e.source,
|
|
193
|
+
territory: i,
|
|
194
|
+
expiresAt: a
|
|
195
|
+
}), y(`watch/${t}`);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
J("error"), X(e instanceof Error ? e.message : "Checkout failed. Please try again.");
|
|
198
|
+
}
|
|
199
|
+
}, [
|
|
200
|
+
t,
|
|
201
|
+
Q,
|
|
202
|
+
D,
|
|
203
|
+
H,
|
|
204
|
+
U,
|
|
205
|
+
W,
|
|
206
|
+
y,
|
|
207
|
+
N?.title
|
|
208
|
+
]), me = m(() => {
|
|
209
|
+
y(t ? `title/${t}` : "browse");
|
|
210
|
+
}, [y, t]);
|
|
211
|
+
if (P) return /* @__PURE__ */ k(n, {
|
|
212
|
+
title: "Checkout",
|
|
213
|
+
children: /* @__PURE__ */ k(p, { label: "Loading checkout…" })
|
|
214
|
+
});
|
|
215
|
+
if (F || !N) return /* @__PURE__ */ k(n, {
|
|
216
|
+
title: "Checkout",
|
|
217
|
+
children: /* @__PURE__ */ k(f, {
|
|
218
|
+
title: "Title not found",
|
|
219
|
+
message: "This title may have been unpublished, or you may not have access to it.",
|
|
220
|
+
onRetry: () => void I()
|
|
221
|
+
})
|
|
222
|
+
});
|
|
223
|
+
let $ = q === "processing" || U.isPending || W.isPending, he = R.isLoading || z.isLoading, ge = R.isError || z.isError;
|
|
224
|
+
return /* @__PURE__ */ k(n, {
|
|
225
|
+
title: "Checkout",
|
|
226
|
+
description: N.title,
|
|
227
|
+
purpose: "Choose how you want to watch — rent for a short window, buy it forever, or start a subscription. Payment is handled by the platform billing service; access is granted the moment your order clears.",
|
|
228
|
+
actions: /* @__PURE__ */ A("button", {
|
|
229
|
+
type: "button",
|
|
230
|
+
onClick: me,
|
|
231
|
+
className: "inline-flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
232
|
+
children: [/* @__PURE__ */ k(te, { className: "h-4 w-4" }), "Back to title"]
|
|
233
|
+
}),
|
|
234
|
+
children: /* @__PURE__ */ A("div", {
|
|
235
|
+
className: "mx-auto w-full max-w-5xl",
|
|
236
|
+
children: [fe && /* @__PURE__ */ k(j, {
|
|
237
|
+
treatment: "metric",
|
|
238
|
+
className: "mb-6 border-status-success-bg-subtle p-4",
|
|
239
|
+
children: /* @__PURE__ */ A("div", {
|
|
240
|
+
className: "flex flex-wrap items-center justify-between gap-3",
|
|
241
|
+
children: [/* @__PURE__ */ A("p", {
|
|
242
|
+
className: "inline-flex items-center gap-2 text-sm font-medium text-status-success-text",
|
|
243
|
+
children: [/* @__PURE__ */ k(v, {
|
|
244
|
+
className: "h-5 w-5",
|
|
245
|
+
"aria-hidden": "true"
|
|
246
|
+
}), "You already have access to this title."]
|
|
247
|
+
}), /* @__PURE__ */ A("button", {
|
|
248
|
+
type: "button",
|
|
249
|
+
onClick: () => y(`watch/${N.id}`),
|
|
250
|
+
className: "inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-text transition-opacity hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
251
|
+
children: [/* @__PURE__ */ k(w, { className: "h-4 w-4 fill-current" }), "Watch now"]
|
|
252
|
+
})]
|
|
253
|
+
})
|
|
254
|
+
}), /* @__PURE__ */ A("div", {
|
|
255
|
+
className: "grid grid-cols-1 gap-6 lg:grid-cols-[1fr_20rem]",
|
|
256
|
+
children: [/* @__PURE__ */ A("section", {
|
|
257
|
+
"aria-label": "Viewing options",
|
|
258
|
+
children: [
|
|
259
|
+
/* @__PURE__ */ k("h2", {
|
|
260
|
+
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
261
|
+
children: "Viewing options"
|
|
262
|
+
}),
|
|
263
|
+
he ? /* @__PURE__ */ k(p, { label: "Loading viewing options…" }) : ge ? /* @__PURE__ */ k(f, {
|
|
264
|
+
title: "Couldn't load viewing options",
|
|
265
|
+
message: "We couldn't reach the rights service. Please try again.",
|
|
266
|
+
onRetry: () => {
|
|
267
|
+
R.refetch(), z.refetch();
|
|
268
|
+
}
|
|
269
|
+
}) : Z.length === 0 ? /* @__PURE__ */ k(d, {
|
|
270
|
+
title: "Not available to rent or buy",
|
|
271
|
+
description: "This title has no active viewing options in your region right now. Check back soon.",
|
|
272
|
+
illustration: "empty-data"
|
|
273
|
+
}) : /* @__PURE__ */ k("div", {
|
|
274
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
275
|
+
children: Z.map((e) => /* @__PURE__ */ k(de, {
|
|
276
|
+
offering: e,
|
|
277
|
+
selected: Q?.def.mode === e.def.mode,
|
|
278
|
+
onSelect: () => {
|
|
279
|
+
K(e.def.mode), q === "error" && (J("idle"), X(null));
|
|
280
|
+
}
|
|
281
|
+
}, e.def.mode))
|
|
282
|
+
}),
|
|
283
|
+
Z.length > 0 && /* @__PURE__ */ A("p", {
|
|
284
|
+
className: "mt-5 inline-flex items-start gap-2 text-xs leading-relaxed text-text-secondary",
|
|
285
|
+
children: [/* @__PURE__ */ k(re, {
|
|
286
|
+
className: "mt-0.5 h-3.5 w-3.5 shrink-0",
|
|
287
|
+
"aria-hidden": "true"
|
|
288
|
+
}), /* @__PURE__ */ A("span", { children: [
|
|
289
|
+
"A share of every rental and purchase is routed to",
|
|
290
|
+
" ",
|
|
291
|
+
N.studio?.name ?? "the title’s studio",
|
|
292
|
+
" and the credited cast & crew through HookMovies’ revenue-share program. Payouts are settled by the platform billing service."
|
|
293
|
+
] })]
|
|
294
|
+
})
|
|
295
|
+
]
|
|
296
|
+
}), /* @__PURE__ */ k("aside", {
|
|
297
|
+
"aria-label": "Order summary",
|
|
298
|
+
className: "lg:sticky lg:top-6 lg:self-start",
|
|
299
|
+
children: /* @__PURE__ */ A(j, {
|
|
300
|
+
treatment: "glass",
|
|
301
|
+
className: "border-border-seam p-5",
|
|
302
|
+
children: [/* @__PURE__ */ A("div", {
|
|
303
|
+
className: "mb-4 flex items-center gap-3",
|
|
304
|
+
children: [/* @__PURE__ */ k("div", {
|
|
305
|
+
className: "h-20 w-14 shrink-0 overflow-hidden rounded-lg border border-border-seam bg-bg-sunken",
|
|
306
|
+
children: L.data ? /* @__PURE__ */ k("img", {
|
|
307
|
+
src: L.data,
|
|
308
|
+
alt: `${N.title} poster`,
|
|
309
|
+
className: "h-full w-full object-cover"
|
|
310
|
+
}) : /* @__PURE__ */ k("div", {
|
|
311
|
+
className: "flex h-full w-full items-center justify-center",
|
|
312
|
+
children: /* @__PURE__ */ k(ne, {
|
|
313
|
+
className: "h-5 w-5 text-text-muted",
|
|
314
|
+
"aria-hidden": "true"
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
}), /* @__PURE__ */ A("div", {
|
|
318
|
+
className: "min-w-0",
|
|
319
|
+
children: [/* @__PURE__ */ k("p", {
|
|
320
|
+
className: "truncate text-sm font-semibold text-text-primary",
|
|
321
|
+
children: N.title
|
|
322
|
+
}), N.releaseYear && /* @__PURE__ */ k("p", {
|
|
323
|
+
className: "text-xs text-text-secondary",
|
|
324
|
+
children: N.releaseYear
|
|
325
|
+
})]
|
|
326
|
+
})]
|
|
327
|
+
}), Q ? /* @__PURE__ */ A(O, { children: [
|
|
328
|
+
/* @__PURE__ */ A("dl", {
|
|
329
|
+
className: "space-y-2 border-t border-border-seam pt-4 text-sm",
|
|
330
|
+
children: [
|
|
331
|
+
/* @__PURE__ */ A("div", {
|
|
332
|
+
className: "flex items-center justify-between gap-3",
|
|
333
|
+
children: [/* @__PURE__ */ k("dt", {
|
|
334
|
+
className: "text-text-secondary",
|
|
335
|
+
children: "Option"
|
|
336
|
+
}), /* @__PURE__ */ k("dd", {
|
|
337
|
+
className: "font-medium text-text-primary",
|
|
338
|
+
children: Q.def.label
|
|
339
|
+
})]
|
|
340
|
+
}),
|
|
341
|
+
/* @__PURE__ */ A("div", {
|
|
342
|
+
className: "flex items-center justify-between gap-3",
|
|
343
|
+
children: [/* @__PURE__ */ A("dt", {
|
|
344
|
+
className: "inline-flex items-center gap-1.5 text-text-secondary",
|
|
345
|
+
children: [/* @__PURE__ */ k(C, {
|
|
346
|
+
className: "h-3.5 w-3.5",
|
|
347
|
+
"aria-hidden": "true"
|
|
348
|
+
}), "Territory"]
|
|
349
|
+
}), /* @__PURE__ */ k("dd", {
|
|
350
|
+
className: "text-right font-medium text-text-primary",
|
|
351
|
+
children: M(Q.window.territories)
|
|
352
|
+
})]
|
|
353
|
+
}),
|
|
354
|
+
Q.def.expiresInHours && /* @__PURE__ */ A("div", {
|
|
355
|
+
className: "flex items-center justify-between gap-3",
|
|
356
|
+
children: [/* @__PURE__ */ A("dt", {
|
|
357
|
+
className: "inline-flex items-center gap-1.5 text-text-secondary",
|
|
358
|
+
children: [/* @__PURE__ */ k(x, {
|
|
359
|
+
className: "h-3.5 w-3.5",
|
|
360
|
+
"aria-hidden": "true"
|
|
361
|
+
}), "Access"]
|
|
362
|
+
}), /* @__PURE__ */ k("dd", {
|
|
363
|
+
className: "text-right font-medium text-text-primary",
|
|
364
|
+
children: Q.def.expiresInHours >= 24 ? `${Math.round(Q.def.expiresInHours / 24)} days` : `${Q.def.expiresInHours} hours`
|
|
365
|
+
})]
|
|
366
|
+
}),
|
|
367
|
+
/* @__PURE__ */ A("div", {
|
|
368
|
+
className: "mt-2 flex items-center justify-between gap-3 border-t border-border-seam pt-3",
|
|
369
|
+
children: [/* @__PURE__ */ k("dt", {
|
|
370
|
+
className: "text-sm font-semibold text-text-primary",
|
|
371
|
+
children: "Total"
|
|
372
|
+
}), /* @__PURE__ */ k("dd", {
|
|
373
|
+
className: "text-lg font-bold tracking-tight text-text-primary",
|
|
374
|
+
children: Q.def.free ? "Free" : Q.priceMinor === null ? "—" : l(Q.priceMinor, Q.window.currency)
|
|
375
|
+
})]
|
|
376
|
+
})
|
|
377
|
+
]
|
|
378
|
+
}),
|
|
379
|
+
!Q.def.free && /* @__PURE__ */ A("p", {
|
|
380
|
+
className: "mt-3 inline-flex items-center gap-1.5 text-xs text-text-secondary",
|
|
381
|
+
children: [/* @__PURE__ */ k(S, {
|
|
382
|
+
className: "h-3.5 w-3.5",
|
|
383
|
+
"aria-hidden": "true"
|
|
384
|
+
}), V.isLoading ? "Loading billing account…" : H ? `Billed to ${H.name ?? "your account"}` : "No billing account found"]
|
|
385
|
+
}),
|
|
386
|
+
q === "error" && Y && /* @__PURE__ */ A("p", {
|
|
387
|
+
role: "alert",
|
|
388
|
+
className: "mt-3 inline-flex items-start gap-2 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text",
|
|
389
|
+
children: [/* @__PURE__ */ k(ee, {
|
|
390
|
+
className: "mt-0.5 h-3.5 w-3.5 shrink-0",
|
|
391
|
+
"aria-hidden": "true"
|
|
392
|
+
}), Y]
|
|
393
|
+
}),
|
|
394
|
+
/* @__PURE__ */ k("button", {
|
|
395
|
+
type: "button",
|
|
396
|
+
onClick: () => void pe(),
|
|
397
|
+
disabled: $ || !Q.def.free && Q.priceMinor === null || !Q.def.free && !H && !V.isLoading,
|
|
398
|
+
className: "mt-5 inline-flex w-full items-center justify-center gap-2 rounded-xl bg-action-primary-bg px-5 py-3 text-sm font-semibold text-action-primary-text shadow-lg transition-opacity hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-60",
|
|
399
|
+
children: $ ? /* @__PURE__ */ A(O, { children: [/* @__PURE__ */ k(ie, {
|
|
400
|
+
className: "h-4 w-4 animate-spin",
|
|
401
|
+
"aria-hidden": "true"
|
|
402
|
+
}), W.isPending ? "Unlocking…" : "Processing payment…"] }) : Q.def.free ? /* @__PURE__ */ A(O, { children: [/* @__PURE__ */ k(w, {
|
|
403
|
+
className: "h-4 w-4 fill-current",
|
|
404
|
+
"aria-hidden": "true"
|
|
405
|
+
}), "Watch free"] }) : /* @__PURE__ */ A(O, { children: [/* @__PURE__ */ k(ae, {
|
|
406
|
+
className: "h-4 w-4",
|
|
407
|
+
"aria-hidden": "true"
|
|
408
|
+
}), "Confirm & watch"] })
|
|
409
|
+
}),
|
|
410
|
+
/* @__PURE__ */ A("p", {
|
|
411
|
+
className: "mt-3 inline-flex items-start gap-1.5 text-[11px] leading-relaxed text-text-muted",
|
|
412
|
+
children: [/* @__PURE__ */ k(T, {
|
|
413
|
+
className: "mt-0.5 h-3 w-3 shrink-0",
|
|
414
|
+
"aria-hidden": "true"
|
|
415
|
+
}), "Secured by the platform billing service. Access is granted immediately once your order clears."]
|
|
416
|
+
})
|
|
417
|
+
] }) : /* @__PURE__ */ k("p", {
|
|
418
|
+
className: "border-t border-border-seam pt-4 text-sm text-text-secondary",
|
|
419
|
+
children: "Select a viewing option to see your order summary."
|
|
420
|
+
})]
|
|
421
|
+
})
|
|
422
|
+
})]
|
|
423
|
+
})]
|
|
424
|
+
})
|
|
425
|
+
});
|
|
426
|
+
};
|
|
427
|
+
//#endregion
|
|
428
|
+
export { N as CheckoutPage };
|
|
429
|
+
|
|
430
|
+
//# sourceMappingURL=CheckoutPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CheckoutPage.js","names":[],"sources":["../../../../src/hookmovies/pages/commerce/CheckoutPage.tsx"],"sourcesContent":["/**\n * Checkout — Rent / Buy / Subscribe (Commerce)\n * @module hookmovies/pages/commerce\n *\n * Transactional page reached from the consumer {@link TitleViewPage}\n * (\"Where to watch\") at route `/hookmovies/checkout/:titleId`. It turns a\n * title's monetization rights into a purchase:\n *\n * 1. Reads the title's monetization options from the live `hokListRightsWindows`\n * read (via the shared {@link useRightsWindows} data-layer hook) and surfaces\n * one tile per offered mode — Rent (TVOD_RENT), Buy (TVOD_BUY),\n * Pay-per-view (PPV), Subscribe (SVOD) and free-with-ads (FREE_AVOD).\n * 2. On confirm it REUSES the platform billing service (global-billing, GLOBAL\n * gateway) via {@link useCreateBillingOrder} to open the paid order — this\n * MFE never owns an order/payment table.\n * 3. On a successful order it writes the real access grant through the\n * hookmovies subgraph ({@link useGrantEntitlement}) and forwards the viewer\n * to the player (`watch/:titleId`).\n *\n * Money is honest end-to-end: prices come from each rights window's\n * `priceMinorUnits` map in the window's own `currency`; the licensed\n * territories and the creator revenue-share note are shown verbatim from the\n * window / title, never fabricated.\n *\n * NOTE ON PRICES: the shared `useRightsWindows` selection intentionally omits\n * the `priceMinorUnits` JSON field, so this page issues one small supplemental\n * read (same `hokListRightsWindows` root, same workspace transport) for just\n * that field, keyed by window id. We do not duplicate the data layer — we read\n * exactly the one field the shared hook leaves off, and we never mutate here\n * except through the shared billing / entitlement hooks.\n */\n\nimport { type FC, useCallback, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useQuery } from '@tanstack/react-query';\nimport {\n AlertCircle,\n ArrowLeft,\n BadgeCheck,\n CalendarClock,\n CheckCircle2,\n Clock,\n CreditCard,\n Film,\n Globe,\n Info,\n Loader2,\n Lock,\n Play,\n ShieldCheck,\n ShoppingBag,\n Ticket,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport { useHookMovies } from '../../context/HookMoviesContext';\nimport {\n useTitle,\n useRightsWindows,\n useCheckEntitlement,\n useMyBillingAccounts,\n useCreateBillingOrder,\n useGrantEntitlement,\n type HokRightsWindow,\n type HokMonetizationMode,\n type HokEntitlementSource,\n} from '../../hooks/useHookMoviesApi';\nimport { useFileUrl } from '../../hooks/useFiles';\nimport { formatMinorUnits } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\n\n// ── Offer catalogue (which monetization modes become checkout tiles) ─────────\n\ninterface OfferDef {\n mode: HokMonetizationMode;\n label: string;\n source: HokEntitlementSource;\n blurb: string;\n icon: typeof Clock;\n /** true → no billing order; grant a free (ad-supported) entitlement directly. */\n free?: boolean;\n /** Rental / subscription access windows. Omitted → perpetual (a purchase). */\n expiresInHours?: number;\n}\n\n/**\n * The purchasable modes, in the order they should appear. FAST (linear FAST\n * channels) is deliberately excluded — it is scheduled linear viewing, not a\n * per-title checkout.\n */\nconst OFFER_DEFS: readonly OfferDef[] = [\n {\n mode: 'TVOD_RENT',\n label: 'Rent',\n source: 'RENTAL',\n blurb: 'Stream anytime within a 48-hour window once you press play.',\n icon: Clock,\n expiresInHours: 48,\n },\n {\n mode: 'TVOD_BUY',\n label: 'Buy',\n source: 'PURCHASE',\n blurb: 'Own it in your library and watch it as often as you like.',\n icon: ShoppingBag,\n },\n {\n mode: 'PPV',\n label: 'Pay-per-view',\n source: 'PURCHASE',\n blurb: 'One-time access to this premium event or release.',\n icon: Ticket,\n },\n {\n mode: 'SVOD',\n label: 'Subscribe',\n source: 'SUBSCRIPTION',\n blurb: 'Included for 30 days with a subscription pass to this title.',\n icon: CalendarClock,\n expiresInHours: 24 * 30,\n },\n {\n mode: 'FREE_AVOD',\n label: 'Watch free',\n source: 'FREE',\n blurb: 'Stream now, free with ad breaks — no payment required.',\n icon: Play,\n free: true,\n },\n] as const;\n\nconst ACTIVE_WINDOW_STATUS = 'ACTIVE';\n\n// ── Small helpers ────────────────────────────────────────────────────────────\n\n/** Pull a mode's price (minor-unit string) out of a window's `priceMinorUnits` JSON. */\nfunction readModePrice(raw: unknown, mode: HokMonetizationMode): string | null {\n if (!raw || typeof raw !== 'object') return null;\n const value = (raw as Record<string, unknown>)[mode];\n if (typeof value === 'number' && Number.isFinite(value)) return String(value);\n if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {\n return value;\n }\n return null;\n}\n\n/** Human-readable licensed-territory line for a rights window. */\nfunction formatTerritories(territories: string[]): string {\n if (territories.length === 0) return 'Worldwide';\n const worldwide = territories.some((t) =>\n ['WW', 'WORLD', 'WORLDWIDE', 'GLOBAL'].includes(t.toUpperCase())\n );\n return worldwide ? 'Worldwide' : territories.join(', ');\n}\n\n/** A concrete, purchasable offering: an offer def matched to an active window + price. */\ninterface Offering {\n def: OfferDef;\n window: HokRightsWindow;\n /** Minor-unit price string; `null` when the window omits a price for this mode. */\n priceMinor: string | null;\n}\n\ntype CheckoutPhase = 'idle' | 'processing' | 'error';\n\n// ── Offer tile ───────────────────────────────────────────────────────────────\n\nconst OfferTile: FC<{\n offering: Offering;\n selected: boolean;\n onSelect: () => void;\n}> = ({ offering, selected, onSelect }) => {\n const { def, window: rightsWindow, priceMinor } = offering;\n const Icon = def.icon;\n const priceLabel = def.free\n ? 'Free'\n : priceMinor !== null\n ? formatMinorUnits(priceMinor, rightsWindow.currency)\n : 'Price unavailable';\n const unavailable = !def.free && priceMinor === null;\n\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n disabled={unavailable}\n aria-pressed={selected}\n className={cn(\n 'group relative flex w-full flex-col rounded-2xl border p-5 text-left transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n selected\n ? 'border-action-primary-bg bg-action-primary-bg/5 shadow-lg'\n : 'border-border-seam bg-bg-surface/60 hover:border-border-strong hover:bg-bg-surface',\n unavailable && 'cursor-not-allowed opacity-60'\n )}\n >\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <span\n className={cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-xl',\n selected\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary'\n )}\n >\n <Icon className=\"h-5 w-5\" aria-hidden=\"true\" />\n </span>\n {selected && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-0.5 text-xs font-medium text-action-primary-bg\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Selected\n </span>\n )}\n </div>\n <p className=\"text-sm font-semibold text-text-primary\">{def.label}</p>\n <p className=\"mt-1 flex-1 text-xs leading-relaxed text-text-secondary\">{def.blurb}</p>\n <p\n className={cn(\n 'mt-4 text-lg font-bold tracking-tight',\n unavailable ? 'text-text-muted' : 'text-text-primary'\n )}\n >\n {priceLabel}\n </p>\n <p className=\"mt-1 inline-flex items-center gap-1 text-[11px] text-text-secondary\">\n <Globe className=\"h-3 w-3\" aria-hidden=\"true\" />\n {formatTerritories(rightsWindow.territories)}\n </p>\n </button>\n );\n};\n\n// ── Page ─────────────────────────────────────────────────────────────────────\n\nexport const CheckoutPage: FC = () => {\n const { titleId } = useParams<{ titleId: string }>();\n const { navigateTo, currentUser, apiGatewayUrl } = useHookMovies();\n\n const userId = currentUser?.id;\n\n // Title (for the summary card + player forwarding).\n const { data: title, isLoading: titleLoading, isError: titleError, refetch } = useTitle(titleId);\n const poster = useFileUrl(title?.posterFileId);\n\n // Monetization options (shared data-layer hook — modes / currency / territories).\n const rights = useRightsWindows({ titleId }, { limit: 50, offset: 0 });\n\n // Supplemental price map (the one field the shared hook omits). Same root, same\n // workspace transport. Keyed by window id → priceMinorUnits JSON blob.\n const prices = useQuery({\n queryKey: ['hookmovies', 'checkout-prices', titleId ?? ''],\n enabled: Boolean(titleId),\n queryFn: async () => {\n const response = await graphqlFetch<{\n hokListRightsWindows: { items: Array<{ id: string; priceMinorUnits: unknown }> };\n }>({\n gateway: 'workspace',\n baseUrl: apiGatewayUrl,\n workspaceToken: true,\n query: `query HokCheckoutPrices($titleId: ID, $pagination: PaginationInput) {\n hokListRightsWindows(titleId: $titleId, pagination: $pagination) {\n items { id priceMinorUnits }\n }\n }`,\n variables: { titleId, pagination: { limit: 50, offset: 0 } },\n });\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'Could not load pricing');\n }\n const map: Record<string, unknown> = {};\n for (const item of response.data?.hokListRightsWindows.items ?? []) {\n map[item.id] = item.priceMinorUnits;\n }\n return map;\n },\n });\n\n // Already entitled? Offer a direct \"Watch now\" instead of re-purchasing.\n const entitlement = useCheckEntitlement(titleId);\n\n // Billing account that anchors the checkout order (creator payouts flow from it too).\n const billingAccounts = useMyBillingAccounts();\n const billingAccount = useMemo(() => {\n const accounts = billingAccounts.data ?? [];\n return accounts.find((a) => a.isDefault) ?? accounts[0] ?? null;\n }, [billingAccounts.data]);\n\n const createOrder = useCreateBillingOrder();\n const grantEntitlement = useGrantEntitlement();\n\n const [selectedMode, setSelectedMode] = useState<HokMonetizationMode | null>(null);\n const [phase, setPhase] = useState<CheckoutPhase>('idle');\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n // Build the concrete offerings from ACTIVE rights windows.\n const offerings = useMemo<Offering[]>(() => {\n const windows = (rights.data?.items ?? []).filter((w) => w.status === ACTIVE_WINDOW_STATUS);\n const priceMap = prices.data ?? {};\n const result: Offering[] = [];\n for (const def of OFFER_DEFS) {\n // First active window that offers this mode.\n const window = windows.find((w) => w.modes.includes(def.mode));\n if (!window) continue;\n result.push({\n def,\n window,\n priceMinor: def.free ? null : readModePrice(priceMap[window.id], def.mode),\n });\n }\n return result;\n }, [rights.data, prices.data]);\n\n // Default the selection to the first available offering once loaded.\n const effectiveSelected = useMemo<Offering | null>(() => {\n if (offerings.length === 0) return null;\n const chosen = selectedMode ? offerings.find((o) => o.def.mode === selectedMode) : undefined;\n return chosen ?? offerings[0]!;\n }, [offerings, selectedMode]);\n\n const alreadyEntitled = entitlement.data?.entitled ?? false;\n\n const handleConfirm = useCallback(async () => {\n if (!titleId || !effectiveSelected) return;\n if (!userId) {\n setPhase('error');\n setErrorMessage('We could not identify your account. Please sign in again and retry.');\n return;\n }\n\n const { def, window: rightsWindow, priceMinor } = effectiveSelected;\n const territory = rightsWindow.territories[0] ?? undefined;\n const expiresAt = def.expiresInHours\n ? new Date(Date.now() + def.expiresInHours * 3_600_000).toISOString()\n : undefined;\n\n setPhase('processing');\n setErrorMessage(null);\n\n try {\n // Paid modes go through the platform billing service first.\n if (!def.free) {\n if (priceMinor === null) {\n throw new Error('This option is not priced yet. Please choose another.');\n }\n if (!billingAccount) {\n throw new Error(\n 'You need a billing account before you can rent or buy. Add one from Billing, then retry.'\n );\n }\n const major = Number(priceMinor) / 100;\n const orderResult = await createOrder.mutateAsync({\n billingAccountId: billingAccount.id,\n currency: rightsWindow.currency,\n externalOrderId:\n typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? crypto.randomUUID()\n : `hok-${titleId}-${def.mode}-${Date.now()}`,\n externalService: 'hookmovies',\n subtotal: major,\n description: `${def.label} · ${title?.title ?? 'HookMovies title'}`,\n items: [\n {\n name: `${title?.title ?? 'Title'} — ${def.label}`,\n quantity: 1,\n unitPrice: major,\n description: `HookMovies ${def.label} (${def.mode})`,\n },\n ],\n });\n if (!orderResult.success || !orderResult.order) {\n throw new Error(orderResult.error ?? 'The payment could not be completed.');\n }\n }\n\n // Write the real access grant on the hookmovies subgraph.\n await grantEntitlement.mutateAsync({\n titleId,\n userId,\n mode: def.mode,\n source: def.source,\n territory,\n expiresAt,\n });\n\n // Forward to the player.\n navigateTo(`watch/${titleId}`);\n } catch (err) {\n setPhase('error');\n setErrorMessage(err instanceof Error ? err.message : 'Checkout failed. Please try again.');\n }\n }, [\n titleId,\n effectiveSelected,\n userId,\n billingAccount,\n createOrder,\n grantEntitlement,\n navigateTo,\n title?.title,\n ]);\n\n const backToTitle = useCallback(() => {\n if (titleId) navigateTo(`title/${titleId}`);\n else navigateTo('browse');\n }, [navigateTo, titleId]);\n\n // ── Loading / error / not-found shells ─────────────────────────────────────\n\n if (titleLoading) {\n return (\n <PageLayout title=\"Checkout\">\n <LoadingState label=\"Loading checkout…\" />\n </PageLayout>\n );\n }\n\n if (titleError || !title) {\n return (\n <PageLayout title=\"Checkout\">\n <ErrorState\n title=\"Title not found\"\n message=\"This title may have been unpublished, or you may not have access to it.\"\n onRetry={() => void refetch()}\n />\n </PageLayout>\n );\n }\n\n const isBusy = phase === 'processing' || createOrder.isPending || grantEntitlement.isPending;\n const optionsLoading = rights.isLoading || prices.isLoading;\n const optionsError = rights.isError || prices.isError;\n\n return (\n <PageLayout\n title=\"Checkout\"\n description={title.title}\n purpose=\"Choose how you want to watch — rent for a short window, buy it forever, or start a subscription. Payment is handled by the platform billing service; access is granted the moment your order clears.\"\n actions={\n <button\n type=\"button\"\n onClick={backToTitle}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Back to title\n </button>\n }\n >\n <div className=\"mx-auto w-full max-w-5xl\">\n {/* Already-entitled shortcut */}\n {alreadyEntitled && (\n <GlassCard treatment=\"metric\" className=\"mb-6 border-status-success-bg-subtle p-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <p className=\"inline-flex items-center gap-2 text-sm font-medium text-status-success-text\">\n <BadgeCheck className=\"h-5 w-5\" aria-hidden=\"true\" />\n You already have access to this title.\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo(`watch/${title.id}`)}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-text transition-opacity hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Play className=\"h-4 w-4 fill-current\" />\n Watch now\n </button>\n </div>\n </GlassCard>\n )}\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[1fr_20rem]\">\n {/* ── Offer tiles ─────────────────────────────────────────────────── */}\n <section aria-label=\"Viewing options\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Viewing options</h2>\n\n {optionsLoading ? (\n <LoadingState label=\"Loading viewing options…\" />\n ) : optionsError ? (\n <ErrorState\n title=\"Couldn't load viewing options\"\n message=\"We couldn't reach the rights service. Please try again.\"\n onRetry={() => {\n void rights.refetch();\n void prices.refetch();\n }}\n />\n ) : offerings.length === 0 ? (\n <EmptyState\n title=\"Not available to rent or buy\"\n description=\"This title has no active viewing options in your region right now. Check back soon.\"\n illustration=\"empty-data\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {offerings.map((offering) => (\n <OfferTile\n key={offering.def.mode}\n offering={offering}\n selected={effectiveSelected?.def.mode === offering.def.mode}\n onSelect={() => {\n setSelectedMode(offering.def.mode);\n if (phase === 'error') {\n setPhase('idle');\n setErrorMessage(null);\n }\n }}\n />\n ))}\n </div>\n )}\n\n {/* Honest creator revenue-share transparency note */}\n {offerings.length > 0 && (\n <p className=\"mt-5 inline-flex items-start gap-2 text-xs leading-relaxed text-text-secondary\">\n <Info className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n <span>\n A share of every rental and purchase is routed to{' '}\n {title.studio?.name ?? 'the title’s studio'} and the credited cast & crew\n through HookMovies’ revenue-share program. Payouts are settled by the platform\n billing service.\n </span>\n </p>\n )}\n </section>\n\n {/* ── Order summary ───────────────────────────────────────────────── */}\n <aside aria-label=\"Order summary\" className=\"lg:sticky lg:top-6 lg:self-start\">\n <GlassCard treatment=\"glass\" className=\"border-border-seam p-5\">\n {/* Title header */}\n <div className=\"mb-4 flex items-center gap-3\">\n <div className=\"h-20 w-14 shrink-0 overflow-hidden rounded-lg border border-border-seam bg-bg-sunken\">\n {poster.data ? (\n <img\n src={poster.data}\n alt={`${title.title} poster`}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center\">\n <Film className=\"h-5 w-5 text-text-muted\" aria-hidden=\"true\" />\n </div>\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{title.title}</p>\n {title.releaseYear && (\n <p className=\"text-xs text-text-secondary\">{title.releaseYear}</p>\n )}\n </div>\n </div>\n\n {effectiveSelected ? (\n <>\n <dl className=\"space-y-2 border-t border-border-seam pt-4 text-sm\">\n <div className=\"flex items-center justify-between gap-3\">\n <dt className=\"text-text-secondary\">Option</dt>\n <dd className=\"font-medium text-text-primary\">\n {effectiveSelected.def.label}\n </dd>\n </div>\n <div className=\"flex items-center justify-between gap-3\">\n <dt className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <Globe className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Territory\n </dt>\n <dd className=\"text-right font-medium text-text-primary\">\n {formatTerritories(effectiveSelected.window.territories)}\n </dd>\n </div>\n {effectiveSelected.def.expiresInHours && (\n <div className=\"flex items-center justify-between gap-3\">\n <dt className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Access\n </dt>\n <dd className=\"text-right font-medium text-text-primary\">\n {effectiveSelected.def.expiresInHours >= 24\n ? `${Math.round(effectiveSelected.def.expiresInHours / 24)} days`\n : `${effectiveSelected.def.expiresInHours} hours`}\n </dd>\n </div>\n )}\n <div className=\"mt-2 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n <dt className=\"text-sm font-semibold text-text-primary\">Total</dt>\n <dd className=\"text-lg font-bold tracking-tight text-text-primary\">\n {effectiveSelected.def.free\n ? 'Free'\n : effectiveSelected.priceMinor !== null\n ? formatMinorUnits(\n effectiveSelected.priceMinor,\n effectiveSelected.window.currency\n )\n : '—'}\n </dd>\n </div>\n </dl>\n\n {/* Billing account line (paid options only) */}\n {!effectiveSelected.def.free && (\n <p className=\"mt-3 inline-flex items-center gap-1.5 text-xs text-text-secondary\">\n <CreditCard className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n {billingAccounts.isLoading\n ? 'Loading billing account…'\n : billingAccount\n ? `Billed to ${billingAccount.name ?? 'your account'}`\n : 'No billing account found'}\n </p>\n )}\n\n {/* Error surface */}\n {phase === 'error' && errorMessage && (\n <p\n role=\"alert\"\n className=\"mt-3 inline-flex items-start gap-2 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\"\n >\n <AlertCircle className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n {errorMessage}\n </p>\n )}\n\n {/* Confirm */}\n <button\n type=\"button\"\n onClick={() => void handleConfirm()}\n disabled={\n isBusy ||\n (!effectiveSelected.def.free && effectiveSelected.priceMinor === null) ||\n (!effectiveSelected.def.free && !billingAccount && !billingAccounts.isLoading)\n }\n className=\"mt-5 inline-flex w-full items-center justify-center gap-2 rounded-xl bg-action-primary-bg px-5 py-3 text-sm font-semibold text-action-primary-text shadow-lg transition-opacity hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {isBusy ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n {grantEntitlement.isPending ? 'Unlocking…' : 'Processing payment…'}\n </>\n ) : effectiveSelected.def.free ? (\n <>\n <Play className=\"h-4 w-4 fill-current\" aria-hidden=\"true\" />\n Watch free\n </>\n ) : (\n <>\n <Lock className=\"h-4 w-4\" aria-hidden=\"true\" />\n Confirm & watch\n </>\n )}\n </button>\n\n <p className=\"mt-3 inline-flex items-start gap-1.5 text-[11px] leading-relaxed text-text-muted\">\n <ShieldCheck className=\"mt-0.5 h-3 w-3 shrink-0\" aria-hidden=\"true\" />\n Secured by the platform billing service. Access is granted immediately once your\n order clears.\n </p>\n </>\n ) : (\n <p className=\"border-t border-border-seam pt-4 text-sm text-text-secondary\">\n Select a viewing option to see your order summary.\n </p>\n )}\n </GlassCard>\n </aside>\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -3,8 +3,8 @@ import { cn as t } from "../../utils/cn.js";
|
|
|
3
3
|
import { useCheckEntitlement as n, useMyWatchProgress as r, useTitles as i } from "../../hooks/useHookMoviesApi.js";
|
|
4
4
|
import { formatEnumLabel as a } from "../../utils/formatters.js";
|
|
5
5
|
import { useFileUrl as o } from "../../hooks/useFiles.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { EmptyState as s } from "../../components/EmptyState.js";
|
|
7
|
+
import { ErrorState as c, LoadingState as l } from "../../components/StateViews.js";
|
|
8
8
|
import { useEffect as u, useMemo as d, useState as f } from "react";
|
|
9
9
|
import { ChevronRight as p, Clapperboard as m, Film as h, Flame as g, History as _, Info as v, Lock as y, Play as b, Search as x, Sparkles as S, Tv as C } from "lucide-react";
|
|
10
10
|
import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
@@ -277,11 +277,11 @@ var O = {
|
|
|
277
277
|
};
|
|
278
278
|
if (b.isLoading) return /* @__PURE__ */ T("div", {
|
|
279
279
|
className: "flex h-full flex-col bg-bg-canvas",
|
|
280
|
-
children: /* @__PURE__ */ T(
|
|
280
|
+
children: /* @__PURE__ */ T(l, { label: "Loading titles…" })
|
|
281
281
|
});
|
|
282
282
|
if (b.isError) return /* @__PURE__ */ T("div", {
|
|
283
283
|
className: "flex h-full flex-col bg-bg-canvas",
|
|
284
|
-
children: /* @__PURE__ */ T(
|
|
284
|
+
children: /* @__PURE__ */ T(c, {
|
|
285
285
|
title: "Could not load HookMovies",
|
|
286
286
|
message: "The catalog service did not respond. Please try again.",
|
|
287
287
|
onRetry: G
|
|
@@ -319,11 +319,11 @@ var O = {
|
|
|
319
319
|
"aria-live": "polite",
|
|
320
320
|
children: [z.data?.totalCount ?? 0, " found"]
|
|
321
321
|
})]
|
|
322
|
-
}), z.isLoading ? /* @__PURE__ */ T(
|
|
322
|
+
}), z.isLoading ? /* @__PURE__ */ T(l, { label: "Searching…" }) : z.isError ? /* @__PURE__ */ T(c, {
|
|
323
323
|
title: "Search failed",
|
|
324
324
|
message: "Could not run that search. Please try again.",
|
|
325
325
|
onRetry: () => void z.refetch()
|
|
326
|
-
}) : (z.data?.items.length ?? 0) === 0 ? /* @__PURE__ */ T(
|
|
326
|
+
}) : (z.data?.items.length ?? 0) === 0 ? /* @__PURE__ */ T(s, {
|
|
327
327
|
illustration: "empty-search",
|
|
328
328
|
title: "No matches",
|
|
329
329
|
description: `Nothing published matches “${o}”. Try a different title, genre, or spelling.`
|
|
@@ -408,7 +408,7 @@ var O = {
|
|
|
408
408
|
]
|
|
409
409
|
})] }) : /* @__PURE__ */ T("div", {
|
|
410
410
|
className: "px-4 py-16 sm:px-6",
|
|
411
|
-
children: /* @__PURE__ */ T(
|
|
411
|
+
children: /* @__PURE__ */ T(s, {
|
|
412
412
|
illustration: "empty-data",
|
|
413
413
|
title: "Nothing to watch yet",
|
|
414
414
|
description: "No titles have been published to this workspace. Once a title is published it will appear here to browse and stream."
|