@aglyn/plugins-commerce 1.0.0-beta.253 → 1.0.0-beta.255

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-commerce",
3
- "version": "1.0.0-beta.253",
3
+ "version": "1.0.0-beta.255",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,18 +25,18 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.253",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.253",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.253",
31
- "@aglyn/shared-ui-next": "1.0.0-beta.253",
32
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.253",
33
- "@aglyn/shared-util-email": "1.0.0-beta.253",
34
- "@aglyn/shared-util-http": "1.0.0-beta.253",
35
- "@aglyn/shared-util-timestamp": "1.0.0-beta.253",
36
- "@aglyn/shared-util-tools": "1.0.0-beta.253",
37
- "@aglyn/tenant-data-admin": "1.0.0-beta.253",
38
- "@aglyn/tenant-feature-instance": "1.0.0-beta.253",
39
- "@aglyn/tenant-runtime": "1.0.0-beta.253",
28
+ "@aglyn/aglyn": "1.0.0-beta.255",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.255",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.255",
31
+ "@aglyn/shared-ui-next": "1.0.0-beta.255",
32
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.255",
33
+ "@aglyn/shared-util-email": "1.0.0-beta.255",
34
+ "@aglyn/shared-util-http": "1.0.0-beta.255",
35
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.255",
36
+ "@aglyn/shared-util-tools": "1.0.0-beta.255",
37
+ "@aglyn/tenant-data-admin": "1.0.0-beta.255",
38
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.255",
39
+ "@aglyn/tenant-runtime": "1.0.0-beta.255",
40
40
  "@stripe/react-stripe-js": "^6.12.0",
41
41
  "@stripe/stripe-js": "^9.17.0",
42
42
  "@swc/helpers": "0.5.23",
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import { type PickedMedia } from '@aglyn/aglyn';
18
+ import { type PaidMediaKind } from '../../model/paid-media';
18
19
  /**
19
20
  * A product's members videos (AGL-2814) and paid downloads (AGL-2847) are
20
21
  * private files.
@@ -31,38 +32,7 @@ import { type PickedMedia } from '@aglyn/aglyn';
31
32
  * confirming, from the same where-used scan the media library runs before a
32
33
  * delete.
33
34
  */
34
- /** A where-used row, reduced to what the confirmation reads. */
35
- export interface PaidMediaReference {
36
- kind: string;
37
- id: string;
38
- name: string;
39
- hostId: string;
40
- collectionId?: string;
41
- }
42
- /** How far the where-used scan got before the author was asked. */
43
- export type PaidMediaUsageCheck = 'full' | 'partial' | 'failed';
44
- /** Which list a flow adds to, for the words it uses. */
45
- export type PaidMediaKind = 'video' | 'download';
46
- /** The request body that names an asset's library to the media routes. */
47
- export declare function paidMediaScopeBody(scope: string): {
48
- orgId: string;
49
- } | {
50
- hostId: string;
51
- };
52
- /**
53
- * Where an asset is used, apart from the product it is being added to. That
54
- * product's own row is not a place making it private could break.
55
- */
56
- export declare function usesBesideProduct(references: readonly PaidMediaReference[], product: {
57
- hostId: string;
58
- productId?: string;
59
- }): PaidMediaReference[];
60
- /** What the author reads before a video becomes private. */
61
- export declare function paidMediaConfirmation(options: {
62
- others: readonly PaidMediaReference[];
63
- checked: PaidMediaUsageCheck;
64
- kind?: PaidMediaKind;
65
- }): string;
35
+ export { paidMediaConfirmation, paidMediaScopeBody, usesBesideProduct, type PaidMediaKind, type PaidMediaReference, type PaidMediaUsageCheck, } from '../../model/paid-media';
66
36
  /**
67
37
  * Adds a picked file to a product as paid media, making it private first when
68
38
  * it is not, and makes an already-added one private.
@@ -24,53 +24,23 @@ import { useFirestore, useUser } from "@aglyn/tenant-feature-instance";
24
24
  import { Button, Chip, Stack, Tooltip, Typography } from "@mui/material";
25
25
  import { doc, getDoc } from "firebase/firestore";
26
26
  import { useCallback, useEffect, useState } from "react";
27
- const PAID_MEDIA_COPY = {
28
- video: {
29
- title: 'Make this video private?',
30
- lead: 'Members videos are private: buyers play them through a link that ' + 'expires, and nobody else can open them.',
31
- notAdded: 'The video was not added.',
32
- unusable: 'Members cannot play it until it is private.',
33
- choose: 'Choose the video from the media library, so its links can expire.'
34
- },
35
- download: {
36
- title: 'Make this file private?',
37
- lead: 'Paid downloads are private: buyers get a link that expires, and nobody ' + 'else can open the file.',
38
- notAdded: 'The file was not added.',
39
- unusable: 'Buyers cannot download it until it is private.',
40
- choose: 'Choose the file from the media library, so its links can expire.'
41
- }
42
- };
43
- /** The request body that names an asset's library to the media routes. */ export function paidMediaScopeBody(scope) {
44
- return scope.startsWith('org:') ? {
45
- orgId: scope.slice('org:'.length).split(':')[0]
46
- } : {
47
- hostId: scope
48
- };
49
- }
27
+ import { PAID_MEDIA_COPY, paidMediaConfirmation, paidMediaScopeBody, usesBesideProduct } from "../../model/paid-media.js";
50
28
  /**
51
- * Where an asset is used, apart from the product it is being added to. That
52
- * product's own row is not a place making it private could break.
53
- */ export function usesBesideProduct(references, product) {
54
- return references.filter((reference)=>!(product.productId && reference.kind === 'plugin' && reference.collectionId === 'products' && reference.hostId === product.hostId && reference.id === product.productId));
55
- }
56
- /** What the author reads before a video becomes private. */ export function paidMediaConfirmation(options) {
57
- var _options_kind;
58
- const { others, checked } = options;
59
- const { lead } = PAID_MEDIA_COPY[(_options_kind = options.kind) != null ? _options_kind : 'video'];
60
- let usage;
61
- if (others.length) {
62
- const names = others.slice(0, 3).map((reference)=>`“${reference.name}”`).join(', ');
63
- const more = others.length > 3 ? ` and ${others.length - 3} more` : '';
64
- usage = `It is also used on ${names}${more}, and it stops showing there.`;
65
- } else if (checked === 'full') {
66
- usage = 'Nothing else uses it.';
67
- } else if (checked === 'partial') {
68
- usage = 'We could not check everywhere it might be used, so something else ' + 'could still rely on it.';
69
- } else {
70
- usage = 'We could not check where else it is used.';
71
- }
72
- return `${lead} ${usage} Any public link to it that was already shared stops working too.`;
73
- }
29
+ * A product's members videos (AGL-2814) and paid downloads (AGL-2847) are
30
+ * private files.
31
+ *
32
+ * The stream and download routes only ever hand a buyer a link that expires,
33
+ * and they can only do that for a PRIVATE asset: a public one already has a
34
+ * URL that works for anyone, forever, and the routes refuse to deliver it. So
35
+ * adding a file to a product makes it private, and the product editor shows,
36
+ * per file, whether that is still true.
37
+ *
38
+ * Making a file private is visible outside the product. A trailer that is
39
+ * also on a public page stops playing there, and every public link to it that
40
+ * was already shared dies. The author is told which places use it before
41
+ * confirming, from the same where-used scan the media library runs before a
42
+ * delete.
43
+ */ export { paidMediaConfirmation, paidMediaScopeBody, usesBesideProduct } from "../../model/paid-media.js";
74
44
  /**
75
45
  * Adds a picked file to a product as paid media, making it private first when
76
46
  * it is not, and makes an already-added one private.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/paid-media.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport {\n formatMediaRef,\n paidMediaAssetOf,\n parsePaidMediaSource,\n type PickedMedia,\n useMediaPicker,\n} from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { Button, Chip, Stack, Tooltip, Typography } from '@mui/material'\nimport { doc, getDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\n\n/**\n * A product's members videos (AGL-2814) and paid downloads (AGL-2847) are\n * private files.\n *\n * The stream and download routes only ever hand a buyer a link that expires,\n * and they can only do that for a PRIVATE asset: a public one already has a\n * URL that works for anyone, forever, and the routes refuse to deliver it. So\n * adding a file to a product makes it private, and the product editor shows,\n * per file, whether that is still true.\n *\n * Making a file private is visible outside the product. A trailer that is\n * also on a public page stops playing there, and every public link to it that\n * was already shared dies. The author is told which places use it before\n * confirming, from the same where-used scan the media library runs before a\n * delete.\n */\n\n/** A where-used row, reduced to what the confirmation reads. */\nexport interface PaidMediaReference {\n kind: string\n id: string\n name: string\n hostId: string\n collectionId?: string\n}\n\n/** How far the where-used scan got before the author was asked. */\nexport type PaidMediaUsageCheck = 'full' | 'partial' | 'failed'\n\n/** Which list a flow adds to, for the words it uses. */\nexport type PaidMediaKind = 'video' | 'download'\n\nconst PAID_MEDIA_COPY: Record<\n PaidMediaKind,\n { title: string; lead: string; notAdded: string; unusable: string; choose: string }\n> = {\n video: {\n title: 'Make this video private?',\n lead:\n 'Members videos are private: buyers play them through a link that ' +\n 'expires, and nobody else can open them.',\n notAdded: 'The video was not added.',\n unusable: 'Members cannot play it until it is private.',\n choose: 'Choose the video from the media library, so its links can expire.',\n },\n download: {\n title: 'Make this file private?',\n lead:\n 'Paid downloads are private: buyers get a link that expires, and nobody ' +\n 'else can open the file.',\n notAdded: 'The file was not added.',\n unusable: 'Buyers cannot download it until it is private.',\n choose: 'Choose the file from the media library, so its links can expire.',\n },\n}\n\n/** The request body that names an asset's library to the media routes. */\nexport function paidMediaScopeBody(\n scope: string,\n): { orgId: string } | { hostId: string } {\n return scope.startsWith('org:')\n ? { orgId: scope.slice('org:'.length).split(':')[0] }\n : { hostId: scope }\n}\n\n/**\n * Where an asset is used, apart from the product it is being added to. That\n * product's own row is not a place making it private could break.\n */\nexport function usesBesideProduct(\n references: readonly PaidMediaReference[],\n product: { hostId: string; productId?: string },\n): PaidMediaReference[] {\n return references.filter(\n (reference) =>\n !(\n product.productId &&\n reference.kind === 'plugin' &&\n reference.collectionId === 'products' &&\n reference.hostId === product.hostId &&\n reference.id === product.productId\n ),\n )\n}\n\n/** What the author reads before a video becomes private. */\nexport function paidMediaConfirmation(options: {\n others: readonly PaidMediaReference[]\n checked: PaidMediaUsageCheck\n kind?: PaidMediaKind\n}): string {\n const { others, checked } = options\n const { lead } = PAID_MEDIA_COPY[options.kind ?? 'video']\n let usage: string\n if (others.length) {\n const names = others\n .slice(0, 3)\n .map((reference) => `“${reference.name}”`)\n .join(', ')\n const more = others.length > 3 ? ` and ${others.length - 3} more` : ''\n usage = `It is also used on ${names}${more}, and it stops showing there.`\n } else if (checked === 'full') {\n usage = 'Nothing else uses it.'\n } else if (checked === 'partial') {\n usage =\n 'We could not check everywhere it might be used, so something else ' +\n 'could still rely on it.'\n } else {\n usage = 'We could not check where else it is used.'\n }\n return `${lead} ${usage} Any public link to it that was already shared stops working too.`\n}\n\n/**\n * Adds a picked file to a product as paid media, making it private first when\n * it is not, and makes an already-added one private.\n */\nexport function usePaidMediaAttach(product: {\n hostId: string\n productId?: string\n kind?: PaidMediaKind\n}) {\n const { hostId, productId } = product\n const kind = product.kind ?? 'video'\n const copy = PAID_MEDIA_COPY[kind]\n const { data: user } = useUser()\n const { confirm } = useConfirmationContext()\n const { enqueueSnackbar } = useSnackbar()\n\n const protect = useCallback(\n async (\n asset: { scope: string; mediaId: string },\n options: { adding: boolean },\n ): Promise<boolean> => {\n const scopeBody = paidMediaScopeBody(asset.scope)\n let others: PaidMediaReference[] = []\n let checked: PaidMediaUsageCheck = 'failed'\n try {\n const response = await authorizedFetch(user, '/api/media/references', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ ...scopeBody, mediaId: asset.mediaId }),\n })\n if (response.ok) {\n const payload = await response.json()\n others = usesBesideProduct(\n Array.isArray(payload?.references) ? payload.references : [],\n { hostId, productId },\n )\n checked = payload?.complete ? 'full' : 'partial'\n }\n } catch {\n // Said in the confirmation instead: \"we could not check\" is not\n // \"nothing uses it\".\n }\n const confirmed = await confirm({\n title: copy.title,\n description: paidMediaConfirmation({ others, checked, kind }),\n confirmationText: 'Make private',\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return false\n const response = await authorizedFetch(user, '/api/media/folders', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...scopeBody,\n action: 'set-private',\n mediaId: asset.mediaId,\n private: true,\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n const reason = String(payload?.error ?? 'Could not make the video private')\n enqueueSnackbar(\n `${reason}. ${options.adding ? copy.notAdded : copy.unusable}`,\n { variant: 'error', allowDuplicate: true },\n )\n return false\n }\n if (payload?.rawUrlCleared === false) {\n enqueueSnackbar(\n 'The video is private, but an old public download link to it could ' +\n 'not be confirmed dead. Make it private again from the media ' +\n 'library to retry.',\n { variant: 'warning', persist: false },\n )\n }\n return true\n },\n [confirm, copy, enqueueSnackbar, hostId, kind, productId, user],\n )\n\n /** The value to store on the product, or null when nothing was added. */\n const attach = useCallback(\n async (picked: PickedMedia): Promise<string | null> => {\n const reference =\n picked.mediaScope && picked.mediaId\n ? formatMediaRef(picked.mediaScope, picked.mediaId)\n : undefined\n if (!reference || !picked.mediaScope || !picked.mediaId) {\n enqueueSnackbar(copy.choose, { variant: 'warning', persist: false })\n return null\n }\n if (picked.private) return reference\n const madePrivate = await protect(\n { scope: picked.mediaScope, mediaId: picked.mediaId },\n { adding: true },\n )\n return madePrivate ? reference : null\n },\n [copy, enqueueSnackbar, protect],\n )\n\n return { attach, protect }\n}\n\ntype Protection = 'checking' | 'private' | 'public' | 'missing' | 'unknown'\n\n/**\n * Whether one stored paid file can be delivered, read off its media document.\n * A public one gets the button that fixes it.\n */\nexport function PaidMediaProtection(props: {\n url: string\n hostId: string\n productId?: string\n kind?: PaidMediaKind\n}) {\n const { url, hostId, productId, kind } = props\n const firestore = useFirestore()\n const { protect } = usePaidMediaAttach({ hostId, productId, kind })\n const [protection, setProtection] = useState<Protection>('checking')\n const [generation, setGeneration] = useState(0)\n const source = parsePaidMediaSource(url)\n const asset = paidMediaAssetOf(url)\n\n useEffect(() => {\n const target = paidMediaAssetOf(url)\n if (!target) return\n let active = true\n setProtection('checking')\n const reference = target.scope.startsWith('org:')\n ? doc(\n firestore,\n 'orgs',\n target.scope.slice('org:'.length).split(':')[0],\n 'media',\n target.mediaId,\n )\n : doc(firestore, 'hosts', target.scope, 'media', target.mediaId)\n getDoc(reference)\n .then((snapshot) => {\n if (!active) return\n if (!snapshot.exists() || snapshot.get('deletedAt')) {\n setProtection('missing')\n } else {\n setProtection(snapshot.get('private') === true ? 'private' : 'public')\n }\n })\n .catch(() => {\n if (active) setProtection('unknown')\n })\n return () => {\n active = false\n }\n }, [firestore, url, generation])\n\n if (source.kind === 'external') {\n return (\n <Tooltip title=\"Served by another site, so its links cannot be made to expire.\">\n <Chip size=\"small\" label=\"Hosted elsewhere\" />\n </Tooltip>\n )\n }\n if (source.kind === 'malformed') {\n return <Chip size=\"small\" color=\"error\" label=\"Cannot be played\" />\n }\n if (!asset) return null\n if (protection === 'private') {\n return (\n <Tooltip title=\"Buyers get a link that expires.\">\n <Chip size=\"small\" color=\"success\" label=\"Private\" />\n </Tooltip>\n )\n }\n if (protection === 'public') {\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Tooltip title=\"Anyone with its link can open a public file, so buyers cannot get it until it is private.\">\n <Chip size=\"small\" color=\"warning\" label=\"Public\" />\n </Tooltip>\n <Button\n size=\"small\"\n onClick={() =>\n void protect(asset, { adding: false }).then((madePrivate) => {\n if (madePrivate) setGeneration((value) => value + 1)\n })\n }\n >\n {'Make private'}\n </Button>\n </Stack>\n )\n }\n if (protection === 'missing') {\n return <Chip size=\"small\" color=\"error\" label=\"Not in the media library\" />\n }\n return null\n}\n\n/** One entry of a product's members video list. */\nexport interface MembersVideo {\n url: string\n title?: string\n}\n\n/**\n * A product's members videos: each with whether it can play, and the button\n * that adds one.\n *\n * Its own component so the product editor's body stays free while the editor\n * is closed: the editor is rendered on every visit to the catalog, and this\n * only mounts inside the open dialog.\n */\nexport function MembersVideosField(props: {\n hostId: string\n productId?: string\n videos: readonly MembersVideo[]\n onChange: (videos: MembersVideo[]) => void\n}) {\n const { hostId, productId, videos, onChange } = props\n const { pickMedia } = useMediaPicker()\n const { attach } = usePaidMediaAttach({ hostId, productId })\n return (\n <>\n {videos.map((video, index) => (\n <Stack key={index} direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {`🎬 ${video.title || video.url}`}\n </Typography>\n <PaidMediaProtection url={video.url} hostId={hostId} productId={productId} />\n <Button\n size=\"small\"\n color=\"error\"\n onClick={() =>\n onChange(videos.filter((_item, itemIndex) => itemIndex !== index))\n }\n >\n {'✕'}\n </Button>\n </Stack>\n ))}\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() =>\n void (async () => {\n // A private file is the only kind a members video can be, so the\n // picker offers private files here and nowhere else. And a film is\n // the only kind of file it can play, so the picker lists and\n // uploads nothing else (AGL-2953).\n const media = await pickMedia?.({\n allowPrivate: true,\n kind: 'video',\n })\n if (!media) return\n const url = await attach(media)\n if (!url) return\n onChange([...videos, { url, title: media.fileName ?? '' }])\n })()\n }\n >\n {'Add members video'}\n </Button>\n </>\n )\n}\n\n/**\n * The button that adds a paid download (AGL-2847): the picker offers private\n * files, and a public one is made private before it is added.\n *\n * Its own component for the same reason as {@link MembersVideosField}: the\n * product editor's body runs while the editor is closed, and this only mounts\n * inside the open dialog.\n */\nexport function PaidDownloadAddButton(props: {\n hostId: string\n productId?: string\n onAdd: (file: { url: string; fileName: string }) => void\n}) {\n const { hostId, productId, onAdd } = props\n const { pickMedia } = useMediaPicker()\n const { attach } = usePaidMediaAttach({ hostId, productId, kind: 'download' })\n return (\n <Button\n size=\"small\"\n onClick={() =>\n void (async () => {\n const media = await pickMedia?.({ allowPrivate: true })\n if (!media) return\n const url = await attach(media)\n if (!url) return\n onAdd({ url, fileName: media.fileName ?? 'download' })\n })()\n }\n >\n {'Add file (media library)'}\n </Button>\n )\n}\n"],"names":["formatMediaRef","paidMediaAssetOf","parsePaidMediaSource","useMediaPicker","useConfirmationContext","useSnackbar","authorizedFetch","useFirestore","useUser","Button","Chip","Stack","Tooltip","Typography","doc","getDoc","useCallback","useEffect","useState","PAID_MEDIA_COPY","video","title","lead","notAdded","unusable","choose","download","paidMediaScopeBody","scope","startsWith","orgId","slice","length","split","hostId","usesBesideProduct","references","product","filter","reference","productId","kind","collectionId","id","paidMediaConfirmation","options","others","checked","usage","names","map","name","join","more","usePaidMediaAttach","copy","data","user","confirm","enqueueSnackbar","protect","asset","scopeBody","response","method","headers","body","JSON","stringify","mediaId","ok","payload","json","Array","isArray","complete","confirmed","description","confirmationText","then","catch","action","private","reason","String","error","adding","variant","allowDuplicate","rawUrlCleared","persist","attach","picked","mediaScope","undefined","madePrivate","PaidMediaProtection","props","url","firestore","protection","setProtection","generation","setGeneration","source","target","active","snapshot","exists","get","size","label","color","direction","spacing","sx","alignItems","onClick","value","MembersVideosField","videos","onChange","pickMedia","index","flex","noWrap","_item","itemIndex","alignSelf","media","allowPrivate","fileName","PaidDownloadAddButton","onAdd"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/paid-media.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport {\n formatMediaRef,\n paidMediaAssetOf,\n parsePaidMediaSource,\n type PickedMedia,\n useMediaPicker,\n} from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { Button, Chip, Stack, Tooltip, Typography } from '@mui/material'\nimport { doc, getDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n PAID_MEDIA_COPY,\n paidMediaConfirmation,\n paidMediaScopeBody,\n usesBesideProduct,\n type PaidMediaKind,\n type PaidMediaReference,\n type PaidMediaUsageCheck,\n} from '../../model/paid-media'\n\n/**\n * A product's members videos (AGL-2814) and paid downloads (AGL-2847) are\n * private files.\n *\n * The stream and download routes only ever hand a buyer a link that expires,\n * and they can only do that for a PRIVATE asset: a public one already has a\n * URL that works for anyone, forever, and the routes refuse to deliver it. So\n * adding a file to a product makes it private, and the product editor shows,\n * per file, whether that is still true.\n *\n * Making a file private is visible outside the product. A trailer that is\n * also on a public page stops playing there, and every public link to it that\n * was already shared dies. The author is told which places use it before\n * confirming, from the same where-used scan the media library runs before a\n * delete.\n */\n\nexport {\n paidMediaConfirmation,\n paidMediaScopeBody,\n usesBesideProduct,\n type PaidMediaKind,\n type PaidMediaReference,\n type PaidMediaUsageCheck,\n} from '../../model/paid-media'\n/**\n * Adds a picked file to a product as paid media, making it private first when\n * it is not, and makes an already-added one private.\n */\nexport function usePaidMediaAttach(product: {\n hostId: string\n productId?: string\n kind?: PaidMediaKind\n}) {\n const { hostId, productId } = product\n const kind = product.kind ?? 'video'\n const copy = PAID_MEDIA_COPY[kind]\n const { data: user } = useUser()\n const { confirm } = useConfirmationContext()\n const { enqueueSnackbar } = useSnackbar()\n\n const protect = useCallback(\n async (\n asset: { scope: string; mediaId: string },\n options: { adding: boolean },\n ): Promise<boolean> => {\n const scopeBody = paidMediaScopeBody(asset.scope)\n let others: PaidMediaReference[] = []\n let checked: PaidMediaUsageCheck = 'failed'\n try {\n const response = await authorizedFetch(user, '/api/media/references', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ ...scopeBody, mediaId: asset.mediaId }),\n })\n if (response.ok) {\n const payload = await response.json()\n others = usesBesideProduct(\n Array.isArray(payload?.references) ? payload.references : [],\n { hostId, productId },\n )\n checked = payload?.complete ? 'full' : 'partial'\n }\n } catch {\n // Said in the confirmation instead: \"we could not check\" is not\n // \"nothing uses it\".\n }\n const confirmed = await confirm({\n title: copy.title,\n description: paidMediaConfirmation({ others, checked, kind }),\n confirmationText: 'Make private',\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return false\n const response = await authorizedFetch(user, '/api/media/folders', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...scopeBody,\n action: 'set-private',\n mediaId: asset.mediaId,\n private: true,\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n const reason = String(payload?.error ?? 'Could not make the video private')\n enqueueSnackbar(\n `${reason}. ${options.adding ? copy.notAdded : copy.unusable}`,\n { variant: 'error', allowDuplicate: true },\n )\n return false\n }\n if (payload?.rawUrlCleared === false) {\n enqueueSnackbar(\n 'The video is private, but an old public download link to it could ' +\n 'not be confirmed dead. Make it private again from the media ' +\n 'library to retry.',\n { variant: 'warning', persist: false },\n )\n }\n return true\n },\n [confirm, copy, enqueueSnackbar, hostId, kind, productId, user],\n )\n\n /** The value to store on the product, or null when nothing was added. */\n const attach = useCallback(\n async (picked: PickedMedia): Promise<string | null> => {\n const reference =\n picked.mediaScope && picked.mediaId\n ? formatMediaRef(picked.mediaScope, picked.mediaId)\n : undefined\n if (!reference || !picked.mediaScope || !picked.mediaId) {\n enqueueSnackbar(copy.choose, { variant: 'warning', persist: false })\n return null\n }\n if (picked.private) return reference\n const madePrivate = await protect(\n { scope: picked.mediaScope, mediaId: picked.mediaId },\n { adding: true },\n )\n return madePrivate ? reference : null\n },\n [copy, enqueueSnackbar, protect],\n )\n\n return { attach, protect }\n}\n\ntype Protection = 'checking' | 'private' | 'public' | 'missing' | 'unknown'\n\n/**\n * Whether one stored paid file can be delivered, read off its media document.\n * A public one gets the button that fixes it.\n */\nexport function PaidMediaProtection(props: {\n url: string\n hostId: string\n productId?: string\n kind?: PaidMediaKind\n}) {\n const { url, hostId, productId, kind } = props\n const firestore = useFirestore()\n const { protect } = usePaidMediaAttach({ hostId, productId, kind })\n const [protection, setProtection] = useState<Protection>('checking')\n const [generation, setGeneration] = useState(0)\n const source = parsePaidMediaSource(url)\n const asset = paidMediaAssetOf(url)\n\n useEffect(() => {\n const target = paidMediaAssetOf(url)\n if (!target) return\n let active = true\n setProtection('checking')\n const reference = target.scope.startsWith('org:')\n ? doc(\n firestore,\n 'orgs',\n target.scope.slice('org:'.length).split(':')[0],\n 'media',\n target.mediaId,\n )\n : doc(firestore, 'hosts', target.scope, 'media', target.mediaId)\n getDoc(reference)\n .then((snapshot) => {\n if (!active) return\n if (!snapshot.exists() || snapshot.get('deletedAt')) {\n setProtection('missing')\n } else {\n setProtection(snapshot.get('private') === true ? 'private' : 'public')\n }\n })\n .catch(() => {\n if (active) setProtection('unknown')\n })\n return () => {\n active = false\n }\n }, [firestore, url, generation])\n\n if (source.kind === 'external') {\n return (\n <Tooltip title=\"Served by another site, so its links cannot be made to expire.\">\n <Chip size=\"small\" label=\"Hosted elsewhere\" />\n </Tooltip>\n )\n }\n if (source.kind === 'malformed') {\n return <Chip size=\"small\" color=\"error\" label=\"Cannot be played\" />\n }\n if (!asset) return null\n if (protection === 'private') {\n return (\n <Tooltip title=\"Buyers get a link that expires.\">\n <Chip size=\"small\" color=\"success\" label=\"Private\" />\n </Tooltip>\n )\n }\n if (protection === 'public') {\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Tooltip title=\"Anyone with its link can open a public file, so buyers cannot get it until it is private.\">\n <Chip size=\"small\" color=\"warning\" label=\"Public\" />\n </Tooltip>\n <Button\n size=\"small\"\n onClick={() =>\n void protect(asset, { adding: false }).then((madePrivate) => {\n if (madePrivate) setGeneration((value) => value + 1)\n })\n }\n >\n {'Make private'}\n </Button>\n </Stack>\n )\n }\n if (protection === 'missing') {\n return <Chip size=\"small\" color=\"error\" label=\"Not in the media library\" />\n }\n return null\n}\n\n/** One entry of a product's members video list. */\nexport interface MembersVideo {\n url: string\n title?: string\n}\n\n/**\n * A product's members videos: each with whether it can play, and the button\n * that adds one.\n *\n * Its own component so the product editor's body stays free while the editor\n * is closed: the editor is rendered on every visit to the catalog, and this\n * only mounts inside the open dialog.\n */\nexport function MembersVideosField(props: {\n hostId: string\n productId?: string\n videos: readonly MembersVideo[]\n onChange: (videos: MembersVideo[]) => void\n}) {\n const { hostId, productId, videos, onChange } = props\n const { pickMedia } = useMediaPicker()\n const { attach } = usePaidMediaAttach({ hostId, productId })\n return (\n <>\n {videos.map((video, index) => (\n <Stack key={index} direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {`🎬 ${video.title || video.url}`}\n </Typography>\n <PaidMediaProtection url={video.url} hostId={hostId} productId={productId} />\n <Button\n size=\"small\"\n color=\"error\"\n onClick={() =>\n onChange(videos.filter((_item, itemIndex) => itemIndex !== index))\n }\n >\n {'✕'}\n </Button>\n </Stack>\n ))}\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() =>\n void (async () => {\n // A private file is the only kind a members video can be, so the\n // picker offers private files here and nowhere else. And a film is\n // the only kind of file it can play, so the picker lists and\n // uploads nothing else (AGL-2953).\n const media = await pickMedia?.({\n allowPrivate: true,\n kind: 'video',\n })\n if (!media) return\n const url = await attach(media)\n if (!url) return\n onChange([...videos, { url, title: media.fileName ?? '' }])\n })()\n }\n >\n {'Add members video'}\n </Button>\n </>\n )\n}\n\n/**\n * The button that adds a paid download (AGL-2847): the picker offers private\n * files, and a public one is made private before it is added.\n *\n * Its own component for the same reason as {@link MembersVideosField}: the\n * product editor's body runs while the editor is closed, and this only mounts\n * inside the open dialog.\n */\nexport function PaidDownloadAddButton(props: {\n hostId: string\n productId?: string\n onAdd: (file: { url: string; fileName: string }) => void\n}) {\n const { hostId, productId, onAdd } = props\n const { pickMedia } = useMediaPicker()\n const { attach } = usePaidMediaAttach({ hostId, productId, kind: 'download' })\n return (\n <Button\n size=\"small\"\n onClick={() =>\n void (async () => {\n const media = await pickMedia?.({ allowPrivate: true })\n if (!media) return\n const url = await attach(media)\n if (!url) return\n onAdd({ url, fileName: media.fileName ?? 'download' })\n })()\n }\n >\n {'Add file (media library)'}\n </Button>\n )\n}\n"],"names":["formatMediaRef","paidMediaAssetOf","parsePaidMediaSource","useMediaPicker","useConfirmationContext","useSnackbar","authorizedFetch","useFirestore","useUser","Button","Chip","Stack","Tooltip","Typography","doc","getDoc","useCallback","useEffect","useState","PAID_MEDIA_COPY","paidMediaConfirmation","paidMediaScopeBody","usesBesideProduct","usePaidMediaAttach","product","hostId","productId","kind","copy","data","user","confirm","enqueueSnackbar","protect","asset","options","scopeBody","scope","others","checked","response","method","headers","body","JSON","stringify","mediaId","ok","payload","json","Array","isArray","references","complete","confirmed","title","description","confirmationText","then","catch","action","private","reason","String","error","adding","notAdded","unusable","variant","allowDuplicate","rawUrlCleared","persist","attach","picked","reference","mediaScope","undefined","choose","madePrivate","PaidMediaProtection","props","url","firestore","protection","setProtection","generation","setGeneration","source","target","active","startsWith","slice","length","split","snapshot","exists","get","size","label","color","direction","spacing","sx","alignItems","onClick","value","MembersVideosField","videos","onChange","pickMedia","map","video","index","flex","noWrap","filter","_item","itemIndex","alignSelf","media","allowPrivate","fileName","PaidDownloadAddButton","onAdd"],"mappings":"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"}
@@ -169,6 +169,19 @@ const day = (dayMs)=>dayMs ? new Date(dayMs).toISOString().slice(0, 10) : '—';
169
169
  const checkInDayMs = Date.parse(`${walkIn.checkIn}T00:00:00Z`);
170
170
  const checkOutDayMs = Date.parse(`${walkIn.checkOut}T00:00:00Z`);
171
171
  /*
172
+ * The stay is priced FIRST, and its `problem` stops the walk-in: the quote
173
+ * is the one rule for what can be booked (no nights, under the minimum
174
+ * stay), shared with the booking widget and both native apps. Ignoring it
175
+ * wrote a confirmed zero-night, $0 stay for a check-out on the arrival
176
+ * day (AGL-3794).
177
+ */ const quote = CommerceModel.computeReservationQuote(resource, checkInDayMs, checkOutDayMs);
178
+ if (quote.problem) {
179
+ return void enqueueSnackbar(quote.problem, {
180
+ variant: 'warning',
181
+ persist: false
182
+ });
183
+ }
184
+ /*
172
185
  * Every stay of THIS resource that ends after the walk-in arrives — the
173
186
  * same query `reserve.ts` checks a booking against, on the composite it
174
187
  * already has (AGL-3321). This used to test the rows on the card's
@@ -197,7 +210,6 @@ const day = (dayMs)=>dayMs ? new Date(dayMs).toISOString().slice(0, 10) : '—';
197
210
  persist: false
198
211
  });
199
212
  }
200
- const quote = CommerceModel.computeReservationQuote(resource, checkInDayMs, checkOutDayMs);
201
213
  await setDoc(doc(firestore, 'hosts', hostId, 'reservations', Aglyn.createResourceUid()), {
202
214
  resourceId: walkIn.resourceId,
203
215
  status: 'confirmed',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/reservations-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport * as CommerceModel from '../../model'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n deleteDoc,\n doc,\n getDocs,\n limit,\n orderBy,\n query,\n setDoc,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n useFirestoreCollection,\n usePagedCollection,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface ReservationsCardProps {\n hostId: string\n}\n\nconst STATUS_COLOR: Record<\n string,\n 'default' | 'success' | 'warning' | 'error' | 'info'\n> = {\n pending: 'warning',\n confirmed: 'info',\n checked_in: 'success',\n checked_out: 'default',\n cancelled: 'default',\n no_show: 'error',\n}\n\nconst usd = (cents: number | undefined) =>\n `$${((cents ?? 0) / 100).toFixed(2)}`\nconst day = (dayMs: number | undefined) =>\n dayMs ? new Date(dayMs).toISOString().slice(0, 10) : '—'\n\n/**\n * Reservations console (AGL-311): resources CRUD (nightly pricing,\n * seasons via multipliers, deposits) plus the reservations list with\n * check-in/out, no-show, cancel, and walk-in creation. The storefront\n * widget books against the same docs.\n */\nexport function ReservationsCard(props: ReservationsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const {\n data: resourceDocs,\n status: resourcesStatus,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing spreads a whole stored resource into\n * `resourceDraft` and writes all of it back, so `merge: true` protects\n * nothing. `blocks` is the field that costs more than money: it is the\n * manually blocked date ranges `isRangeAvailable` reads, so a cached seed\n * re-opens dates that were closed and lets the next guest book a room\n * that is already spoken for.\n */\n fromCache: resourcesFromCache,\n } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'resources'), limit(50)),\n [firestore, hostId],\n { idField: '$id' },\n )\n /*\n * Ordered by the server, and a growing window rather than a fixed 300.\n *\n * `limit(300)` carried no `orderBy`, so Firestore returned DOCUMENT-ID\n * order and the client sort below arranged that pseudo-random sample by\n * check-in. The ordering is unchanged — earliest check-in first, exactly\n * what the sort did — it is now the QUERY's, so the window is the real\n * front of the list and the rest can be reached.\n */\n const {\n rows: reservations,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<any>(\n (pageLimit) =>\n query(\n collection(firestore, 'hosts', hostId, 'reservations'),\n orderBy('checkInDayMs', 'asc'),\n limit(pageLimit),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const resourceNames = useMemo(() => {\n const map: Record<string, string> = {}\n for (const resource of resourceDocs ?? []) {\n map[resource.$id] = resource.name\n }\n return map\n }, [resourceDocs])\n\n const [resourceDraft, setResourceDraft] = useState<\n (Partial<CommerceModel.HostResource> & { id: string | null }) | null\n >(null)\n const [walkIn, setWalkIn] = useState<{\n resourceId: string\n checkIn: string\n checkOut: string\n guestName: string\n } | null>(null)\n\n const handleResourceSave = useCallback(async () => {\n if (!resourceDraft?.name?.trim() || !(resourceDraft.nightlyRateUsd! > 0)) {\n return\n }\n /**\n * `$id` comes off with `id` (AGL-1374).\n *\n * The editor is seeded `{ id: resource.$id, ...resource }`, and that\n * spread carries the listener's SYNTHETIC `$id` — `idField: '$id'`\n * stamps the document id onto the in-memory row, where nothing persists\n * it. Destructuring only `id` left `$id` in `data`, so every save wrote\n * it into the stored document as a real field. Nothing reads it; it is\n * the listener's bookkeeping leaking into storage, and once there no\n * reader can tell it from a field the card meant to write.\n */\n const seeded = resourceDraft as typeof resourceDraft & { $id?: string }\n const { id, $id: _syntheticId, ...data } = seeded\n /**\n * Refuse an EDIT whose seed the server never confirmed (AGL-1358).\n * `data` is the whole stored row spread out of the listener, so\n * `merge: true` has nothing left to protect — every field is present,\n * `blocks` and the seasonal multipliers included.\n *\n * Only the edit path. A NEW resource is built from defaults at a fresh\n * uid and can overwrite nothing, and the first snapshot of any listener\n * is `fromCache: true`, so guarding a create would refuse a save that was\n * never unsafe.\n *\n * The guard WRAPS the write — an early return is a shape you can keep\n * while losing the protection.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'resource',\n unreadable: Boolean(id) && resourcesStatus === 'error',\n fromCache: Boolean(id) && resourcesFromCache,\n },\n async () => {\n await setDoc(\n doc(\n firestore,\n 'hosts',\n hostId,\n 'resources',\n id ?? Aglyn.createResourceUid(),\n ),\n {\n ...data,\n name: resourceDraft.name!.trim().slice(0, 120),\n },\n { merge: true },\n )\n },\n )\n // Before `setResourceDraft(null)`, so a refusal keeps the dialog open\n // with what was typed.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setResourceDraft(null)\n enqueueSnackbar('Resource saved', { variant: 'success', persist: false })\n }, [\n resourceDraft,\n firestore,\n hostId,\n enqueueSnackbar,\n resourcesStatus,\n resourcesFromCache,\n ])\n\n const handleStatus = useCallback(\n (reservation: any, status: CommerceModel.ReservationStatus) => async () => {\n await updateDoc(\n doc(firestore, 'hosts', hostId, 'reservations', reservation.$id),\n { status },\n )\n },\n [firestore, hostId],\n )\n\n const handleWalkIn = useCallback(async () => {\n if (!walkIn?.resourceId || !walkIn.checkIn || !walkIn.checkOut) return\n const resource = (resourceDocs ?? []).find(\n (item: any) => item.$id === walkIn.resourceId,\n ) as CommerceModel.HostResource | undefined\n if (!resource) return\n const checkInDayMs = Date.parse(`${walkIn.checkIn}T00:00:00Z`)\n const checkOutDayMs = Date.parse(`${walkIn.checkOut}T00:00:00Z`)\n /*\n * Every stay of THIS resource that ends after the walk-in arrives — the\n * same query `reserve.ts` checks a booking against, on the composite it\n * already has (AGL-3321). This used to test the rows on the card's\n * current page, so a stay on the next page did not block the dates and\n * the desk could double-book the room.\n */\n const overlapping = await getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'reservations'),\n where('resourceId', '==', walkIn.resourceId),\n where('checkOutDayMs', '>', checkInDayMs),\n orderBy('checkOutDayMs'),\n limit(500),\n ),\n ).catch(() => null)\n if (!overlapping) {\n return void enqueueSnackbar('Could not check those dates — try again', {\n variant: 'warning',\n persist: false,\n })\n }\n // `createdAtMs` carried through because the pending hold lapses on it\n // (`reservationHoldsDates`). Dropping it made every unpaid `pending` row\n // block forever, so the front desk was refused a walk-in into a room that\n // an abandoned online checkout had left empty.\n const existing = overlapping.docs\n .map((snapshot) => snapshot.data() as any)\n .map((item: any) => ({\n checkInDayMs: item.checkInDayMs,\n checkOutDayMs: item.checkOutDayMs,\n status: item.status,\n createdAtMs: item.createdAtMs,\n }))\n if (\n !CommerceModel.isRangeAvailable(resource, existing, checkInDayMs, checkOutDayMs)\n ) {\n return void enqueueSnackbar('Those dates are taken', {\n variant: 'warning',\n persist: false,\n })\n }\n const quote = CommerceModel.computeReservationQuote(\n resource,\n checkInDayMs,\n checkOutDayMs,\n )\n await setDoc(\n doc(\n firestore,\n 'hosts',\n hostId,\n 'reservations',\n Aglyn.createResourceUid(),\n ),\n {\n resourceId: walkIn.resourceId,\n status: 'confirmed',\n checkInDayMs,\n checkOutDayMs,\n guestName: walkIn.guestName || null,\n guestEmail: null,\n nights: quote.nights,\n totalCents: quote.totalCents,\n depositCents: 0,\n paidCents: 0,\n createdAtMs: Date.now(),\n } satisfies CommerceModel.HostReservation,\n )\n setWalkIn(null)\n enqueueSnackbar('Walk-in reserved — collect payment at POS', {\n variant: 'success',\n persist: false,\n })\n }, [walkIn, resourceDocs, firestore, hostId, enqueueSnackbar])\n\n return (\n <CardDisplay\n header={'Reservations'}\n help={pluginDocsHelp('pos', { anchor: '#reservations' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Resources'}</Typography>\n {(resourceDocs ?? []).map((resource: any) => (\n <Stack\n key={resource.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {`${resource.name} · $${resource.nightlyRateUsd}/night`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`id: ${resource.$id}`}\n </Typography>\n </Stack>\n <Button\n size=\"small\"\n onClick={() => setResourceDraft({ id: resource.$id, ...resource })}\n >\n {'Edit'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={async () => {\n const confirmed = await confirm({\n title: 'Remove this resource?',\n description:\n 'Existing reservations keep their records; the widget ' +\n 'stops accepting new stays.',\n confirmationText: 'Remove',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await deleteDoc(\n doc(firestore, 'hosts', hostId, 'resources', resource.$id),\n )\n }}\n >\n {'Remove'}\n </Button>\n </Stack>\n ))}\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n onClick={() =>\n setResourceDraft({\n id: null,\n name: '',\n nightlyRateUsd: 100,\n depositPct: 30,\n minNights: 1,\n })\n }\n >\n {'Add resource'}\n </Button>\n <Button\n size=\"small\"\n disabled={(resourceDocs?.length ?? 0) === 0}\n onClick={() =>\n setWalkIn({\n resourceId: resourceDocs?.[0]?.$id ?? '',\n checkIn: '',\n checkOut: '',\n guestName: '',\n })\n }\n >\n {'Walk-in'}\n </Button>\n </Stack>\n\n <Typography variant=\"subtitle2\" sx={{ mt: 1 }}>\n {'Upcoming & recent stays'}\n </Typography>\n {reservations.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Reservations from the widget (and walk-ins) appear here.'}\n </Typography>\n ) : (\n reservations.map((reservation: any) => (\n <Stack\n key={reservation.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {`${resourceNames[reservation.resourceId] ?? '?'} · ` +\n `${day(reservation.checkInDayMs)} → ${day(reservation.checkOutDayMs)}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {[\n reservation.guestName ?? reservation.guestEmail ?? 'Guest',\n `${usd(reservation.paidCents)} / ${usd(reservation.totalCents)} paid`,\n ].join(' · ')}\n </Typography>\n </Stack>\n <Chip\n label={String(reservation.status).replace('_', ' ')}\n size=\"small\"\n variant=\"outlined\"\n color={STATUS_COLOR[reservation.status] ?? 'default'}\n />\n {reservation.status === 'confirmed' ? (\n <>\n <Button size=\"small\" onClick={handleStatus(reservation, 'checked_in')}>\n {'Check in'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleStatus(reservation, 'no_show')}\n >\n {'No-show'}\n </Button>\n </>\n ) : null}\n {reservation.status === 'checked_in' ? (\n <Button\n size=\"small\"\n onClick={async () => {\n // Folio settlement (AGL-317): surface unpaid room\n // charges before the guest leaves.\n const folio = (reservation.folio ?? []) as Array<{\n amountCents: number\n }>\n const folioCents = folio.reduce(\n (sum, entry) => sum + (entry.amountCents ?? 0),\n 0,\n )\n const balanceCents =\n (reservation.totalCents ?? 0) -\n (reservation.paidCents ?? 0)\n if (folioCents > 0 || balanceCents > 0) {\n const confirmed = await confirm({\n title: 'Check out with open balance?',\n description:\n `${folioCents > 0 ? `Room charges: ${usd(folioCents)} (already recorded as paid POS orders). ` : ''}` +\n `${balanceCents > 0 ? `Stay balance due: ${usd(balanceCents)} — collect at the register.` : ''}`,\n confirmationText: 'Check out',\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n }\n await handleStatus(reservation, 'checked_out')()\n }}\n >\n {'Check out'}\n </Button>\n ) : null}\n {['pending', 'confirmed'].includes(reservation.status) ? (\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleStatus(reservation, 'cancelled')}\n >\n {'Cancel'}\n </Button>\n ) : null}\n </Stack>\n ))\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={reservations.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n\n <Dialog\n open={Boolean(resourceDraft)}\n onClose={() => setResourceDraft(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>\n {resourceDraft?.id ? 'Edit resource' : 'New resource'}\n </DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Name\"\n value={resourceDraft?.name ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev ? { ...prev, name: event.target.value } : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n placeholder=\"Lakeside cabin\"\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Nightly rate ($)\"\n value={resourceDraft?.nightlyRateUsd ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n nightlyRateUsd: Number(event.target.value) || 0,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Weekend ×\"\n placeholder=\"1\"\n value={resourceDraft?.weekendMultiplier ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n weekendMultiplier:\n Number(event.target.value) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n </Stack>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Min nights\"\n value={resourceDraft?.minNights ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n minNights:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"Deposit %\"\n placeholder=\"100 = pay in full\"\n value={resourceDraft?.depositPct ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n depositPct:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"Free cancel (hrs)\"\n value={resourceDraft?.cancellationHours ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n cancellationHours:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setResourceDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={\n !resourceDraft?.name?.trim() ||\n !((resourceDraft?.nightlyRateUsd ?? 0) > 0)\n }\n onClick={handleResourceSave}\n >\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog\n open={Boolean(walkIn)}\n onClose={() => setWalkIn(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Walk-in reservation'}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Resource\"\n value={walkIn?.resourceId ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, resourceId: event.target.value } : prev,\n )\n }\n size=\"small\"\n select\n sx={{ mt: 1 }}\n >\n {(resourceDocs ?? []).map((resource: any) => (\n <MenuItem key={resource.$id} value={resource.$id}>\n {resource.name}\n </MenuItem>\n ))}\n </TextField>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Check-in\"\n type=\"date\"\n value={walkIn?.checkIn ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, checkIn: event.target.value } : prev,\n )\n }\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n <TextField\n label=\"Check-out\"\n type=\"date\"\n value={walkIn?.checkOut ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, checkOut: event.target.value } : prev,\n )\n }\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n </Stack>\n <TextField\n label=\"Guest name\"\n value={walkIn?.guestName ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, guestName: event.target.value } : prev,\n )\n }\n size=\"small\"\n />\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setWalkIn(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!walkIn?.resourceId || !walkIn?.checkIn || !walkIn?.checkOut}\n onClick={handleWalkIn}\n >\n {'Reserve'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nReservationsCard.displayName = 'ReservationsCard'\n\nexport default ReservationsCard\n"],"names":["Aglyn","CommerceModel","CardDisplay","useConfirmationContext","ListPagination","useSnackbar","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Stack","TextField","Typography","collection","deleteDoc","doc","getDocs","limit","orderBy","query","setDoc","updateDoc","where","useCallback","useMemo","useState","useFirestore","useFirestoreCollection","usePagedCollection","writeGuardedBySeed","pluginDocsHelp","STATUS_COLOR","pending","confirmed","checked_in","checked_out","cancelled","no_show","usd","cents","toFixed","day","dayMs","Date","toISOString","slice","ReservationsCard","props","resourceDraft","hostId","firestore","enqueueSnackbar","confirm","data","resourceDocs","status","resourcesStatus","fromCache","resourcesFromCache","idField","rows","reservations","hasMore","page","setPage","pageSize","setPageSize","pageLimit","resourceNames","map","resource","$id","name","setResourceDraft","walkIn","setWalkIn","handleResourceSave","trim","nightlyRateUsd","seeded","id","_syntheticId","verdict","subject","unreadable","Boolean","createResourceUid","merge","ok","message","variant","persist","handleStatus","reservation","handleWalkIn","resourceId","checkIn","checkOut","find","item","checkInDayMs","parse","checkOutDayMs","overlapping","catch","existing","docs","snapshot","createdAtMs","isRangeAvailable","quote","computeReservationQuote","guestName","guestEmail","nights","totalCents","depositCents","paidCents","now","header","help","anchor","contentGutterX","contentGutterY","spacing","direction","sx","alignItems","flex","minWidth","noWrap","color","size","onClick","title","description","confirmationText","confirmationButtonProps","then","depositPct","minNights","disabled","length","mt","join","label","String","replace","folio","folioCents","reduce","sum","entry","amountCents","balanceCents","includes","rowCount","onPageChange","onPageSizeChange","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","value","onChange","event","prev","target","autoFocus","placeholder","Number","slotProps","htmlInput","inputMode","weekendMultiplier","undefined","Math","round","cancellationHours","select","type","inputLabel","shrink","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/reservations-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport * as CommerceModel from '../../model'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n deleteDoc,\n doc,\n getDocs,\n limit,\n orderBy,\n query,\n setDoc,\n updateDoc,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n useFirestoreCollection,\n usePagedCollection,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface ReservationsCardProps {\n hostId: string\n}\n\nconst STATUS_COLOR: Record<\n string,\n 'default' | 'success' | 'warning' | 'error' | 'info'\n> = {\n pending: 'warning',\n confirmed: 'info',\n checked_in: 'success',\n checked_out: 'default',\n cancelled: 'default',\n no_show: 'error',\n}\n\nconst usd = (cents: number | undefined) =>\n `$${((cents ?? 0) / 100).toFixed(2)}`\nconst day = (dayMs: number | undefined) =>\n dayMs ? new Date(dayMs).toISOString().slice(0, 10) : '—'\n\n/**\n * Reservations console (AGL-311): resources CRUD (nightly pricing,\n * seasons via multipliers, deposits) plus the reservations list with\n * check-in/out, no-show, cancel, and walk-in creation. The storefront\n * widget books against the same docs.\n */\nexport function ReservationsCard(props: ReservationsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const {\n data: resourceDocs,\n status: resourcesStatus,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing spreads a whole stored resource into\n * `resourceDraft` and writes all of it back, so `merge: true` protects\n * nothing. `blocks` is the field that costs more than money: it is the\n * manually blocked date ranges `isRangeAvailable` reads, so a cached seed\n * re-opens dates that were closed and lets the next guest book a room\n * that is already spoken for.\n */\n fromCache: resourcesFromCache,\n } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'resources'), limit(50)),\n [firestore, hostId],\n { idField: '$id' },\n )\n /*\n * Ordered by the server, and a growing window rather than a fixed 300.\n *\n * `limit(300)` carried no `orderBy`, so Firestore returned DOCUMENT-ID\n * order and the client sort below arranged that pseudo-random sample by\n * check-in. The ordering is unchanged — earliest check-in first, exactly\n * what the sort did — it is now the QUERY's, so the window is the real\n * front of the list and the rest can be reached.\n */\n const {\n rows: reservations,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<any>(\n (pageLimit) =>\n query(\n collection(firestore, 'hosts', hostId, 'reservations'),\n orderBy('checkInDayMs', 'asc'),\n limit(pageLimit),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const resourceNames = useMemo(() => {\n const map: Record<string, string> = {}\n for (const resource of resourceDocs ?? []) {\n map[resource.$id] = resource.name\n }\n return map\n }, [resourceDocs])\n\n const [resourceDraft, setResourceDraft] = useState<\n (Partial<CommerceModel.HostResource> & { id: string | null }) | null\n >(null)\n const [walkIn, setWalkIn] = useState<{\n resourceId: string\n checkIn: string\n checkOut: string\n guestName: string\n } | null>(null)\n\n const handleResourceSave = useCallback(async () => {\n if (!resourceDraft?.name?.trim() || !(resourceDraft.nightlyRateUsd! > 0)) {\n return\n }\n /**\n * `$id` comes off with `id` (AGL-1374).\n *\n * The editor is seeded `{ id: resource.$id, ...resource }`, and that\n * spread carries the listener's SYNTHETIC `$id` — `idField: '$id'`\n * stamps the document id onto the in-memory row, where nothing persists\n * it. Destructuring only `id` left `$id` in `data`, so every save wrote\n * it into the stored document as a real field. Nothing reads it; it is\n * the listener's bookkeeping leaking into storage, and once there no\n * reader can tell it from a field the card meant to write.\n */\n const seeded = resourceDraft as typeof resourceDraft & { $id?: string }\n const { id, $id: _syntheticId, ...data } = seeded\n /**\n * Refuse an EDIT whose seed the server never confirmed (AGL-1358).\n * `data` is the whole stored row spread out of the listener, so\n * `merge: true` has nothing left to protect — every field is present,\n * `blocks` and the seasonal multipliers included.\n *\n * Only the edit path. A NEW resource is built from defaults at a fresh\n * uid and can overwrite nothing, and the first snapshot of any listener\n * is `fromCache: true`, so guarding a create would refuse a save that was\n * never unsafe.\n *\n * The guard WRAPS the write — an early return is a shape you can keep\n * while losing the protection.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'resource',\n unreadable: Boolean(id) && resourcesStatus === 'error',\n fromCache: Boolean(id) && resourcesFromCache,\n },\n async () => {\n await setDoc(\n doc(\n firestore,\n 'hosts',\n hostId,\n 'resources',\n id ?? Aglyn.createResourceUid(),\n ),\n {\n ...data,\n name: resourceDraft.name!.trim().slice(0, 120),\n },\n { merge: true },\n )\n },\n )\n // Before `setResourceDraft(null)`, so a refusal keeps the dialog open\n // with what was typed.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setResourceDraft(null)\n enqueueSnackbar('Resource saved', { variant: 'success', persist: false })\n }, [\n resourceDraft,\n firestore,\n hostId,\n enqueueSnackbar,\n resourcesStatus,\n resourcesFromCache,\n ])\n\n const handleStatus = useCallback(\n (reservation: any, status: CommerceModel.ReservationStatus) => async () => {\n await updateDoc(\n doc(firestore, 'hosts', hostId, 'reservations', reservation.$id),\n { status },\n )\n },\n [firestore, hostId],\n )\n\n const handleWalkIn = useCallback(async () => {\n if (!walkIn?.resourceId || !walkIn.checkIn || !walkIn.checkOut) return\n const resource = (resourceDocs ?? []).find(\n (item: any) => item.$id === walkIn.resourceId,\n ) as CommerceModel.HostResource | undefined\n if (!resource) return\n const checkInDayMs = Date.parse(`${walkIn.checkIn}T00:00:00Z`)\n const checkOutDayMs = Date.parse(`${walkIn.checkOut}T00:00:00Z`)\n /*\n * The stay is priced FIRST, and its `problem` stops the walk-in: the quote\n * is the one rule for what can be booked (no nights, under the minimum\n * stay), shared with the booking widget and both native apps. Ignoring it\n * wrote a confirmed zero-night, $0 stay for a check-out on the arrival\n * day (AGL-3794).\n */\n const quote = CommerceModel.computeReservationQuote(\n resource,\n checkInDayMs,\n checkOutDayMs,\n )\n if (quote.problem) {\n return void enqueueSnackbar(quote.problem, {\n variant: 'warning',\n persist: false,\n })\n }\n /*\n * Every stay of THIS resource that ends after the walk-in arrives — the\n * same query `reserve.ts` checks a booking against, on the composite it\n * already has (AGL-3321). This used to test the rows on the card's\n * current page, so a stay on the next page did not block the dates and\n * the desk could double-book the room.\n */\n const overlapping = await getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'reservations'),\n where('resourceId', '==', walkIn.resourceId),\n where('checkOutDayMs', '>', checkInDayMs),\n orderBy('checkOutDayMs'),\n limit(500),\n ),\n ).catch(() => null)\n if (!overlapping) {\n return void enqueueSnackbar('Could not check those dates — try again', {\n variant: 'warning',\n persist: false,\n })\n }\n // `createdAtMs` carried through because the pending hold lapses on it\n // (`reservationHoldsDates`). Dropping it made every unpaid `pending` row\n // block forever, so the front desk was refused a walk-in into a room that\n // an abandoned online checkout had left empty.\n const existing = overlapping.docs\n .map((snapshot) => snapshot.data() as any)\n .map((item: any) => ({\n checkInDayMs: item.checkInDayMs,\n checkOutDayMs: item.checkOutDayMs,\n status: item.status,\n createdAtMs: item.createdAtMs,\n }))\n if (\n !CommerceModel.isRangeAvailable(resource, existing, checkInDayMs, checkOutDayMs)\n ) {\n return void enqueueSnackbar('Those dates are taken', {\n variant: 'warning',\n persist: false,\n })\n }\n await setDoc(\n doc(\n firestore,\n 'hosts',\n hostId,\n 'reservations',\n Aglyn.createResourceUid(),\n ),\n {\n resourceId: walkIn.resourceId,\n status: 'confirmed',\n checkInDayMs,\n checkOutDayMs,\n guestName: walkIn.guestName || null,\n guestEmail: null,\n nights: quote.nights,\n totalCents: quote.totalCents,\n depositCents: 0,\n paidCents: 0,\n createdAtMs: Date.now(),\n } satisfies CommerceModel.HostReservation,\n )\n setWalkIn(null)\n enqueueSnackbar('Walk-in reserved — collect payment at POS', {\n variant: 'success',\n persist: false,\n })\n }, [walkIn, resourceDocs, firestore, hostId, enqueueSnackbar])\n\n return (\n <CardDisplay\n header={'Reservations'}\n help={pluginDocsHelp('pos', { anchor: '#reservations' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Resources'}</Typography>\n {(resourceDocs ?? []).map((resource: any) => (\n <Stack\n key={resource.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {`${resource.name} · $${resource.nightlyRateUsd}/night`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`id: ${resource.$id}`}\n </Typography>\n </Stack>\n <Button\n size=\"small\"\n onClick={() => setResourceDraft({ id: resource.$id, ...resource })}\n >\n {'Edit'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={async () => {\n const confirmed = await confirm({\n title: 'Remove this resource?',\n description:\n 'Existing reservations keep their records; the widget ' +\n 'stops accepting new stays.',\n confirmationText: 'Remove',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await deleteDoc(\n doc(firestore, 'hosts', hostId, 'resources', resource.$id),\n )\n }}\n >\n {'Remove'}\n </Button>\n </Stack>\n ))}\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n onClick={() =>\n setResourceDraft({\n id: null,\n name: '',\n nightlyRateUsd: 100,\n depositPct: 30,\n minNights: 1,\n })\n }\n >\n {'Add resource'}\n </Button>\n <Button\n size=\"small\"\n disabled={(resourceDocs?.length ?? 0) === 0}\n onClick={() =>\n setWalkIn({\n resourceId: resourceDocs?.[0]?.$id ?? '',\n checkIn: '',\n checkOut: '',\n guestName: '',\n })\n }\n >\n {'Walk-in'}\n </Button>\n </Stack>\n\n <Typography variant=\"subtitle2\" sx={{ mt: 1 }}>\n {'Upcoming & recent stays'}\n </Typography>\n {reservations.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Reservations from the widget (and walk-ins) appear here.'}\n </Typography>\n ) : (\n reservations.map((reservation: any) => (\n <Stack\n key={reservation.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {`${resourceNames[reservation.resourceId] ?? '?'} · ` +\n `${day(reservation.checkInDayMs)} → ${day(reservation.checkOutDayMs)}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {[\n reservation.guestName ?? reservation.guestEmail ?? 'Guest',\n `${usd(reservation.paidCents)} / ${usd(reservation.totalCents)} paid`,\n ].join(' · ')}\n </Typography>\n </Stack>\n <Chip\n label={String(reservation.status).replace('_', ' ')}\n size=\"small\"\n variant=\"outlined\"\n color={STATUS_COLOR[reservation.status] ?? 'default'}\n />\n {reservation.status === 'confirmed' ? (\n <>\n <Button size=\"small\" onClick={handleStatus(reservation, 'checked_in')}>\n {'Check in'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleStatus(reservation, 'no_show')}\n >\n {'No-show'}\n </Button>\n </>\n ) : null}\n {reservation.status === 'checked_in' ? (\n <Button\n size=\"small\"\n onClick={async () => {\n // Folio settlement (AGL-317): surface unpaid room\n // charges before the guest leaves.\n const folio = (reservation.folio ?? []) as Array<{\n amountCents: number\n }>\n const folioCents = folio.reduce(\n (sum, entry) => sum + (entry.amountCents ?? 0),\n 0,\n )\n const balanceCents =\n (reservation.totalCents ?? 0) -\n (reservation.paidCents ?? 0)\n if (folioCents > 0 || balanceCents > 0) {\n const confirmed = await confirm({\n title: 'Check out with open balance?',\n description:\n `${folioCents > 0 ? `Room charges: ${usd(folioCents)} (already recorded as paid POS orders). ` : ''}` +\n `${balanceCents > 0 ? `Stay balance due: ${usd(balanceCents)} — collect at the register.` : ''}`,\n confirmationText: 'Check out',\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n }\n await handleStatus(reservation, 'checked_out')()\n }}\n >\n {'Check out'}\n </Button>\n ) : null}\n {['pending', 'confirmed'].includes(reservation.status) ? (\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleStatus(reservation, 'cancelled')}\n >\n {'Cancel'}\n </Button>\n ) : null}\n </Stack>\n ))\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={reservations.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n\n <Dialog\n open={Boolean(resourceDraft)}\n onClose={() => setResourceDraft(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>\n {resourceDraft?.id ? 'Edit resource' : 'New resource'}\n </DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Name\"\n value={resourceDraft?.name ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev ? { ...prev, name: event.target.value } : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n placeholder=\"Lakeside cabin\"\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Nightly rate ($)\"\n value={resourceDraft?.nightlyRateUsd ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n nightlyRateUsd: Number(event.target.value) || 0,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Weekend ×\"\n placeholder=\"1\"\n value={resourceDraft?.weekendMultiplier ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n weekendMultiplier:\n Number(event.target.value) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n </Stack>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Min nights\"\n value={resourceDraft?.minNights ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n minNights:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"Deposit %\"\n placeholder=\"100 = pay in full\"\n value={resourceDraft?.depositPct ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n depositPct:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n <TextField\n label=\"Free cancel (hrs)\"\n value={resourceDraft?.cancellationHours ?? ''}\n onChange={(event) =>\n setResourceDraft((prev) =>\n prev\n ? {\n ...prev,\n cancellationHours:\n Math.round(Number(event.target.value)) || undefined,\n }\n : prev,\n )\n }\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'numeric' } }}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setResourceDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={\n !resourceDraft?.name?.trim() ||\n !((resourceDraft?.nightlyRateUsd ?? 0) > 0)\n }\n onClick={handleResourceSave}\n >\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog\n open={Boolean(walkIn)}\n onClose={() => setWalkIn(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Walk-in reservation'}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Resource\"\n value={walkIn?.resourceId ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, resourceId: event.target.value } : prev,\n )\n }\n size=\"small\"\n select\n sx={{ mt: 1 }}\n >\n {(resourceDocs ?? []).map((resource: any) => (\n <MenuItem key={resource.$id} value={resource.$id}>\n {resource.name}\n </MenuItem>\n ))}\n </TextField>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Check-in\"\n type=\"date\"\n value={walkIn?.checkIn ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, checkIn: event.target.value } : prev,\n )\n }\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n <TextField\n label=\"Check-out\"\n type=\"date\"\n value={walkIn?.checkOut ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, checkOut: event.target.value } : prev,\n )\n }\n size=\"small\"\n fullWidth\n slotProps={{ inputLabel: { shrink: true } }}\n />\n </Stack>\n <TextField\n label=\"Guest name\"\n value={walkIn?.guestName ?? ''}\n onChange={(event) =>\n setWalkIn((prev) =>\n prev ? { ...prev, guestName: event.target.value } : prev,\n )\n }\n size=\"small\"\n />\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setWalkIn(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!walkIn?.resourceId || !walkIn?.checkIn || !walkIn?.checkOut}\n onClick={handleWalkIn}\n >\n {'Reserve'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nReservationsCard.displayName = 'ReservationsCard'\n\nexport default ReservationsCard\n"],"names":["Aglyn","CommerceModel","CardDisplay","useConfirmationContext","ListPagination","useSnackbar","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Stack","TextField","Typography","collection","deleteDoc","doc","getDocs","limit","orderBy","query","setDoc","updateDoc","where","useCallback","useMemo","useState","useFirestore","useFirestoreCollection","usePagedCollection","writeGuardedBySeed","pluginDocsHelp","STATUS_COLOR","pending","confirmed","checked_in","checked_out","cancelled","no_show","usd","cents","toFixed","day","dayMs","Date","toISOString","slice","ReservationsCard","props","resourceDraft","hostId","firestore","enqueueSnackbar","confirm","data","resourceDocs","status","resourcesStatus","fromCache","resourcesFromCache","idField","rows","reservations","hasMore","page","setPage","pageSize","setPageSize","pageLimit","resourceNames","map","resource","$id","name","setResourceDraft","walkIn","setWalkIn","handleResourceSave","trim","nightlyRateUsd","seeded","id","_syntheticId","verdict","subject","unreadable","Boolean","createResourceUid","merge","ok","message","variant","persist","handleStatus","reservation","handleWalkIn","resourceId","checkIn","checkOut","find","item","checkInDayMs","parse","checkOutDayMs","quote","computeReservationQuote","problem","overlapping","catch","existing","docs","snapshot","createdAtMs","isRangeAvailable","guestName","guestEmail","nights","totalCents","depositCents","paidCents","now","header","help","anchor","contentGutterX","contentGutterY","spacing","direction","sx","alignItems","flex","minWidth","noWrap","color","size","onClick","title","description","confirmationText","confirmationButtonProps","then","depositPct","minNights","disabled","length","mt","join","label","String","replace","folio","folioCents","reduce","sum","entry","amountCents","balanceCents","includes","rowCount","onPageChange","onPageSizeChange","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","value","onChange","event","prev","target","autoFocus","placeholder","Number","slotProps","htmlInput","inputMode","weekendMultiplier","undefined","Math","round","cancellationHours","select","type","inputLabel","shrink","displayName"],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * What the product editor tells an author before a file becomes private paid media (AGL-2814, AGL-2847): the where-used rows
19
+ * the media library reports, which of them are other places than the product being edited, and the sentence the author
20
+ * confirms. Pure, so the native apps replay it from the contracts' function cases and say the same words.
21
+ */
22
+ /** A where-used row, reduced to what the confirmation reads. */
23
+ export interface PaidMediaReference {
24
+ kind: string;
25
+ id: string;
26
+ name: string;
27
+ hostId: string;
28
+ collectionId?: string;
29
+ }
30
+ /** How far the where-used scan got before the author was asked. */
31
+ export type PaidMediaUsageCheck = 'full' | 'partial' | 'failed';
32
+ /** Which list a flow adds to, for the words it uses. */
33
+ export type PaidMediaKind = 'video' | 'download';
34
+ export declare const PAID_MEDIA_COPY: Record<PaidMediaKind, {
35
+ title: string;
36
+ lead: string;
37
+ notAdded: string;
38
+ unusable: string;
39
+ choose: string;
40
+ }>;
41
+ /** The request body that names an asset's library to the media routes. */
42
+ export declare function paidMediaScopeBody(scope: string): {
43
+ orgId: string;
44
+ } | {
45
+ hostId: string;
46
+ };
47
+ /**
48
+ * Where an asset is used, apart from the product it is being added to. That
49
+ * product's own row is not a place making it private could break.
50
+ */
51
+ export declare function usesBesideProduct(references: readonly PaidMediaReference[], product: {
52
+ hostId: string;
53
+ productId?: string;
54
+ }): PaidMediaReference[];
55
+ /** What the author reads before a video becomes private. */
56
+ export declare function paidMediaConfirmation(options: {
57
+ others: readonly PaidMediaReference[];
58
+ checked: PaidMediaUsageCheck;
59
+ kind?: PaidMediaKind;
60
+ }): string;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * What the product editor tells an author before a file becomes private paid media (AGL-2814, AGL-2847): the where-used rows
18
+ * the media library reports, which of them are other places than the product being edited, and the sentence the author
19
+ * confirms. Pure, so the native apps replay it from the contracts' function cases and say the same words.
20
+ */ /** A where-used row, reduced to what the confirmation reads. */ export const PAID_MEDIA_COPY = {
21
+ video: {
22
+ title: 'Make this video private?',
23
+ lead: 'Members videos are private: buyers play them through a link that ' + 'expires, and nobody else can open them.',
24
+ notAdded: 'The video was not added.',
25
+ unusable: 'Members cannot play it until it is private.',
26
+ choose: 'Choose the video from the media library, so its links can expire.'
27
+ },
28
+ download: {
29
+ title: 'Make this file private?',
30
+ lead: 'Paid downloads are private: buyers get a link that expires, and nobody ' + 'else can open the file.',
31
+ notAdded: 'The file was not added.',
32
+ unusable: 'Buyers cannot download it until it is private.',
33
+ choose: 'Choose the file from the media library, so its links can expire.'
34
+ }
35
+ };
36
+ /** The request body that names an asset's library to the media routes. */ export function paidMediaScopeBody(scope) {
37
+ return scope.startsWith('org:') ? {
38
+ orgId: scope.slice('org:'.length).split(':')[0]
39
+ } : {
40
+ hostId: scope
41
+ };
42
+ }
43
+ /**
44
+ * Where an asset is used, apart from the product it is being added to. That
45
+ * product's own row is not a place making it private could break.
46
+ */ export function usesBesideProduct(references, product) {
47
+ return references.filter((reference)=>!(product.productId && reference.kind === 'plugin' && reference.collectionId === 'products' && reference.hostId === product.hostId && reference.id === product.productId));
48
+ }
49
+ /** What the author reads before a video becomes private. */ export function paidMediaConfirmation(options) {
50
+ var _options_kind;
51
+ const { others, checked } = options;
52
+ const { lead } = PAID_MEDIA_COPY[(_options_kind = options.kind) != null ? _options_kind : 'video'];
53
+ let usage;
54
+ if (others.length) {
55
+ const names = others.slice(0, 3).map((reference)=>`“${reference.name}”`).join(', ');
56
+ const more = others.length > 3 ? ` and ${others.length - 3} more` : '';
57
+ usage = `It is also used on ${names}${more}, and it stops showing there.`;
58
+ } else if (checked === 'full') {
59
+ usage = 'Nothing else uses it.';
60
+ } else if (checked === 'partial') {
61
+ usage = 'We could not check everywhere it might be used, so something else ' + 'could still rely on it.';
62
+ } else {
63
+ usage = 'We could not check where else it is used.';
64
+ }
65
+ return `${lead} ${usage} Any public link to it that was already shared stops working too.`;
66
+ }
67
+
68
+ //# sourceMappingURL=paid-media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/paid-media.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * What the product editor tells an author before a file becomes private paid media (AGL-2814, AGL-2847): the where-used rows\n * the media library reports, which of them are other places than the product being edited, and the sentence the author\n * confirms. Pure, so the native apps replay it from the contracts' function cases and say the same words.\n */\n\n/** A where-used row, reduced to what the confirmation reads. */\nexport interface PaidMediaReference {\n kind: string\n id: string\n name: string\n hostId: string\n collectionId?: string\n}\n\n/** How far the where-used scan got before the author was asked. */\nexport type PaidMediaUsageCheck = 'full' | 'partial' | 'failed'\n\n/** Which list a flow adds to, for the words it uses. */\nexport type PaidMediaKind = 'video' | 'download'\n\nexport const PAID_MEDIA_COPY: Record<\n PaidMediaKind,\n { title: string; lead: string; notAdded: string; unusable: string; choose: string }\n> = {\n video: {\n title: 'Make this video private?',\n lead:\n 'Members videos are private: buyers play them through a link that ' +\n 'expires, and nobody else can open them.',\n notAdded: 'The video was not added.',\n unusable: 'Members cannot play it until it is private.',\n choose: 'Choose the video from the media library, so its links can expire.',\n },\n download: {\n title: 'Make this file private?',\n lead:\n 'Paid downloads are private: buyers get a link that expires, and nobody ' +\n 'else can open the file.',\n notAdded: 'The file was not added.',\n unusable: 'Buyers cannot download it until it is private.',\n choose: 'Choose the file from the media library, so its links can expire.',\n },\n}\n\n/** The request body that names an asset's library to the media routes. */\nexport function paidMediaScopeBody(\n scope: string,\n): { orgId: string } | { hostId: string } {\n return scope.startsWith('org:')\n ? { orgId: scope.slice('org:'.length).split(':')[0] }\n : { hostId: scope }\n}\n\n/**\n * Where an asset is used, apart from the product it is being added to. That\n * product's own row is not a place making it private could break.\n */\nexport function usesBesideProduct(\n references: readonly PaidMediaReference[],\n product: { hostId: string; productId?: string },\n): PaidMediaReference[] {\n return references.filter(\n (reference) =>\n !(\n product.productId &&\n reference.kind === 'plugin' &&\n reference.collectionId === 'products' &&\n reference.hostId === product.hostId &&\n reference.id === product.productId\n ),\n )\n}\n\n/** What the author reads before a video becomes private. */\nexport function paidMediaConfirmation(options: {\n others: readonly PaidMediaReference[]\n checked: PaidMediaUsageCheck\n kind?: PaidMediaKind\n}): string {\n const { others, checked } = options\n const { lead } = PAID_MEDIA_COPY[options.kind ?? 'video']\n let usage: string\n if (others.length) {\n const names = others\n .slice(0, 3)\n .map((reference) => `“${reference.name}”`)\n .join(', ')\n const more = others.length > 3 ? ` and ${others.length - 3} more` : ''\n usage = `It is also used on ${names}${more}, and it stops showing there.`\n } else if (checked === 'full') {\n usage = 'Nothing else uses it.'\n } else if (checked === 'partial') {\n usage =\n 'We could not check everywhere it might be used, so something else ' +\n 'could still rely on it.'\n } else {\n usage = 'We could not check where else it is used.'\n }\n return `${lead} ${usage} Any public link to it that was already shared stops working too.`\n}\n\n"],"names":["PAID_MEDIA_COPY","video","title","lead","notAdded","unusable","choose","download","paidMediaScopeBody","scope","startsWith","orgId","slice","length","split","hostId","usesBesideProduct","references","product","filter","reference","productId","kind","collectionId","id","paidMediaConfirmation","options","others","checked","usage","names","map","name","join","more"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GAED,8DAA8D,GAe9D,OAAO,MAAMA,kBAGT;IACFC,OAAO;QACLC,OAAO;QACPC,MACE,sEACA;QACFC,UAAU;QACVC,UAAU;QACVC,QAAQ;IACV;IACAC,UAAU;QACRL,OAAO;QACPC,MACE,4EACA;QACFC,UAAU;QACVC,UAAU;QACVC,QAAQ;IACV;AACF,EAAC;AAED,wEAAwE,GACxE,OAAO,SAASE,mBACdC,KAAa;IAEb,OAAOA,MAAMC,UAAU,CAAC,UACpB;QAAEC,OAAOF,MAAMG,KAAK,CAAC,OAAOC,MAAM,EAAEC,KAAK,CAAC,IAAI,CAAC,EAAE;IAAC,IAClD;QAAEC,QAAQN;IAAM;AACtB;AAEA;;;CAGC,GACD,OAAO,SAASO,kBACdC,UAAyC,EACzCC,OAA+C;IAE/C,OAAOD,WAAWE,MAAM,CACtB,CAACC,YACC,CACEF,CAAAA,QAAQG,SAAS,IACjBD,UAAUE,IAAI,KAAK,YACnBF,UAAUG,YAAY,KAAK,cAC3BH,UAAUL,MAAM,KAAKG,QAAQH,MAAM,IACnCK,UAAUI,EAAE,KAAKN,QAAQG,SAAS,AAAD;AAGzC;AAEA,0DAA0D,GAC1D,OAAO,SAASI,sBAAsBC,OAIrC;QAEkCA;IADjC,MAAM,EAAEC,MAAM,EAAEC,OAAO,EAAE,GAAGF;IAC5B,MAAM,EAAEvB,IAAI,EAAE,GAAGH,eAAe,EAAC0B,gBAAAA,QAAQJ,IAAI,YAAZI,gBAAgB,QAAQ;IACzD,IAAIG;IACJ,IAAIF,OAAOd,MAAM,EAAE;QACjB,MAAMiB,QAAQH,OACXf,KAAK,CAAC,GAAG,GACTmB,GAAG,CAAC,CAACX,YAAc,CAAC,CAAC,EAAEA,UAAUY,IAAI,CAAC,CAAC,CAAC,EACxCC,IAAI,CAAC;QACR,MAAMC,OAAOP,OAAOd,MAAM,GAAG,IAAI,CAAC,KAAK,EAAEc,OAAOd,MAAM,GAAG,EAAE,KAAK,CAAC,GAAG;QACpEgB,QAAQ,CAAC,mBAAmB,EAAEC,QAAQI,KAAK,6BAA6B,CAAC;IAC3E,OAAO,IAAIN,YAAY,QAAQ;QAC7BC,QAAQ;IACV,OAAO,IAAID,YAAY,WAAW;QAChCC,QACE,uEACA;IACJ,OAAO;QACLA,QAAQ;IACV;IACA,OAAO,GAAG1B,KAAK,CAAC,EAAE0B,MAAM,iEAAiE,CAAC;AAC5F"}